Subclassing is something any object oriented programmer is pretty familiar with. Common examples would be that a square is a subclass of a rectangle. This means that all squares are rectangles, and hold all properties of rectangles but not all rectangles are squares. Squares are a special subset of rectangles that have both sides being the same length. Objective-C has become so evolved that you may use this simplified conceptualization for all objects in Objective-C when in reality there is much more complexity behind the scenes in some of the most common Objective-C objects.
For certain Objective-C objects such as NSString, NSNumber, NSArray, NSData, NSDictionary a design pattern called Class Clustering is utilized to present a public facing class to represent a collection of classes used behind the scenes. While this ends up creating some really powerful objects in the default Objective-C API, it presents some challenges to subclassing. Apple speaks to the challenges in subclassing the abstract class superclass representing a class cluster. When doing this a class must:
This list of tasks can seem a little overwhelming at first but it is pretty simple once you get into it. Today we are going to subclass NSString. The example we will make will encrypt a string upon initialization, the reason we want this to be a subclass rather than a category is that we want to be able to distinguish encrypted NSString object types from non encrypted object types.
ICBEncryptedString.h
Below is the our encrypted string object header. As you can see we are indeed subclassing the cluster’s abstract super class, in the case NSString. We are also declaring our own storage there with out NSString backing store instance variable. The great thing about this abstract parent class subclassing is that you can still use the type you are most used to as your backing store. If speed or memory was important to you here, you could also implement it with more lightweight C based structures as well. With that done we declare a new special class level initializer and override initWithString:
#import <Foundation/Foundation.h> @interface ICBEncryptedString : NSString { NSString *_backingStore; } +(id)encryptedStringWithString:(NSString *)string; -(id)initWithString:(NSString*)string; @end
ICBEncryptedString.m
According to our to do list the next thing we need to do is override all initializer methods of the super. This would be very annoying to do for NSString so I have done it with 2 specific methods that I will always use when initializing ICBEncryptedString’s. So in our initializer here we simply take he string provided to us, SHA1 it, and then set our _backingStore NSString instance variable to the now encryptedString. The only remaining thing to do is override the superclass’s primitive method. NSString’s documentation tells us that the required primitive methods to override are length and characterAtIndex:, essentially, every other instance method for NSString relies on these methods as their backing. With this done we now have a distinguishable Encrypted String class that has all of the abilities of NSString but does encrypting for us on initialization. Check out the class implementation below.
You can find the NSString category for SHA1 at the awesome new site iOS Boiler Blate
#import "MaaSEncryptedString.h" #import "NSString+Helper.h" @implementation MaaSEncryptedString +(NSString*)encryptedString:(NSString*)string { return [[NSString stringWithFormat:@"%@", [string SHA1]]; } +(id)encryptedStringWithString:(NSString *)string { return [[[MaaSEncryptedString alloc] initWithString:string] autorelease]; } -(id)initWithString:(NSString*)string { if(self = [self init]) { _backingStore = [[MaaSEncryptedString encryptedString:string] copy]; } return self; } -(void)dealloc { [_backingStore release]; [super dealloc]; } -(NSUInteger)length { return [_backingStore length]; } -(unichar)characterAtIndex:(NSUInteger)index { return [_backingStore characterAtIndex:index]; } @end
Follow me on Twitter @cruffenach