深入剖析OC Runtime(三) Message Forward Demo
原文地址
objc_msgSend
objc_msgSend的格式如下:
void objc_msgSend(id self, SEL cmd, parameter...)
OC中所有的調(diào)用方法,屬性賦值,都會轉(zhuǎn)化為上面的C函數(shù)發(fā)送消息。找不到方法時,會走消息轉(zhuǎn)發(fā)機制。
消息轉(zhuǎn)發(fā)
先上圖,
消息轉(zhuǎn)發(fā)全流程
消息轉(zhuǎn)發(fā)分為兩大階段。
- 第一階段先問接收者,所屬的類,看能否動態(tài)添加方法,以處理這個未知的selector,這叫動態(tài)方法解析。
- 第二階段涉及完整的消息轉(zhuǎn)發(fā)機制。
如果運行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那接收者自己就無法再以動態(tài)新增方法的手段來響應(yīng)包含該selector的消息了。此時運行期系統(tǒng)會請求接收者以其他手段來處理與消息相關(guān)的方法調(diào)用。這又分為兩步:a.請接發(fā)者看看有沒有其他對象能處理這條消息,若有,則runtime會把消息轉(zhuǎn)給那個對象,轉(zhuǎn)發(fā)過程結(jié)束。若沒備用的接收者,會啟動完整的消息轉(zhuǎn)發(fā)機制,runtime會把與消息有關(guān)的全部細節(jié)都封裝在NSInvocation對象中,再給接收者最后一次機會。
示例
由開發(fā)者來添加屬性的定義,并聲明為@dynamic,此類會自動處理屬性值的存放。
header中:
#import <Foundation/Foundation.h>
@interface SSAutoDictionary : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSNumber *number;
@end
上面定義了幾種數(shù)據(jù)類型。
在實現(xiàn)文件中:
#import "SSAutoDictionary.h"
#import <objc/runtime.h>
@interface SSAutoDictionary()
@property (nonatomic, strong) NSMutableDictionary *backStore;
@end
@implementation SSAutoDictionary
@dynamic name, date, number;
- (id)init {
if (self = [super init]) {
_backStore = [NSMutableDictionary new];
}
return self;
}
假如只這樣寫,從外部訪問屬性的set和get方法時,都會找不到方法,所以引入了resolveInstanceMethod:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else {
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
用前綴斷定是否為set,兩種情況下,都向類中新增一個處理該selector的子方法,這兩個方法以函數(shù)指針形式出現(xiàn)autoDictionarySetter和autoDictionaryGetter。Getter方法:
id autoDictionaryGetter(id self, SEL _cmd) {
SSAutoDictionary *mSelf = (SSAutoDictionary *)self;
NSString *key = NSStringFromSelector(_cmd);
return [mSelf.backStore objectForKey:key];
}
setter方法:
void autoDictionarySetter(id self, SEL _cmd, id value) {
SSAutoDictionary *mSelf = (SSAutoDictionary *)self;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[mSelf.backStore setObject:value forKey:key];
} else {
[mSelf.backStore removeObjectForKey:key];
}
}
使用的時候:
SSAutoDictionary *dict = [SSAutoDictionary new];
dict.date = [NSDate new];
NSLog(@"dict.date = %@", dict.date);
輸出:
RuntimeDemo[61043:9339954] dict.date = 2017-07-25 13:19:22 +0000