Swift Rewrite Redux

After talking with several people and seeing other implementations of the "Swift Rewrite Challenge", I decided to revise my code using a more complete approach. In my first rewrite, I tried to stay true to the original function. This time, I broke the process down into different functions and extensions, and added functionality that I had originally thought would be useful, but didn't think was in scope. If you haven't, check out the first post, and then take a look at the new code here.

Extensions

The first thing I wanted to do was move the function into a UIImage extension. I usually prefer extensions to "helper" functions, since I feel that they better capture the scope of responsibility, while still avoiding the problem of having a bloated class or file. In fact, I ended up putting all of my code into extensions, which happened naturally as I worked through the requirements.

The cool thing about extensions is that you can have a kind of specialized responsibility/functionality scope for each one. For example, I made two CGSize extensions in my code, each with a scaled function that returns a CGSize. One of them was a general-use CGSize function, which allows you to scale a CGSize by some factor. The other function allows you to scale a CGSize to fit another size, using my UIImage.ScaleMode enum. To me, bounding the extensions separately into "things that are general-use" and "things that work with UIImage" made more sense than putting them both into a "things that scale" extension. I wouldn't want to keep adding to my single "things that scale" extension every time another class wanted to scale CGSize in a certain way. It seems more like that class' responsibility than CGSize's in general.

Self-Documenting

By extending classes and structs so that each part of the transformation is handled in the proper domain, I naturally ended up with a composition-based architecture with small functions. I also created some types, UIImage.ScaleMode and CGRect.Anchor, to describe some of the functionality I wanted to add (scaling aspect-fit as well as aspect-fill, and cropping by anchoring in one of nine positions, rather than just centered). This had the effect of requiring fewer comments, since the function names and types are descriptive. One of the comments in my first rewrite was explaining the code that created the drawing rect with an inset such that it would be centered in the cropping size. In the new version, creating a drawing rect with an initializer taking a CGSize called drawingSize, a CGRect.Anchor called croppingAnchor, and a CGSize called containerSize seems to pretty much explain itself. I didn't make these with the intention of removing comments — this is just an example of what people mean when they talk about small functions and proper types begetting "self-documenting code".

Explicit Content

Something I noticed when looking closer at the drawing code is that UIGraphicsGetImageFromCurrentImageContext() returns a force-unwrapped optional. That is, the definition is:

func UIGraphicsGetImageFromCurrentImageContext() -> UIImage!

In my original rewrite, I had applied it like this:

let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!

Swift 3 alert! Adding the ! is necessary here, because implicitly-unwrapped optionals are gone in Swift 3. However, in my new code, I explicitly declare scaledImage like this:

let scaledImage: UIImage

and then initialize it later like this:

scaledImage = UIGraphicsGetImageFromCurrentImageContext()

Note that initializing it without an explicit type requires the !, since the implicitly-unwrapped status doesn't carry over. But declaring it with an explicit UIImage type forces the unwrap.

So in Swift 3:

func UIGraphicsGetImageFromCurrentImageContext() -> UIImage!
let scaledImage = UIGraphicsGetImageFromCurrentImageContext() //scaledImage: UIImage?
let scaledImage2 = UIGraphicsGetImageFromCurrentImageContext()! //scaledImage2: UIImage, we forced the unwrap
let scaledImage3: UIImage = UIGraphicsGetImageFromCurrentImageContext()! //scaledImage3: UIImage, we forced the unwrap again, this time by specifying the type as not Optional

Guard and Exit

The last thing I want to note is that I decided against my previous reluctance to use guard due to the double-negative in guard !image.size.equalTo(newSize) else {}. I thought about it and discussed with others, and I really agree that you should use guard when performing these kinds of scope-terminating checks. It's what guard was made for, and it's better to add a second line of code to make it clearer if you must, so that it is semantically clearer and compiler-guaranteed to exit the scope.

As always, if you have questions or comments, you can hit me up on Twitter, the iOS Developers Slack, or in the comments here.

Leave A Comment

Your email address will not be published. Required fields are marked *