14
Dec
2015
 

Swift Type Constrained Extensions: Express Yourself

by Matt Long

I admit it. I was pretty down when Swift was announced. I appreciated the finer points of the reasons for it, however, they weren’t convincing–at least not to me–not at first. I denied that it was actually “expressive” as the overall community was referring to it, however, that was before version 2. It’s at the point when the lights went on for me. I am now a convert and this post explains why–at least one of the reasons. The only question I ask now is while you can do some really cool expressive things, should you? You’ll see what I mean if you read on.

tl;dr;

You can download the code and playground for this post from github.

What Are Type Constrained Extensions

One of Swift’s most expressive and powerful features is type constrained extensions. If you’re new to Swift coming from Objective-C, think of it as a category in Objective-C yet with the ability to specify type criteria so that your category (extension) only applies to the class if it conforms to a certain type. For example, if you have an array of your own custom objects, you can create functions that are specific to an array of your objects. Here’s a quick snippet similar to one that I posted to Twitter a while back:

class Thing {
    var name:String
    init(name:String) {
        self.name = name
    }
}

extension Array where Element : Thing {
    func whereNamed(name:String) -> [Element] {
        return self.filter( { $0.name == name } )
    }
}

let thing1 = Thing(name: "Thing 1")
let thing2 = Thing(name: "Thing 2")
let thing3 = Thing(name: "Thing 3")

let things = [thing1, thing2, thing3]

let found = things.whereNamed("Thing 2")

After the code runs in a playground the found variable contains an array of Things with the name “Thing 2”. Obviously, you could just run filter on the Things array and achieve the same result, but the code demonstrates the capability succinctly. This is a very cool feature of the Swift language. You can create any function you want that is specific to arrays of your Thing objects.

Yes, you can, in essence, do the same in Objective-C using a category on NSArray, but there is no type safety. It would look something like this:

@interface NSArray (Additions)
- (NSArray*)whereNamed:(NSString*)name;
@end

@implementation NSArray (Additions)

- (NSArray*)whereNamed:(NSString*)name
{
    return [self filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"name = %@", name]];
}
@end

You can call whereNamed: on any NSArray and it will work fine as long as the object inside your array has a property named name.

Adding App Specific Functionality

Let’s talk about type constrained extensions by way of an example. If you’ve written code for mobile apps for any significant amount of time, the one thing you can do in your sleep is fetch JSON from a URL, parse it, and display it in your UI. Recently while prototyping I decided that instead of creating an object hierarchy I would just use the arrays of dictionaries as parsed by the NSJSONSerialization class. But then I got to thinking about it and realized that if I use type constraints, I can add some properties to my dictionaries that are specific to the data in the JSON feed. Here’s what I mean.

Let’s start with downloading JSON from iTunes. If you go to this URL: https://itunes.apple.com/us/rss/topmovies/limit=50/json it will download the JSON for the top 50 movies currently in the store. The output currently looks like this in a browser (well, assuming you have a JSON parser plugin).

iTunes JSON.

So given this url, let’s download it (in a view controller just for discussion purposes).

let url = NSURL(string: "https://itunes.apple.com/us/rss/topmovies/limit=50/json")

var movies = [[String:AnyObject]]()

override func viewDidLoad() {
    super.viewDidLoad()

    self.downloadData()
}

func downloadData() {
    
    let task = NSURLSession.sharedSession().dataTaskWithURL(self.url!) { [weak self] (data, response, error) -> Void in
        do {
            if let records = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject] {
                self?.movies = records.movieEntries
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self?.tableView.reloadData()
                })
            }
        } catch {
            
        }
    }
    
    task.resume()
}

In this code you can see we’ve created an array-of-dictionaries, String to AnyObject, variable called movies to hold our downloaded/parsed data and then we have a function to perform the download from our iTunes URL. The download gets spun off in the background using an NSURLSession in viewDidLoad. When the download completes, using an NSURLSessionDataTask, the data is now in memory and we can parse it using the NSJSONSerialization class. After parsing completes, we set our movies class variable to the parsed result and then we reload the table view causing it to display the data. Now look closely, though. See how the records variable is type casted as an optional [String:AnyObject] dictionary, yet it has a property on it called moveEntries. Where did that come from? How does a dictionary know anything about movieEntries? Take a look at this extension.

