Runtime消息傳遞:
一個對象的方法像這樣[obj foo],編譯器轉(zhuǎn)成消息發(fā)送objc_msgSend(obj, foo),Runtime時執(zhí)行的流程是這樣的:
首先,通過obj的isa指針找到它的class;
在class的method list找foo;
如果class中沒到foo,繼續(xù)往它的superclass中找 ;
一旦找到foo這個函數(shù),就去執(zhí)行它的實(shí)現(xiàn)IMP。、
但這種實(shí)現(xiàn)有個問題,效率低。但一個class 往往只有 20% 的函數(shù)會被經(jīng)常調(diào)用,可能占總調(diào)用次數(shù)的 80% 。每個消息都需要遍歷一次objc_method_list 并不合理。如果把經(jīng)常被調(diào)用的函數(shù)緩存下來,那可以大大提高函數(shù)查詢的效率。這也就是objc_class 中另一個重要成員objc_cache 做的事情 - 再找到foo 之后,把foo 的method_name 作為key ,method_imp作為value 給存起來。當(dāng)再次收到foo 消息的時候,可以直接在cache 里找到,避免去遍歷objc_method_list。從前面的源代碼可以看到objc_cache是存在objc_class 結(jié)構(gòu)體中的。
緩存查找是Hash查找? 排序的方法方法列表查找是二分查找???未排序的方法方法列表查找是遍歷查找
對象(object),類(class),方法(method)這幾個的結(jié)構(gòu)體:
//對象
struct objc_object {
? ? Class isa? OBJC_ISA_AVAILABILITY;
};
//類
struct objc_class {
? ? Class isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
? ? Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? const char *name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long version? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long instance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_ivar_list *ivars? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_method_list **methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_cache *cache? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_protocol_list *protocols? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
? ? struct objc_method_list *obsolete? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? int method_count? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#ifdef __LP64__
? ? int space? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
? ? /* variable length structure */
? ? struct objc_method method_list[1]? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
//方法
struct objc_method {
? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
}
消息傳遞用到的一些概念:
isa指針:
指針型isa :isa的值代表class的地址
非指針型isa:isa的值的部分代表class的地址
實(shí)例對象的 isa 指向類對象
類對象的 isa 指向元類對象
元類對象的 isa 指向元類的基類
類對象(objc_class)
Objective-C類是由Class類型來表示的,它實(shí)際上是一個指向objc_class結(jié)構(gòu)體的指針。
objc_class結(jié)構(gòu)體的定義如下:
struct objc_class {
? ? Class _Nonnull isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
? ? Class _Nullable super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? const char * _Nonnull name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long version? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? long instance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_ivar_list * _Nullable ivars? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_method_list * _Nullable * _Nullable methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_cache * _Nonnull cache? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? struct objc_protocol_list * _Nullable protocols? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
實(shí)例(objc_object)
/// Represents an instance of a class.
struct objc_object {
? ? Class isa? OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
元類(Meta Class)
就是從isa指針指向的結(jié)構(gòu)體創(chuàng)建,類對象的isa指針指向的我們稱之為元類(metaclass)
通過上圖我們可以看出整個體系構(gòu)成了一個自閉環(huán),struct objc_object結(jié)構(gòu)體實(shí)例它的isa指針指向類對象,
類對象的isa指針指向了元類,super_class指針指向了父類的類對象,根類指向nil
而元類的super_class指針指向了父類的元類,那元類的isa指針又指向了自己。
Method(objc_method)
runtime.h
/// An opaque type that represents a method in a class definition.代表類定義中一個方法的不透明類型
typedef struct objc_method *Method;
struct objc_method {
? ? SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
Method和我們平時理解的函數(shù)是一致的,就是表示能夠獨(dú)立完成一個功能的一段代碼
SEL(objc_selector)
selector是SEL的一個實(shí)例。
IMP
類緩存(objc_cache) 局部性原理
Category(objc_category)
Runtime消息轉(zhuǎn)發(fā):
動態(tài)方法解析?
首先,Objective-C運(yùn)行時會調(diào)用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機(jī)會提供一個函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)并返回YES, 那運(yùn)行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? // Do any additional setup after loading the view, typically from a nib.
? ? //執(zhí)行foo函數(shù)
? ? [self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
? ? if (sel == @selector(foo:)) {//如果是執(zhí)行foo函數(shù),就動態(tài)解析,指定新的IMP
? ? ? ? class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
? ? ? ? return YES;
? ? }
? ? return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
? ? NSLog(@"Doing foo");//新的foo函數(shù)
}
備用接收者
如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機(jī)會。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
? ? return YES;//返回YES,進(jìn)入下一步轉(zhuǎn)發(fā)
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
? ? if (aSelector == @selector(foo)) {
? ? ? ? return [Person new];//返回Person對象,讓Person對象接收這個消息
? ? }
? ? return [super forwardingTargetForSelector:aSelector];
}
完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。
首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil ,Runtime則會發(fā)出 -doesNotRecognizeSelector: 消息,程序這時也就掛掉了。如果返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation 對象并發(fā)送 -forwardInvocation:消息給目標(biāo)對象。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
? ? return YES;//返回YES,進(jìn)入下一步轉(zhuǎn)發(fā)
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
? ? return nil;//返回nil,進(jìn)入下一步轉(zhuǎn)發(fā)
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
? ? if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
? ? ? ? return [NSMethodSignature signatureWithObjCTypes:"v@:"];//簽名,進(jìn)入forwardInvocation
? ? }
? ? return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
? ? SEL sel = anInvocation.selector;
? ? Person *p = [Person new];
? ? if([p respondsToSelector:sel]) {
? ? ? ? [anInvocation invokeWithTarget:p];
? ? }
? ? else {
? ? ? ? [self doesNotRecognizeSelector:sel];
? ? }
}
Runtime應(yīng)用:
$\color{red}{關(guān)聯(lián)對象(Objective-C Associated Objects)給分類增加屬性}$
//關(guān)聯(lián)對象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
這里,第一個參數(shù)是主對象,第二個參數(shù)是鍵,第三個參數(shù)是關(guān)聯(lián)的對象,第四個參數(shù)是存儲策略:是枚舉,定義了內(nèi)存管理語義。
//獲取關(guān)聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
//移除關(guān)聯(lián)的對象
void objc_removeAssociatedObjects(id object)
本質(zhì)原理:
關(guān)聯(lián)對象由AssociationsManage管理并在AssociationsHashMap中存儲,所有對象的關(guān)聯(lián)內(nèi)容都存在一個全局的容器中。
方法魔法(Method Swizzling)方法添加和替換和KVO實(shí)現(xiàn)
@implementation ViewController
+ (void)load {
? ? static dispatch_once_t onceToken;
? ? dispatch_once(&onceToken, ^{
? ? ? ? Class class = [self class];
? ? ? ? SEL originalSelector = @selector(viewDidLoad);
? ? ? ? SEL swizzledSelector = @selector(jkviewDidLoad);
? ? ? ? Method originalMethod = class_getInstanceMethod(class,originalSelector);
? ? ? ? Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
? ? ? ? //judge the method named? swizzledMethod is already existed.
? ? ? ? BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
? ? ? ? // if swizzledMethod is already existed.
? ? ? ? if (didAddMethod) {
? ? ? ? ? ? class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
? ? ? ? }
? ? ? ? else {
? ? ? ? ? ? method_exchangeImplementations(originalMethod, swizzledMethod);
? ? ? ? }
? ? });
}
- (void)jkviewDidLoad {
? ? NSLog(@"替換的方法");
? ? [self jkviewDidLoad];
}
- (void)viewDidLoad {
? ? NSLog(@"自帶的方法");
? ? [super viewDidLoad];
}
@end
消息轉(zhuǎn)發(fā)(熱更新)解決Bug(JSPatch)
JSPatch 是一個 iOS 動態(tài)更新框架,只需在項目中引入極小的引擎,就可以使用 JavaScript 調(diào)用任何 Objective-C 原生接口,獲得腳本語言的優(yōu)勢:為項目動態(tài)添加模塊,或替換項目原生代碼動態(tài)修復(fù) bug。
關(guān)于消息轉(zhuǎn)發(fā),前面已經(jīng)講到過了,消息轉(zhuǎn)發(fā)分為三級,我們可以在每級實(shí)現(xiàn)替換功能,實(shí)現(xiàn)消息轉(zhuǎn)發(fā),從而不會造成崩潰。JSPatch不僅能夠?qū)崿F(xiàn)消息轉(zhuǎn)發(fā),還可以實(shí)現(xiàn)方法添加、替換能一系列功能。
實(shí)現(xiàn)NSCoding的自動歸檔和自動解檔
- (id)initWithCoder:(NSCoder *)aDecoder {
? ? if (self = [super init]) {
? ? ? ? unsigned int outCount;
? ? ? ? Ivar * ivars = class_copyIvarList([self class], &outCount);
? ? ? ? for (int i = 0; i < outCount; i ++) {
? ? ? ? ? ? Ivar ivar = ivars[i];
? ? ? ? ? ? NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
? ? ? ? ? ? [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
? ? ? ? }
? ? }
? ? return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
? ? unsigned int outCount;
? ? Ivar * ivars = class_copyIvarList([self class], &outCount);
? ? for (int i = 0; i < outCount; i ++) {
? ? ? ? Ivar ivar = ivars[i];
? ? ? ? NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
? ? ? ? [aCoder encodeObject:[self valueForKey:key] forKey:key];
? ? }
}
實(shí)現(xiàn)字典和模型的自動轉(zhuǎn)換(MJExtension)
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
? ? ? ? for (int i = 0; i < outCount; i ++) {
? ? ? ? ? ? objc_property_t property = properties[i];
? ? ? ? ? ? //通過property_getName函數(shù)獲得屬性的名字
? ? ? ? ? ? NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
? ? ? ? ? ? [keys addObject:propertyName];
? ? ? ? ? ? //通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
? ? ? ? ? ? NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
? ? ? ? ? ? [attributes addObject:propertyAttribute];
? ? ? ? }
面試題:
1. class_copyIvarList & class_copyPropertyList區(qū)別?
交互兩個方法的現(xiàn)實(shí)有什么風(fēng)險?
第一種實(shí)現(xiàn):
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){
? Method originalMethod = class_getInstanceMethod(cls, origSelector);
? Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
? IMP previousIMP = class_replaceMethod(cls, origSelector, method_getImplementation(swizzledMethod),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? method_getTypeEncoding(swizzledMethod));
? class_replaceMethod(cls, newSelector, previousIMP,method_getTypeEncoding(originalMethod));
}
第一種方法,用class_replaceMethod()實(shí)現(xiàn)的。由于A中不存在hookPrint方法,class_replaceMethod會調(diào)用class_addMethod方法,而class_addMethod會把Base的hookPrint實(shí)現(xiàn)添加到當(dāng)前類,print的實(shí)現(xiàn)最終會和hookPrint的實(shí)現(xiàn)交換。B中倆方法都不存在,也會添加倆方法,最終交換倆方法的實(shí)現(xiàn)。最終打印的時候,會調(diào)用Base的hookPrint方法。
如果調(diào)用[a hookPrint:@"hello_k"];那么最終實(shí)現(xiàn)的應(yīng)該是A類中print的實(shí)現(xiàn)。結(jié)果是:A obj print say:hello_k。
第二種實(shí)現(xiàn):
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector){
? Method originalMethod = class_getInstanceMethod(cls, origSelector);
? Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
? method_exchangeImplementations(originalMethod, swizzledMethod);
}
第二種方法,用method_exchangeImplementations實(shí)現(xiàn)。由于A沒有實(shí)現(xiàn)hookPrint方法,在調(diào)用
[A twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的時候,將A的print實(shí)現(xiàn)與Base的hookPrint實(shí)現(xiàn)交換了。
接著調(diào)用 [B twoSwizzleInstanceMethod:@selector(print:) withMethod:@selector(hookPrint:)]的時候,由于B類啥都沒實(shí)現(xiàn),它只能將Base的print實(shí)現(xiàn)與base的hookPrint交換了。最終,調(diào)用[b print:@"hello2"]的時候,調(diào)用的是代碼中A類的print方法的實(shí)現(xiàn)。
倆中方法都用到了class_getInstanceMethod(cls, selector)方法,這個方法有個特點(diǎn):如果這個類中沒有實(shí)現(xiàn)selector這個方法,它返回的是它某父類的 Method 對象(沿著繼承鏈找到為止)。
當(dāng)我們寫的類沒有繼承的關(guān)系的時候,倆種方法都沒什么問題。當(dāng)有繼承關(guān)系又不確定方法實(shí)現(xiàn)沒實(shí)現(xiàn),最好用class_replaceMethod方法。當(dāng)啥都不確定的時候,老老實(shí)實(shí)地用class_replaceMethod 吧,安全無痛苦
2. Aspects實(shí)現(xiàn)的核心原理是什么?
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
? ? ? ? ? ? ? ? ? ? ? ? ? withOptions:(AspectOptions)options
? ? ? ? ? ? ? ? ? ? ? ? ? ? usingBlock:(id)block
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:(NSError **)error;、
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
? ? ? ? ? ? ? ? ? ? ? ? ? withOptions:(AspectOptions)options
? ? ? ? ? ? ? ? ? ? ? ? ? ? usingBlock:(id)block
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:(NSError **)error;
Aspects就是利用了消息轉(zhuǎn)發(fā)機(jī)制,通過hook第三層的轉(zhuǎn)發(fā)方法forwardInvocation:,然后根據(jù)切面的時機(jī)來動態(tài)調(diào)用block。接下來詳細(xì)分析巧妙的設(shè)計
Aspects或者Runtime方法交換使用前提:
把"retain", "release", "autorelease", "forwardInvocation:這幾個加入集合中,判斷集合中是否包含傳入的selector,如果包含返回NO,這也說明Aspects不能對這幾個函數(shù)進(jìn)行hook操作;
判斷selector是不是dealloc方法,如果是切面時機(jī)必須是AspectPositionBefore,要不然就會報錯并返回NO,dealloc之后對象就銷毀,所以切片時機(jī)只能是在原方法調(diào)用之前調(diào)用
判斷類和實(shí)例對象是否可以響應(yīng)傳入的selector,不能就返回NO
判斷是不是元類,如果是元類,判斷方法有沒有被hook過,如果沒有就保存數(shù)據(jù),一個方法在一個類的層級里面只能hook一次
2.上報機(jī)制
在收集到埋點(diǎn)信息之后,會有一個上報到服務(wù)器進(jìn)行統(tǒng)計分析的機(jī)制,比如實(shí)時發(fā)送、啟動時發(fā)送、最小間隔時間發(fā)送等。服務(wù)器在接收到這些數(shù)據(jù)信息之后,按自己整理的計算統(tǒng)計規(guī)則得出最后的數(shù)據(jù)報表,提供給相關(guān)人員對項目進(jìn)行分析使用。
面試題:
1. 動態(tài)綁定?
2.什么時候會報unrecognized selector錯誤?iOS有哪些機(jī)制來避免走到這一步?
3._objc_msgForward函數(shù)是做什么的,直接調(diào)用它將會發(fā)生什么?
_objc_msgForward是IMP類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個對象發(fā)送一條消息,但它并沒有實(shí)現(xiàn)的時候,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)。
在“消息傳遞”過程中,objc_msgSend的動作比較清晰:首先在類中的緩存查找IMP(沒緩存則初始化緩存),如果沒找到,則向父類的類查找。如果一直查找到根類仍舊沒有實(shí)現(xiàn),則用_objc_msgForward函數(shù)指針代替IMP。最后,執(zhí)行這個IMP。
直接調(diào)用_objc_msgForward是非常危險的事,如果用不好會直接導(dǎo)致程序崩潰,但是如果用得好,能做很多非??岬氖?。
一旦調(diào)用_objc_msgForward,將跳過查找IMP的過程,直接觸發(fā)“消息轉(zhuǎn)發(fā)”,
如果調(diào)用了_objc_msgForward,即使這個對象確實(shí)已經(jīng)實(shí)現(xiàn)了這個方法,你也會告訴objc_msgSend:
“我沒有在這個對象里找到這個方法的實(shí)現(xiàn)”
有哪些場景需要直接調(diào)用_objc_msgForward?最常見的場景是:你想獲取某方法所對應(yīng)的NSInvocation對象。表示說明:
JSPatch就是直接調(diào)用_objc_msgForward來實(shí)現(xiàn)其核心功能的:
JSPatch以小巧的體積做到了讓JS調(diào)用/替換任意OC方法,讓iOS APP具有熱更新的能力。
4. objc中向一個nil對象發(fā)送消息將會發(fā)生什么?
先說結(jié)論:OC中向nil發(fā)消息,程序是不會崩潰的
因?yàn)镺C的函數(shù)都是通過objc_msgSend進(jìn)行消息發(fā)送來實(shí)現(xiàn)的,相對于C和C++來說,對于空指針的操作會引起crash問題,而objc_msgSend會通過判斷self來決定是否發(fā)送消息,如果self為nil,那么selector也會為空,直接返回,不會出現(xiàn)問題。視方法返回值,向nil發(fā)消息可能會返回nil(返回值為對象),0(返回值為一些基礎(chǔ)數(shù)據(jù))或0X0(返回值為id)等。但對于[NSNull null]對象發(fā)送消息時,是會crash的,因?yàn)镹SNull類只有一個null方法
5.?objc中向一個對象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系?
在OC中,給對象發(fā)送消息的語法是:id returnValue = [someObject messageName:parameter];
編譯器看到此消息后,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù)叫做objc_msgSend,它的原型如下
void objc_msgSend(id self, SEL cmd, ...)
因此,上述以O(shè)C形式展現(xiàn)出來的函數(shù)就會轉(zhuǎn)化成如下函數(shù):
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
這個函數(shù)會在接收者所屬的類中搜尋其“方法列表”,如果能找到與選擇子名稱相符的方法,就去實(shí)現(xiàn)代碼,如果找不到就沿著繼承體系繼續(xù)向上查找。如果找到了就執(zhí)行,如果最終還是找不到,就執(zhí)行消息轉(zhuǎn)發(fā)操作。
6.?什么時候會報unrecognized selector的異常?
當(dāng)調(diào)用該對象上某個方法,而該對象上沒有實(shí)現(xiàn)該方法的時候,可以通過“消息轉(zhuǎn)發(fā)”進(jìn)行解決。
objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中查找方法運(yùn)行,如果,在最上方的父類中依然找到相應(yīng)的方法時,程序在運(yùn)行時會掛掉并引發(fā)異常,但無法識別的選擇器已發(fā)送到XXX。但是在這之前,objc的運(yùn)行時會通過三次拯救程序崩潰的機(jī)會:
1. 方法解析
objc時運(yùn)行會調(diào)用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機(jī)會提供一個函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù),那運(yùn)行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程,否則,運(yùn)行時就會移到下一步,消息轉(zhuǎn)發(fā)
2. 快速轉(zhuǎn)發(fā)
如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:,運(yùn)行時這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機(jī)會。只要這個方法返回的不是nil和self,整個消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個對象。
3.正常轉(zhuǎn)發(fā)
這一步是Runtime最后一次給你挽救的機(jī)會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數(shù)簽名,運(yùn)行時就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象。
7.?一個objc對象的isa的指針指向什么?有什么作用?
對象的isa指針指向所屬的類
類的isa指針指向了所屬的元類
元類的isa指向了根元類,根元類指向了自己。
isa幫助一個對象找到它的方法。isa:是一個Class 類型的指針. 每個實(shí)例對象有個isa的指針,他指向?qū)ο蟮念?,而Class里也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當(dāng)類方法被調(diào)用時,先會從本身查找類方法的實(shí)現(xiàn),如果沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內(nèi)循環(huán)。
8.?能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時創(chuàng)建的類中添加實(shí)例變量?為什么?
1.不能向編譯后得到的類增加實(shí)例變量
2.能向運(yùn)行時創(chuàng)建的類中添加實(shí)例變量
解釋:
1.編譯后的類已經(jīng)注冊在runtime中,類結(jié)構(gòu)體中的objc_ivar_list實(shí)例變量的鏈表和instance_size實(shí)例變量的內(nèi)存大小已經(jīng)確定,runtime會調(diào)用class_setvarlayout或class_setWeaklvarLayout來處理strong weak引用.所以不能向存在的類中添加實(shí)例變量
2.運(yùn)行時創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù).但是的在調(diào)用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.
9.runtime如何實(shí)現(xiàn)weak變量的自動置nil?
runtime 對注冊的類, 會進(jìn)行布局,對于 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內(nèi)存地址作為 key,當(dāng)此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a,那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設(shè)置為 nil。
10.給類添加一個屬性后,在類結(jié)構(gòu)體里哪些元素會發(fā)生變化?
11.運(yùn)行時能增加成員變量么?能增加屬性么?如果能,如何增加?如果不能,為什么?
12.有沒有用過運(yùn)行時,用它都能做什么?(交換方法,創(chuàng)建類,給新創(chuàng)建的類增加方法,改變isa指針)
13.runtime如何通過selector找到對應(yīng)的IMP地址?(分別考慮類方法和實(shí)例方法)?
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,實(shí)際上選擇器實(shí)質(zhì)上就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實(shí)現(xiàn)。
參考NSObject上面的方法:
-(IMP)methodForSelector :( SEL)aSelector;+(IMP)instanceMethodForSelector :( SEL)aSelector;
14.使用runtime Associate方法關(guān)聯(lián)的對象,需要在主對象dealloc的時候釋放么?
無論在MRC下還是ARC下均不需要
15. 一個對象的isa指針指向什么?有什么作用?
16.objc_msgForward 函數(shù)是做什么的,直接調(diào)用會怎么樣?
17.class_copyIvarList & class_copyPropertyList區(qū)別?
18.交互兩個方法的現(xiàn)實(shí)有什么風(fēng)險?
19.Aspects實(shí)現(xiàn)的核心原理是什么?哪些方法不能被hook
(1)不允許hookretain、release、autorelease、forwardInvocation:
(2)允許hookdealloc,但是只能在dealloc執(zhí)行前,這都是為了程序的安全性設(shè)置的
(3)檢查這個方法是否存在,不存在則不能hook
(4)Aspects對于hook的生效作用域做了區(qū)分:所有實(shí)例對象&某個具體實(shí)例對象。對于所有實(shí)例對象在整個繼承鏈中,同一個方法只能被hook一次,這么做的目的是為了規(guī)避循環(huán)調(diào)用的問題
15.在運(yùn)行時創(chuàng)建類的方法objc_allocateClassPair的方法名尾部為什么是pair(成對的意思)?
20. 消息轉(zhuǎn)發(fā)三種方法的防護(hù)優(yōu)劣?
http://www.itdecent.cn/p/9fac4ed527e3
可以利用消息轉(zhuǎn)發(fā)機(jī)制來做文章。那么問題來了,在這三個步驟里面,選擇哪一步去改造比較合適呢。
這里我們選擇了第二步forwardingTargetForSelector來做文章。原因如下:
resolveInstanceMethod 需要在類的本身上動態(tài)添加它本身不存在的方法,這些方法對于該類本身來說冗余的
forwardInvocation可以通過NSInvocation的形式將消息轉(zhuǎn)發(fā)給多個對象,但是其開銷較大,需要創(chuàng)建新的NSInvocation對象,并且forwardInvocation的函數(shù)經(jīng)常被使用者調(diào)用,來做多層消息轉(zhuǎn)發(fā)選擇機(jī)制,不適合多次重寫
forwardingTargetForSelector可以將消息轉(zhuǎn)發(fā)給一個對象,開銷較小,并且被重寫的概率較低,適合重寫
選擇了forwardingTargetForSelector之后,可以將NSObject的該方法重寫,做以下幾步的處理:
1.動態(tài)創(chuàng)建一個樁類
2.動態(tài)為樁類添加對應(yīng)的Selector,用一個通用的返回0的函數(shù)來實(shí)現(xiàn)該SEL的IMP
3.將消息直接轉(zhuǎn)發(fā)到這個樁類對象上。

注意如果對象的類本事如果重寫了forwardInvocation方法的話,就不應(yīng)該對forwardingTargetForSelector進(jìn)行重寫了,否則會影響到該類型的對象原本的消息轉(zhuǎn)發(fā)流程。
通過重寫NSObject的forwardingTargetForSelector方法,我們就可以將無法識別的方法進(jìn)行攔截并且將消息轉(zhuǎn)發(fā)到安全的樁類對象中,從而可以使app繼續(xù)正常運(yùn)行。
21. 無埋點(diǎn)實(shí)現(xiàn)的原理?