Swift is fun and cool, but Objc is a tank. Here is my favorite hack in objc adapted to swift.

After about two years of iOS development I really got tired of integrating web services into my application. If a login request succedes it would return something similar to this response:

{ "status" : "success" , "user" : { "first_name" : "Andrei" , "last_name" : "Puni" , "age" : 22 } }

After handling the networking part we usually get a NSDictionary with the values from the json/xml response. Now all we need to do is write the User class and create a User instance from that information:

class User { var firstName : String = "" var lastName : String = "" var age : Int = 0 class func createFrom ( info : NSDictionary ) -> User { var user = User () if let firstName = info [ "first_name" ] as ? String { user . firstName = firstName } if let lastName = info [ "last_name" ] as ? String { user . lastName = lastName } if let age = info [ "age" ] as ? Int { user . age = age } return user } }

This code is pretty simple and does the job, but it has some issues:

if you add or remove a property from the model you must also mirror the changes in the createFrom method

method you have to write code every time you create a new model

it’s repetitive and boring to code

The same problem appears when sending data to the server.

The Objective-c Runtime allows you to look under the hood of your code and play with it while it is bening executed. You can see the properties and methods of a class, it’s superclass, it’s subclasses and the methods and properties from a protocol. You can even change the way your program works at runtime by creating a new class, changing it’s super class or swizzling it’s methods.

By using Key Value Coding and the Objective-C Runtime to get the property list of the current class we can implement a NSObject extension that will give a general implementation for loading data from a JSON and converting an object to a NSDictionary :

extension NSObject { // Creates an object from a dictionary class func fromJson ( jsonInfo : NSDictionary ) -> Self { var object = self () ( object as NSObject ). load ( jsonInfo ) return object } func load ( jsonInfo : NSDictionary ) { for ( key , value ) in jsonInfo { let keyName = key as String if ( respondsToSelector ( NSSelectorFromString ( keyName ))) { setValue ( value , forKey : keyName ) } } } func asJson () -> NSDictionary { var json = NSMutableDictionary . dictionary () for name in propertyNames () { if let value : AnyObject = valueForKey ( name ) { json [ name ] = value } } return json } func propertyNames () -> [ String ] { var names : [ String ] = [] var count : UInt32 = 0 // Uses the Objc Runtime to get the property list var properties = class_copyPropertyList ( classForCoder , & count ) for var i = 0 ; i < Int ( count ); ++ i { let property : objc_property_t = properties [ i ] let name : String = NSString . stringWithCString ( property_getName ( property ), encoding : NSUTF8StringEncoding ) names . append ( name ) } free ( properties ) return names } }

code on github

When you make a subclass of NSObject the methods fromJson() and asJson() will just work.

class User : NSObject { var firstName : String = "" var lastName : String = "" var age : Int = 0 } let info = [ "firstName" : "Andrei" , "lastName" : "Puni" , "age" : 23 ] var user = User . fromJson ( info ) println ( user . firstName ) // "Andrei" println ( user . lastName ) // "Puni" println ( user . age ) // 23 println ( user . asJson ()) //{ // age = 23; // firstName = Andrei; // lastName = Puni; //}

This solution can be extended to support snake_case JSONs and camelCase objects.

func underscoreToCamelCase ( string : String ) -> String { var items : [ String ] = string . componentsSeparatedByString ( "_" ) var camelCase = "" var isFirst = true for item : String in items { if isFirst == true { isFirst = false camelCase += item } else { camelCase += item . capitalizedString } } return camelCase } extension NSObject { ... func load ( jsonInfo : NSDictionary ) { for ( key , value ) in jsonInfo { let keyName = key as String if ( respondsToSelector ( NSSelectorFromString ( keyName ))) { setValue ( value , forKey : keyName ) } else { let camelCaseName = underscoreToCamelCase ( keyName ) if ( respondsToSelector ( NSSelectorFromString ( camelCaseName ))) { setValue ( value , forKey : camelCaseName ) } } } } ... } let info = [ "first_name" : "Andrei" , "last_name" : "Puni" , "age" : 23 ] var user = User . fromJson ( info ) println ( user . firstName ) // "Andrei" println ( user . lastName ) // "Puni" println ( user . age ) // 23 println ( user . asJson ()) //{ // age = 23; // firstName = Andrei; // lastName = Puni; //}

There are a lot of optimizations that can be done on the load function. The Obj-C version I use is in APUtils, you can read the source code if you want to see what optimizations can be done.

This hack can also be applied to other kinds tasks. For example: loading data from a object in a table view cell.

// a reddit post class Post : NSObject { var title : String = "" var url : String = "" var ups : Int = 0 var downs : Int = 0 var score : Int = 0 } // cell for reddit post class PostCell : UITableViewCell { @ IBOutlet var titleLabel : UILabel ! @ IBOutlet var scoreLabel : UILabel ! func loadPost ( post : Post ) { self . titleLabel . text = post . title self . scoreLabel . text = "\(post.score)" } }

We can implement a UITableViewCell extension that has a general implementation for loading data from an object. It does this by using a naming convention: all label properties should end with Label (ex. titleLabel ).

extension UITableViewCell { func loadInfo ( info : NSDictionary ) { let propertyNames = self . propertyNames () let labels : [ String ] = propertyNames . filter { ( name : String ) -> Bool in return name . hasSuffix ( "Label" ) } for label in labels { let propertyName = ( label + "$$$" ). stringByReplacingOccurrencesOfString ( "Label$$$" , withString : "" ) if let value : AnyObject = info [ propertyName ] { let l : UILabel ? = valueForKey ( label ) as UILabel ? if l != nil { let textValue = "\(value)" l ! . text = textValue } } } } }

Now our implementation of PostCell will look like this:

class PostCell : UITableViewCell { @ IBOutlet var titleLabel : UILabel ! @ IBOutlet var scoreLabel : UILabel ! }

In interface builder we edit the interface and connect the IBOutlets .

And before passing the cell to the table view, we call the loadInfo method.

class ViewController : UIViewController , UITableViewDelegate , UITableViewDataSource { var posts : [ Post ] = [] ... func tableView ( tableView : UITableView , cellForRowAtIndexPath indexPath : NSIndexPath ) -> UITableViewCell { var cell = tableView . dequeueReusableCellWithIdentifier ( "cell" ) as PostCell var post = self . posts [ indexPath . row ] cell . loadInfo ( post . asJson ()) return cell } ... }

You can get the code from github or zip.

If you found this usefull, please take a moment and share it with your friends.