self与super
为了让大家更好地理解self和super,借用sunnyxx博客的ios程序员6级考试一道题目:下面的代码分别输出什么?@implementation Son : Father- (id)init{ self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self;}@endself表示当前这个类的对象,而super是一个编译器标示符,和self指向同一个消息接受者。在本例中,无论是[self class]还是[super class],接受消息者都是Son对象,但super与self不同的是,self调用class方法时,是在子类Son中查找方法,而super调用class方法时,是在父类Father中查找方法。当调用[self class]方法时,会转化为objc_msgSend函数,这个函数定义如下:id objc_msgSend(id self, SEL op, ...)这时会从当前Son类的方法列表中查找,如果没有,就到Father类查找,还是没有,最后在NSObject类查找到。我们可以从NSObject.mm文件中看到- (Class)class的实现:- (Class)class { return object_getClass(self);}所以NSLog(@"%@", NSStringFromClass([self class]));会输出Son。当调用[super class]方法时,会转化为objc_msgSendSuper,这个函数定义如下:id objc_msgSendSuper(struct objc_super *super, SEL op, ...)objc_msgSendSuper函数第一个参数super的数据类型是一个指向objc_super的结构体,从message.h文件中查看它的定义:/// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. __unsafe_unretained id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained Class class;#else __unsafe_unretained Class super_class;#endif /* super_class is the first class to search */};#endif结构体包含两个成员,第一个是receiver,表示某个类的实例。第二个是super_class表示当前类的父类。这时首先会构造出objc_super结构体,这个结构体第一个成员是self,第二个成员是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father类查找class方法,查找不到,最后在NSObject查到。此时,内部使用objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self class]调用相同,所以结果还是Son。隐藏参数self和_cmd当[receiver message]调用方法时,系统会在运行时偷偷地动态传入两个隐藏参数self和_cmd,之所以称它们为隐藏参数,是因为在源代码中没有声明和定义这两个参数。至于对于self的描述,上面已经解释非常清楚了,下面我们重点讲解_cmd。_cmd表示当前调用方法,其实它就是一个方法选择器SEL。一般用于判断方法名或在Associated Objects中唯一标识键名,后面在Associated Objects会讲到。方法解析与消息转发[receiver message]调用方法时,如果在message方法在receiver对象的类继承体系中没有找到方法,那怎么办?一般情况下,程序在运行时就会Crash掉,抛出 unrecognized selector sent to …类似这样的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。Method ResolutionFast ForwardingNormal ForwardingMessage Forward from GoogleMethod Resolution首先Objective-C在运行时调用+ resolveInstanceMethod:或+ resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:@interface Message : NSObject- (void)sendMessage:(NSString *)word;@end@implementation Message- (void)sendMessage:(NSString *)word{ NSLog(@"normal way : send message = %@", word);}@end如果我在viewDidLoad方法中创建Message对象并调用sendMessage方法:- (void)viewDidLoad { [super viewDidLoad]; Message *message = [Message new]; [message sendMessage:@"Sam Lau"];}控制台会打印以下信息:normal way : send message = Sam Lau但现在我将原来sendMessage方法实现给注释掉,覆盖resolveInstanceMethod方法:#pragma mark - Method Resolution/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(sendMessage:)) { class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) { NSLog(@"method resolution way : send message = %@", word); }), "v@*"); } return YES;}控制台就会打印以下信息: method resolution way : send message = Sam Lau注意到上面代码有这样一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type Encodings如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)Fast Forwarding如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。继续上面Message类的例子,将sendMessage和resolveInstanceMethod方法注释掉,然后添加forwardingTargetForSelector方法的实现:#pragma mark - Fast Forwarding- (id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == @selector(sendMessage:)) { return [MessageForwarding new]; } return nil;}此时还缺一个转发消息的类MessageForwarding,这个类的设计与实现如下:@interface MessageForwarding : NSObject- (void)sendMessage:(NSString *)word;@end@implementation MessageForwarding- (void)sendMessage:(NSString *)word{ NSLog(@"fast forwarding way : send message = %@", word);}@end此时,控制台会打印以下信息:fast forwarding way : send message = Sam Lau这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。Normal Forwarding如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。继续前面的例子,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelector和forwardInvocation方法的实现:#pragma mark - Normal Forwarding- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (!methodSignature) { methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"]; } return methodSignature;}- (void)forwardInvocation:(NSInvocation *)anInvocation{ MessageForwarding *messageForwarding = [MessageForwarding new]; if ([messageForwarding respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:messageForwarding]; }}关于这个例子的示例代码请到github下载。三种方法的选择Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?Method Resolution:由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。Fast Forwarding:它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。Normal Forwarding:它跟Fast Forwarding一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。Associated ObjectsCategories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class. (Programming with Objective-C)当想使用Category对已存在的类进行扩展时,一般只能添加实例方法或类方法,而不适合添加额外的属性。虽然可以在Category头文件中声明property属性,但在实现文件中编译器是无法synthesize任何实例变量和属性访问方法。这时需要自定义属性访问方法并且使用Associated Objects来给已存在的类Category添加自定义的属性。Associated Objects提供三个API来向对象添加、获取和删除关联值:void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )id objc_getAssociatedObject (id object, const void *key )void objc_removeAssociatedObjects (id object )其中objc_AssociationPolicy是个枚举类型,它可以指定Objc内存管理的引用计数机制。typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */};下面有个关于NSObject+AssociatedObject Category添加属性associatedObject的示例代码:NSObject+AssociatedObject.h@interface NSObject (AssociatedObject)@property (strong, nonatomic) id associatedObject;@endNSObject+AssociatedObject.m@implementation NSObject (AssociatedObject)- (void)setAssociatedObject:(id)associatedObject{ objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (id)associatedObject{ return objc_getAssociatedObject(self, _cmd);}@endAssociated Objects的key要求是唯一并且是常量,而SEL是满足这个要求的,所以上面的采用隐藏参数_cmd作为key。Method SwizzlingMethod Swizzling就是在运行时将一个方法的实现代替为另一个方法的实现。如果能够利用好这个技巧,可以写出简洁、有效且维护性更好的代码。可以参考两篇关于Method Swizzling技巧的文章:nshipster Method SwizzlingMethod Swizzling 和 AOP 实践Aspect-Oriented Programming(AOP)类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern),AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。危险性Method Swizzling就像一把瑞士小刀,如果使用得当,它会有效地解决问题。但使用不当,将带来很多麻烦。在stackoverflow上有人已经提出这样一个问题:What are the Dangers of Method Swizzling in Objective C?,它的危险性主要体现以下几个方面:Method swizzling is not atomicChanges behavior of un-owned codePossible naming conflictsSwizzling changes the method's argumentsThe order of swizzles mattersDifficult to understand (looks recursive)Difficult to debug总结虽然在平时项目不是经常用到Objective-C的Runtime特性,但当你阅读一些iOS开源项目时,你就会发现很多时候都会用到。所以深入理解Objective-C的Runtime数据结构、消息转发机制有助于你更容易地阅读和学习开源项目。