Code: Update objects like a pro in Swift (2 of 3)

Part 2 of 3

RNDM
6 min readJun 7, 2017

The Story so Far

If you have followed along with part one of this session, then you will be aware of the code we have created thus far. We currently have a simple protocol that allows us to create and update NSObject instances and all instances of NSObject sub classes:

protocol Updateable { }extension NSObject: Updateable { }extension Updateable where Self: NSObject {
@discardableResult
func update(completion: (Self) -> Void) -> Self {
completion(self)
return self
}
}
let label = UILabel()
label.update {
$0.text = "Hello World!"
}

In this session, we are going to take a deeper dive into creating a copy or clone of this object and then implementing the updating of the new object. Be warned, though, this topic is not for the faint-hearted as it delves into some of the finer and more obscure parts of cocoa code.

NOTE: As a prelude to this, we are going to be creating our own protocol (Clonable), but we could also make use of NSCopying if we want to. During these tutorials, I wanted to keep the protocol clean, without the requirement for further conformities, and so have opted for my own simple protocol.

Getting Started

Let’s take care of the easiest parts first. The first thing we want to do is create a new instance of the object we are going to clone. For this purpose, it is important that we have an initialiser of some sort on the object in question. Again, we will create a new protocol and we will give it an initialiser.

protocol Clonable {
init()
}

Once more we will assign this protocol to NSObject (our chosen base class):

extension NSObject: Clonable { }

Since NSObject already has an init(), we don’t have to write any code to make it conform. Nice!

Now let’s add some extension code under the NSObject binding:

extension Clonable where Self: NSObject {
var clone: Self {
let clone = Self.init()
return clone
}
}

This is pretty simple code, right? All we are doing is accessing the Self object, which we know has an init() call. We are initialising it and returning a new object. Awesome! Let’s give it a go:

class Person: NSObject {
var firstName: String = ""
var lastName: String = ""
var age: Int = 0
}
let person = Person()
person.update {
$0.firstName = "Paul"
$0.lastName = "Napier"
$0.age = 100
}
person2 = person.clone
print(person2.firstName)
print(person2.lastName)
print(person2.age)
// output: ""
// output: ""
// output: 0

Damn! We didn’t actually clone anything, we just made a new instance. Hmmm… What to do?

Deep in the Bowels of code!

What I am about to show you is some of the under-the-covers aspects of the code we use. Most of us never need to come into this area of code, but it is sometimes fun to have a little look behind the curtain, before jumping back out into the safer lands of our much higher development landscape. If this is not your usual area of comfort, please don’t be afraid… we won’t stay here long! If, however, you are already we versed in this… I hope you enjoy it!

We are going to use something similar to reflection to generate a list of the properties available to us, grab the values on the existing object and finally populate it in our new clone. And we are going to do it making use of Swift type safety and enums :D

Under the covers, Objective C is littered with OpaquePointers. This struct types represent pointers to types that cannot be represented in Swift for various reasons. However, we can get access to these OpaquePointers, and we will. The only one we are interested in at this time is the typeAlias objc_property_t, which represents an Objective C property.

First, however, we are going to get a an array of OpacquePointers from an UnsafeMutablePointer:

extension UnsafeMutablePointer {
func properties(length: Int) -> [OpaquePointer] {
return UnsafeBufferPointer(
start: self,
count: length).flatMap { $0 as? OpaquePointer}
}
}

The code above might seem complex, but all it is doing is effectively transforming the UnsafeMutablePointer via the UnsafeBufferPointer into an array of OpaquePointers. That’s it. We’ll leave that there.

Each NSObject subclass has a property list associated with the class that can be returned by calling the method ‘class_copyPropertyList’. For this, we need the class of the object we want, as well as an inout variable to be populated with the expected count of the number of properties. So again, we will extend NSObject to give us back the array of ‘objc_property_t’ (the type alias of the OpaquePointer we saw above).

extension NSObject {
static var properties: [objc_property_t] {
guard let classForKeyedArchiver = classForKeyedArchiver()
else { return [] } // 1
var count: UInt32 = 0 // 2
return class_copyPropertyList(
classForKeyedArchiver,
&count).properties(length: Int(count)) // 3
}
var properties: [objc_property_t] {
return type(of: self).properties
}
}

There’s a bit going on here, but it all follows the same logic as the paragraph above.

  1. Firstly, we ensure we have a class for the object we are on, since this is returned as AnyClass?, otherwise we will return an empty array.
  2. Secondly, we create a UInt32 variable to be passed in as an inout variable (signified by the ampersand prepended to its name in the method).
  3. Finally we call the properties method we created early, passing in the count cast to an standard Int. Then to access this from our our class, we pass in the handy method type(of: ) and grab the class properties.

If we run this on an NSObject instance then this is going to work great! However, if we subclass this we are only going to receive the properties at the top level. Let’s resolve this with a little recursion.

extension NSObject {
static var properties: [objc_property_t] {
guard let classForKeyedArchiver = classForKeyedArchiver()
else { return [] }
var count: UInt32 = 0
var properties = class_copyPropertyList(
classForKeyedArchiver,
&count).properties(length: Int(count))
if let parent = class_getSuperclass(classForKeyedArchiver)
as? NSObject.Type {
properties.append(contentsOf: parent.properties)
}
return properties

}
...
}

There’s one little bit of clean up I want to do right now, and that is to get the name of the property as a string. We can do this by extending the objc_property_t struct.

extension objc_property_t {
var name: String {
guard let name = property_getName(self),
let string = String(utf8String: name)
else { return "" }
return string
}
}

This just takes the pointer, gets a property name for it, and returns this if it is available. If there is no name, then it just returns an empty string.

Right! Let’s jump back to our clone variable:

extension Clonable where Self: NSObject {
var clone: Self {
let clone = Self.init()
for property in properties {
guard case let name = property.name,
let v = value(forKey: name)
else { continue }
clone.setValue(v, forKey: name)
}

return clone
}
}

We have inserted a loop here that checks the object has a value for the name of the property and if it does, then it sets that property on the clone! Whoop!

Getting past the Crash

But wait… Try running this code in a Playground and you’re going to start seeing some funky crashes about key-coding-compliance. These occur because we are not respecting the attributes of the properties we have generated.

Think of it this way. If we set a let, we are setting a constant. Something that is immutable and read only. However, here we are simply telling the new class that irrespective of what the attributes are, it should set the value we have as the new value. Hence the code-explosion. So in the final instalment, we are going to build the mechanism that will allow us to determine which properties we want to set and which ones we don’t.

Next Part: https://medium.com/@rndm.com/update-objects-like-a-pro-in-swift-3-of-3-981286194015

--

--