extension Dictionary where Key : StringLiteralConvertible, Value : AnyObject {
    var movieEntries : [[String:AnyObject]] {
        if let feed = self["feed"] as? [String:AnyObject] {
            return feed["entry"] as! [[String:AnyObject]]
        }
        return []
    }
}

This extension is constrained to a String (well, StringLiteralConverrible technically) key and an AnyObject value which matches the type of our dictionary. Once you’ve implemented this extension even code completion now knows about it and will happily suggest movieEntries to you when you type a dot after you type records in the download task completion handler.

You Can, But Should You?

I alluded to this notion earlier, but what I have just demonstrated is really cool and interesting, but you should think about whether it is something you should do in a shipping app. A number of thoughts come to mind. Say, for example, that you grab an object embedded deeper in your JSON/dictionary hierarchy. You would need to create some properties specific to that object as well and the intention of your code may lose clarity. The movieEntries property I demonstrated grabs the “feed” dictionary, and then grabs the “entry” array of dictionaries (refer to the feed JSON again if this is unclear). The “entry” array contains the movie entries themselves in an array. So, say you want to grab a property of a specific movie entry. Now your extension looks like this for the movieName property.

extension Dictionary where Key : StringLiteralConvertible, Value : AnyObject {
    var movieName : String {
        if let nameObj = self["im:name"] as? [String:AnyObject] {
            return nameObj["label"] as! String
        }
        return ""
    }
    
    var movieEntries : [[String:AnyObject]] {
        if let feed = self["feed"] as? [String:AnyObject] {
            return feed["entry"] as! [[String:AnyObject]]
        }
        return []
    }
}

Now, presumably, you’ll know by the context of the code when you want to access a property and when you don’t, however, as I mentioned, this reduces the code clarity making it more difficult to understand what’s going on. Read this code in a couple months after not having touched it in a while and you’ll see what I mean. While writing obscure code might be (arguably) great for your job security, it’s not a best practice. However, you have to admit that this capability is really cool! I really love the expressiveness it demonstrates.

So in spite of my warnings, let’s keep going. When my extension was finished, it looked like this:

extension Dictionary where Key : StringLiteralConvertible, Value : AnyObject {
    
    var movieName : String {
        if let nameObj = self["im:name"] as? [String:AnyObject] {
            return nameObj["label"] as! String
        }
        return ""
    }

    var movieSummary : String {
        if let summary = self["summary"] as? [String:AnyObject] {
            return summary["label"] as! String
        }
        return ""
    }
    
    var movieThumnailURL : NSURL? {
        if let imageItems = self["im:image"] as? [[String:AnyObject]] {
            let firstOne = imageItems[0]["label"] as! String
            return NSURL(string: firstOne)
        }
        return nil
    }
    
    var movieEntries : [[String:AnyObject]] {
        if let feed = self["feed"] as? [String:AnyObject] {
            return feed["entry"] as! [[String:AnyObject]]
        }
        return []
    }
}

I access the the movie object specific properties in my cellForRowAtIndexPath: in this next snippet. Notice I’ve grabbed the image URL to download the movie thumbnail (The download code is quick and dirty in the view controller. Don’t do this in a real shipping app).

var images = [NSIndexPath:UIImage]()

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)

    let object = self.movies[indexPath.row]           // Grab a movie object from our parsed array
    
    cell.textLabel!.text = object.movieName           // Movie name property
    cell.detailTextLabel!.text = object.movieSummary  // Movie summary property

    
    if let image = self.images[indexPath] {          // Use the cached image if it exists
        cell.imageView?.image = image
    } else {                                         // Otherwise download it
        if let url = object.movieThumnailURL {       // Movie thumbnail URL property
            let task = NSURLSession.sharedSession().dataTaskWithURL(url) { [weak self] (data, response, error) -> Void in
                if let data = data {
                    if let image = UIImage(data: data) {
                        self?.images[indexPath] = image  // Cache the image so it doesn't get re-requested
                        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                            self?.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
                        })
                    }
                    
                }
            }
            task.resume()           
        }
    }
    
    return cell
}

