RunTime

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)的原理?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容