The model layer is the core component of your application. While serialization technologies (e.g. JSON, XML), transport protocols (e.g. HTTP, WebSockets) and even databases (e.g. SQL, NoSQL, MySQL, MongoBD) can be changed in your application, your model is your business logic and as such is less likely to change in a well-defined application. In this chapter you will learn different techniques with examples for properly defining your model, with the goal of high performance, code reusability, and low memory footprint.
In a model entity, values like text, numbers, links and data are attributes. In Swift, Protocol Oriented Programming improves the programming model with value types, as well as lower memory footprint for value types and static dispatch of methods. We can further optimize our model by using the right type of value in certain situations. Lets start by representing a user in Swift.
let AdminUserType = "admin"
let SpeakerUserType = "speaker"
let AttendeeUserType = "attendee"
/// A user.
public struct Person {
public let identifier: String
public let created: String
public var firstName: String
public var lastName: String
public var userType: String
}
let user = Person(identifier: "32A8ACDD-C74A-4FDC-84C1-13F139E783D0", created: "\(Date())", firstName: "John", lastName: "Apple", userType: AdminUserType)
print(user)
In this example, the firstName
and lastName
values are variable strings, while identifier
is a constant UUID string and created
is the date when the user was created. We should see the following output for your user.
Person(identifier: "32A8ACDD-C74A-4FDC-84C1-13F139E783D0", created: "2016-10-23 07:16:04 +0000", firstName: "John", lastName: "Apple", userType: "admin")
This description is very informative and we can see the user has valid values in this case. However if our identifiers are UUIDs, it is still possible to provide an invalid or empty string, the same case being for date
. The userType
attribute is even more constrained and only accepts three possible values. Another issue is memory footprint. For this simple Person
struct, we are allocating 5 string buffers on the heap. In this case the userType
property is using a singleton string buffer because we assigned it from a static string. But if we were to created this entity and populate its values from the JSON in a HTTP request, or values in our database, the string would be dynamically allocated on the heap and always point to a different buffer, despite having the same value. Lets refactor this model to make is safer and use less memory.
import Foundation
public enum UserType: String {
case admin
case speaker
case attendee
}
/// A user.
public struct Person {
public let identifier: UUID
public let created: Date
public var firstName: String
public var lastName: String
public var userType: UserType
}
let identifier = UUID(uuidString: "32A8ACDD-C74A-4FDC-84C1-13F139E783D0")!
let user = Person(identifier: identifier, created: Date(), firstName: "John", lastName: "Apple", userType: .admin)
print(user)
print("Admin string value: \(UserType.admin.rawValue)")
print("Admin is \(unsafeBitCast(UserType.admin, to: UInt8.self))")
print("Speaker is \(unsafeBitCast(UserType.speaker, to: UInt8.self))")
print("Attendee is \(unsafeBitCast(UserType.attendee, to: UInt8.self))")
Running this code will show the following output:
Person(identifier: 32A8ACDD-C74A-4FDC-84C1-13F139E783D0, created: 2016-10-23 07:16:04 +0000, firstName: "John", lastName: "Apple", userType: UserType.admin)
Admin string value: admin
Admin is 0
Speaker is 1
Attendee is 2
In this revised version, the user represents the same data as before. However, identifier
is a 128 bit UUID
which ensures we use less memory than its string counterpart, as well as using memory on the stack instead of a string buffer on the heap. Using a UUID
instead of a String
provides more safety without any validation overhead, and ensures we can only create a Person
by providing a valid UUID
. We also use less memory by making created
a Date
, which makes it a Double
value on the stack, instead of a string on the heap. For the UserType
attribute, we created an enumeration which will enforce that only the three posible string values can be provided when creating a Person
. In addition to the input validation, when an instance of Person
is copied, only a single byte is being copied for the UserType
attribute. While UserType
represents a string value, it is internally stored as a byte (UserType.admin
is 0x00
, ``UserType.speaker is
0x01 and
UserType.attendee is
0x02`). Keep this in mind when defining your model. Using the correct types can improve application performance, and require less code for input validation.