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

Part 1 of 3

RNDM
4 min readJun 7, 2017

Since I have been doing a lot of work across different languages recently, especially in the realms of React and React Native with JavaScript, my development perspective has been filled with an abundance of different design patterns and coding concepts. You can check out some of these on my profile, my YouTube channel or at the Sydney Cocoaheads site. It is from these paradigms that I have derived inspiration for the below code! But enough preaching about React and React Native, I’m going to focus on my other love… Swift.

At my day job, I am a big advocate of Protocol Oriented Programming, Enums and have recently dipped my toe into the world of Functional Programming. It is my firm belief that each and every design pattern has a time and a place within code and that each tool at our disposal has pros and cons, and should be measured up next to the requirement at hand.

Having said that, the style in which I have become accustomed to writing in JavaScript has influenced the style in Swift and vice-versa. In this piece I hope to address one glaring issue that has bugged me, since I first wrote a line of code all those years ago.

What’s the fuss all about?

Take a look at the code below:

let label = UILabel()
label.text = "Hello World"
label.textColor = UIColor.red
label.isHidden = false
label.backgroundColor = UIColor.green
label.font = UIFont(name: "Helvetica", size: 12)

We are initialising the label then setting the properties on the class. So what? What’s wrong with the code? Well, let’s create another label:

let label2 = UILabel()
label2.text = "Hello Universe"
label2.textColor = UIColor.orange
label.isHidden = true
label2.backgroundColor = UIColor.white
label2.font = UIFont(name: "Helvetica", size: 14)

Nothing wrong with this, right? Wrong! Read it again if you didn’t see it the first time. Silly me, I introduced an issue by copying and pasting then rewriting. I was expecting the second label to be hidden, but instead the second one was. Aargh!! OK, so one cool feature of swift is inline initialisation. This is where we are able to determine the properties inside of a block while initialising the object:

let label: UILabel = {
$0.text = "Hello World"
$0.textColor = UIColor.red
$0.backgroundColor = UIColor.green
$0.font = UIFont(name: "Helvetica", size: 12)
$0.isHidden = false
return $0
}(UILabel())
let label2: UILabel = {
$0.text = "Hello Universe"
$0.textColor = UIColor.orange
$0.backgroundColor = UIColor.white
$0.font = UIFont(name: "Helvetica", size: 14)
$0.isHidden = true
return $0
}(UILabel())

This type of initialisation is great for building out a custom view with defined properties, without the need to rewrite the name of the instance. The dollar assigned default property in the closure reduces the risk of developer error. Wonderful! So I am happy, right? Nope. I’m still sad :(

My issue is that I might want to change some of these properties later in an update. Now, I still have to write out the name of my object, and hope that I don’t make any silly mistakes. Nooooooo!

OK, I’m calm again. Let’s get writing a solution to this problem.

Ahhh, so what’s the solution?

Let’s quickly review what I want to have. Basically, my code should have the same simplistic design that we have above. I want to update cleanly, inside a block of code and then reference the object I am accessing as a dollar assigned default property.

The solution we are going to use is a closure. At first we are just going to make this very simple. We will extend the UILabel class with a method called update and pass in a completion block.

extension UILabel {
func update(completion: (UILabel) -> Void) {
completion(self)
}
}

Now as simple as that, I can update my object using a trailing completion block by accessing the dollar property inside the completion.

label.update {
$0.text = "Hello Universe"
$0.textColor = UIColor.orange
}

This is great, but unfortunately it is limited. I wonder if there is a way of making this more versatile… Well, I probably wouldn’t be writing this if there wasn’t, right? Right!

Rise of the Protocol

There is a limitation to the extension method of implementing this. Sadly, when we are writing out the method, we are not able to access the Self as a generic expression. So if we want to have this same functionality for different classes, then we are not able to pass in the correct type, or we have to hardcode the type ourselves. Ewww!

I will create a shell protocol:

protocol Updateable { }

And I will ask NSObject to conform to it:

extension NSObject: Updateable { } // Pleeeaaase

Finally, for the magic:

extension Updateable where Self: NSObject {
func update(completion: (Self) -> Void) {
completion(self)
}
}

And with this, I can now access any NSObject subclass and pass in the correct type. This includes all views, view controllers, and even NSArrays and NSDictionaries (if you are still using them…). So my code might look like this:

let view = UIView()
view.update {
$0.backgroundColor = .red
$0.frame = .zero
}
let person = Person()
person.update {
$0.firstName = "Paul"
$0.lastName = "Napier"
$0.age = 21 // Again...
}

Pretty powerful for such a simple protocol, huh? But wait… there’s more! With one weird little trick, we will now pass back the object itself:

extension Updateable where Self: NSObject {
@discardableResult
func update(completion: (Self) -> Void) -> Self {
completion(self)
return self
}
}

With this little trick we can now access the label being passed back to us. Why? Well, why not… What? You still want to know why?? Ok. Well now, I can choose not to inline the initialisation, but can initialise and inline the property setting through an update:

let person = Person().update {
$0.firstName = "Paul"
$0.lastName = "Napier"
$0.age = 21 // Again...
}

Clone Wars

Above we have outlined some pretty cool functionality, but I really wanted to push this to the next level. In the next instalment, we will take a good hard look at how we can make a clone of our existing object, retain all the properties it has and then update it! Pretty exciting stuff, hey?

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

--

--