(The download and caching code here is immaterial to the goals of this post except to point out that I grabbed the URL using the same typed constrained dictionary. How you implement an image cache will likely vary depending on your app’s requirements.)

Primitives Need Not Apply, Well Sorta

At this point you’ve probably already thought of some cool ways you could leverage this capability (if not, you will), but as with everything else there are limitations that require you to think about your problem before attempting to solve it. Let me provide an example. When working with paths, CGPathRef or UIBezierPath, you don’t typically want to initialize your paths and pass them around, but instead you’ll keep around an array of CGPoints and only convert the points to a path when you’re ready to render it. Wouldn’t it be cool if an array of CGPoints had a calculated property called path that would return the CGPathRef you need to render it? Enter type constrained extensions.

extension Array where Element : CGPoint {
    var path : CGPathRef {
        let bezier = UIBezierPath()

        if self.count > 0 {
            bezier.moveToPoint(point)
        }

        for i in 1..< self.count {
            let point = self[i]
            bezier.addLineToPoint(point.point)
        }

        return bezier.CGPath
    }
}

(This is a simplistic example and likely wouldn't suit every need for building out a path, however, the point is again about the convenience that type constrained extensions provide)

And voila! Your array of elements now has a variable called path.

var points = [CGPoint]()
// .. add some points
let path = points.path

Except it doesn't work! Sorry. I know. You were all excited. Well fear not. We will make this work, but first you have to realize that type constraining is looking for objects. CGPoint is a primitive (struct). I know what you're thinking. Let's just wrap a CGPoint in a class and away we go. Well, you could do. How about this instead, though? Let's create a protocol that CGPoint can conform to and constrain it to that instead.

protocol Pointable {
    var point : CGPoint { get }
}

extension CGPoint : Pointable {
    var point : CGPoint {
        return self
    }
}

So now our type constrained array looks like this:

extension Array where Element : Pointable {
    var path : CGPathRef {
        let bezier = UIBezierPath()

        if self.count > 0 {
            bezier.moveToPoint(self[0].point)
        }

        for i in 1..< self.count {
            let point = self[i]
            bezier.addLineToPoint(point.point)
        }

        return bezier.CGPath
    }
}

Now, with a minor addition of a protocol and making CGPoint conform to it, our path variable on the array of Pointables works as expected.

Conclusion

I will conclude with one more practical example. If you've ever needed to manage the items in your navigation bar, you've probably wondered if there is a simpler way to do so other than managing the items array. Well, there really isn't, however, by using a type constrained array extension, you can add functionality that makes managing the items more intuitive. Here's what I mean. For an app I'm working on, I needed to remove one of the nav bar items in a form view controller when I loaded data for editing, but needed it to be available when creating a new object from the form data. Here's what I did:

extension Array where Element : UIBarButtonItem {
    mutating func removeItemWithTitle(title:String) {
        for item in self {
            if item.title == title {
                self.removeObject(item)
            }
        }
    }
}

Notice my function is marked with the mutating keyword to indicate that the function can make changes to the internal array. So now when I load my view controller, I just check to see if my object instance variable is non-nil indicating that we're editing and just remove the navigation item like this if we are:

self.navigationItem.rightBarButtonItems?.removeItemWithTitle("Search")

It's pretty powerful to be able to work this way. I'm finding this functionality to come in handy in a lot of situations. As I said at the beginning, I wasn't exactly enamored with Swift when I first started using it, but the improvements and features we've seen get added to the language in the more recent versions make it a language that is interesting, fun, and expressive to use. You can can call me a believer. I'm really starting to love using Swift. Until next time.