Running Time

靜態(tài)綁定:編譯的時(shí)候就已經(jīng)確定了調(diào)用的方法。函數(shù)地址其實(shí)是硬編碼在指令中。

動(dòng)態(tài)綁定:運(yùn)行期才確定要調(diào)用的方法。

1.消息調(diào)用

調(diào)用過(guò)程過(guò)程

Alt Image Text

(1)發(fā)送sendMessage:params消息給object對(duì)象;

(2)OC消息通過(guò)編譯器轉(zhuǎn)換成了C語(yǔ)言函數(shù);

消息傳遞機(jī)制中的核心函數(shù),叫做objc_msgSend,其“原型”如下:

void object_msgSend(id self, SEL cmd, ...)

OC中給對(duì)象發(fā)消息可以這樣來(lái)寫(xiě):

id returnValue = [someObject messageName: parameter];

someObject是接收者; messageName是選擇子(方法名),選擇子+參數(shù)合起來(lái)稱(chēng)為消息

編譯器會(huì)將其轉(zhuǎn)化為C語(yǔ)言的如下函數(shù):

id returnValue = objc_msgSend(someObject, @selector(message:), params);

(3)在接收者someObject所屬的類(lèi)中,查詢(xún)選擇子selector(message:);

(4)如果沒(méi)有找到,就去super class中查找;

(5)如果最終還是沒(méi)有,就啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制,將消息轉(zhuǎn)發(fā)給別的對(duì)象。

(6)objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表中”(fast map),每個(gè)類(lèi)都有一個(gè)表,方便再次查找時(shí)快速查找。

2.消息轉(zhuǎn)發(fā)機(jī)制

原理

(1)如果類(lèi)想了解某條消息,它需要實(shí)現(xiàn)相對(duì)應(yīng)的方法才行。

(2)由于編譯期間不會(huì)進(jìn)行動(dòng)態(tài)綁定方法,所以如果沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)方法的話,是不會(huì)抱錯(cuò)的,因?yàn)榭梢赃\(yùn)行期間添加方法。

(3)如果運(yùn)行期間也沒(méi)有添加相應(yīng)的方法的話,會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”直接,來(lái)讓其他對(duì)象處理這條消息。

(4)但是如果其他對(duì)象也不能處理的話,程序就會(huì)抱錯(cuò):

unrecognized selector sent to instance XXX

因?yàn)橄⒌慕邮照撸╥nstance)并沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)的方法,不能理解這條消息(selector)。

消息轉(zhuǎn)發(fā)過(guò)程(分為兩個(gè)階段)

Alt Image Text
第一階段:動(dòng)態(tài)方法解析

(1)對(duì)象收到無(wú)法解析的message后,首先調(diào)用所屬類(lèi)的下列方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel
 
+ (BOOL)resolveClassMethod:(SEL)sel

該方法會(huì)決定,是否新增一個(gè)實(shí)例方法來(lái)處理sel這個(gè)選擇子;

前提是:該方法的視線代碼已經(jīng)寫(xiě)好了,只要在運(yùn)行時(shí)動(dòng)態(tài)插入到類(lèi)里面即可。

(2)備援接收者

調(diào)用如下方法

- (id)forwardingTargetForSelector:(SEL)aSelector

若能當(dāng)前接收者能找到備援對(duì)象,則將其返回;若找不到,就返回nil。

一個(gè)對(duì)象內(nèi)部,可能有其他對(duì)象(實(shí)例變量),可以通過(guò)上面的方法,將內(nèi)部能處理aSelector的對(duì)象返回,這樣就好想該對(duì)象親自處理了aSelector一樣。

我們無(wú)法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息,如果想要在發(fā)送給備援接收者之前先修改消息內(nèi)容,那就得通過(guò)完整的消息轉(zhuǎn)發(fā)機(jī)制來(lái)做了

第二階段:完整的消息轉(zhuǎn)發(fā)機(jī)制

(1)創(chuàng)建 NSInvocation對(duì)象:該對(duì)象封裝了selector、target、pramaters

(2)調(diào)用方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

該方法多用于改變消息內(nèi)容,比如追加pramaters,改變selector.

(3)如果本類(lèi)不能處理,則應(yīng)調(diào)用super class中的同名方法,直至NSObject

(4)如果調(diào)用了NSObject類(lèi)的方法,就會(huì)繼續(xù)調(diào)用

doesNotRecognizeSelector:

然后拋出異常,表明selector最終未能得到處理

(5)步驟越往后,處理消息的代價(jià)就越大。盡量在第一步就處理完,然后將該方法緩存起來(lái),以便之后快速處理。

下面是一個(gè)代碼示例

比如我們想給UIButton添加屬性,比如name或order,用來(lái)描述button。

(1)首先定義一個(gè)類(lèi)繼承自UIButton

@interface XZButton : UIButton

@property (nonatomic ,strong) NSString *name;

@property (nonatomic ,strong) NSNumber *order;

@end

(2)在XZButton.m文件中導(dǎo)入下面的頭文件,這樣才能使用running time的函數(shù)。

 #import <objc/runtime.h>

(3)注意使用 @dynamic,告訴編譯器不用自動(dòng)生成存取方法。

@dynamic name,order;

(4)聲明一個(gè)NSMutableDictionary屬性,用來(lái)存儲(chǔ)要添加的屬性,這里要注意了,既然是使用字典存儲(chǔ),那么如果你想添加NSInteger屬性的話是會(huì)抱錯(cuò)的,因?yàn)樽值洳辉试S添加整型做為value(?)

@property (nonatomic ,strong) NSMutableDictionary *backingStore;

(5)init 方法中初始化字典

- (id)init{

if (self = [super init]) {

    _backingStore = [NSMutableDictionary dictionary];
}

return self;
}

(6)注意這步是重點(diǎn),調(diào)用了resolveInstanceMethod方法,也就是上面說(shuō)的,詢(xún)問(wèn)receiver能否動(dòng)態(tài)添加對(duì)應(yīng)的方法來(lái)處理selector。

+ (BOOL)resolveInstanceMethod:(SEL)sel{

NSString *selectorString = NSStringFromSelector(sel);

if ([selectorString hasPrefix:@"set"]) {

    class_addMethod(self,sel,(IMP)xZAutoDictionarySetter,"v@:@");
}else{

    class_addMethod(self, sel, (IMP)xZAutoDictionaryGetter, "@ @:");
}

return YES;
}

(7)上面通過(guò)sel來(lái)判斷存取方法,然后在下面實(shí)現(xiàn)對(duì)應(yīng)的存取的方法


//set
void xZAutoDictionarySetter(id self,SEL _cmd,id value){
    
XZButton *typeSelf = (XZButton *)self;
    
NSMutableDictionary *backingStore = typeSelf.backingStore;
    
NSString *selectorString = NSStringFromSelector(_cmd);
    
NSMutableString *key = [selectorString mutableCopy];
    
//remove :
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
//remove set
[key deleteCharactersInRange:NSMakeRange(0, 3)];
    
//首字母小寫(xiě)
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
if (value) {
    
    [backingStore setValue:value forKey:key];
}else{
    
    [backingStore removeObjectForKey:key];
}
}
//get
id xZAutoDictionaryGetter(id self, SEL _cmd){

XZButton *typeSelf = (XZButton *)self;

NSMutableDictionary *backingStore = typeSelf.backingStore;
    
NSString *key = NSStringFromSelector(_cmd);
    
return [backingStore valueForKey:key];
}

(8)注意,這里添加的get和set方法名不能重復(fù),也就是不同的類(lèi)中不能使用相同的方法名。否則會(huì)報(bào)錯(cuò)( duplicate symbol)。這里我也沒(méi)太搞懂,不知道是不是因?yàn)槭荂函數(shù),所以不能重名,一般的OC方法,在不同的類(lèi)中是可以重名的,有明白的高人還請(qǐng)講解一下

(9)使用

XZButton *button = [[XZButton alloc] init];

button.name = @"xuz";
button.order = [NSNumber numberWithInteger:14];

NSLog(@"button's name is %@ order is %ld",button.name,(long)[button.order integerValue]);

3.消息處理

(1)方法調(diào)配:處理selector的方法可以在運(yùn)行期改變。既不需要源碼,也不用通過(guò)子類(lèi)繼承來(lái)復(fù)寫(xiě)方法改變這個(gè)類(lèi)本身的功能。并且新功能對(duì)本類(lèi)的所有實(shí)例生效,而不是僅限于復(fù)寫(xiě)了相關(guān)方法的那些子類(lèi)實(shí)例。

(2)類(lèi)的方法列表會(huì)把selector映射到相關(guān)的實(shí)現(xiàn)方法之上,可以通過(guò)這張表來(lái)找到應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式來(lái)表示,這種指針叫做IMP,原型如下:

id (*IMP) (id,SEL,....)

(3)映射表如下

(4)OC運(yùn)行期間可以向表中添加新的selector(并實(shí)現(xiàn)對(duì)應(yīng)的方法);也可以更改selector映射的IMP指針指向的方法;還可以交換兩個(gè)selector映射的指針。

(5)舉例子,如何交換兩個(gè)方法的實(shí)現(xiàn),這里創(chuàng)建了Nsstring的分類(lèi)ExchangeString,并實(shí)現(xiàn)了一個(gè)方法,用來(lái)和lowercaseString交換實(shí)現(xiàn)的方法

#import "NSString+ExchangeString.h"
#import <objc/runtime.h>

@implementation NSString (ExchangeString)

- (NSString *)eoc_myLowercaseString{

    //獲取selector對(duì)應(yīng)的IMP, 注意這里是class_getInstanceMethod(實(shí)例方法),我一開(kāi)始寫(xiě)的是class_getClassMethod(類(lèi)方法),沒(méi)有交換成功,所以一直循環(huán)調(diào)用
    Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));

    Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));

    //交換selector對(duì)應(yīng)的IMP
    method_exchangeImplementations(originalMethod, swappedMethod);


    NSString *lowerCase = [self eoc_myLowercaseString];

    NSLog(@"%@ - > %@",self,lowerCase);

    return lowerCase;
}

使用:

NSString *testString = @"This is the testString";
NSString *resultString = [testString eoc_myLowercaseString];

結(jié)果

對(duì)象

(1)OC中對(duì)象并非在編譯期就綁定好了,而是在運(yùn)行期查找的。

(2)正常情況下,應(yīng)該指明receiver的類(lèi)型,如果想它發(fā)送無(wú)法解讀的message的話,會(huì)報(bào)錯(cuò)。

(3)特殊情況id類(lèi)型的對(duì)象,id 指代任意的OC對(duì)象類(lèi)型。編譯器假定它能響應(yīng)任何message

(4)其實(shí)每個(gè)OC對(duì)象都是指向某塊內(nèi)存地址的指針。比如:

NSString *pointerVariable = @"some thing";

這里的pointerVariable可以理解為指向NSString的實(shí)例變量some thing的指針。

id genericTypedString = @"Some thing";

因?yàn)檫@里id類(lèi)型,本身就已經(jīng)是指針了,所以不用加*。

所有OC對(duì)象都是分配在堆上的,如果不使用*,那么會(huì)把對(duì)象分配在棧上,編譯器會(huì)報(bào)錯(cuò)
棧和堆的區(qū)別:< >。

(5)OC對(duì)象所用的結(jié)構(gòu)體定義在運(yùn)行期的程序庫(kù)頭文件中,比如id類(lèi)型:

typedef struct objc_object *id;

struct objc_object{
    Class isa;
};

首個(gè)結(jié)構(gòu)體的首個(gè)成員是Class類(lèi)的變量,isa指針指向?qū)ο笏鶎俚念?lèi)。

Class對(duì)象也定義在運(yùn)行期程序庫(kù)的頭文件中:

typedef struct objc_class *Class;

struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};

isa指針:指向類(lèi)所屬的元類(lèi)(meta),用來(lái)表述類(lèi)對(duì)象本身所具備的的元數(shù)據(jù),類(lèi)方法即存在于元類(lèi)中。

class指針:指向本類(lèi)的超類(lèi)。

name:類(lèi)名。

version:類(lèi)的版本號(hào)。

info:類(lèi)的詳情。

instance_size:該類(lèi)實(shí)例對(duì)象的大小。

ivars:指向該類(lèi)的成員變量的列表(類(lèi)的屬性列表)。

methodLists:指向該類(lèi)的實(shí)例方法列表,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)。methodLists 是指向 ·objc_method_list 指針的指針,也就是說(shuō)可以動(dòng)態(tài)修改 *methodLists 的值來(lái)添加成員方法,這也是 Category 實(shí)現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因(為什么不能添加屬性,因?yàn)閕vars指向的是屬性列表,修改ivars,并不能添加屬性?只是改變了指向地址?)。

cache:Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 cache 中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。

protocols:指向該類(lèi)的協(xié)議列表。

類(lèi)的繼承體系圖如下:


上圖實(shí)線是 super_class 指針,虛線是 isa 指針。根元類(lèi)的超類(lèi)是NSObject,而 isa 指向了自己(元類(lèi):即類(lèi)對(duì)象所屬的類(lèi)型,也就是說(shuō)所有的元類(lèi),它的類(lèi)型都是NSObject?)。NSObject 的超類(lèi)為 nil,也就是它沒(méi)有超類(lèi)。

(6)類(lèi)型查詢(xún)

isMemberOfClass:能夠判斷對(duì)象是否為某個(gè)特定類(lèi)的實(shí)例。

isKindOfClass:能判斷對(duì)象是否為某個(gè)類(lèi)或其派生類(lèi)的實(shí)例。

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict isMemberOfClass:[NSMutableDictionary class]];//yes
[dict isMemberOfClass:[NSDictionary class]];//no
[dict isKindOfClass:[NSMutableDictionary class]];//yes
[dict isKindOfClass:[NSDictionary class]];//yes

使用isa獲得實(shí)例所屬的類(lèi),然后通過(guò)super_class指針在繼承體系中查詢(xún)。因?yàn)閷?duì)象是動(dòng)態(tài)的,所以必須通過(guò)類(lèi)型查詢(xún)才能了解對(duì)象的真實(shí)類(lèi)型。

從collection中獲取的對(duì)象時(shí),通常會(huì)使用類(lèi)型查詢(xún),這些對(duì)象一般都不是強(qiáng)類(lèi)型的,通常是id,所以如果不判斷一下的話,有可能會(huì)報(bào)錯(cuò)。

如果是代理模式的話,那么用class方法查出來(lái)的類(lèi)型和isKindOfClass查詢(xún)處的結(jié)果會(huì)不一樣,前者返回的是發(fā)起代理的對(duì)象,后者返回的是接受代理的對(duì)象。要盡量使用類(lèi)型查詢(xún),這樣會(huì)得到準(zhǔn)確的對(duì)象類(lèi)型。

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

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,058評(píng)論 0 9
  • 1,NSObject中description屬性的意義,它可以重寫(xiě)嗎?答案:每當(dāng) NSLog(@"")函數(shù)中出現(xiàn) ...
    eightzg閱讀 4,340評(píng)論 2 19
  • 父類(lèi)實(shí)現(xiàn)深拷貝時(shí),子類(lèi)如何實(shí)現(xiàn)深度拷貝。父類(lèi)沒(méi)有實(shí)現(xiàn)深拷貝時(shí),子類(lèi)如何實(shí)現(xiàn)深度拷貝。? 深拷貝同淺拷貝的區(qū)別:淺拷...
    JonesCxy閱讀 1,203評(píng)論 1 7
  • 1.OC里用到集合類(lèi)是什么? 基本類(lèi)型為:NSArray,NSSet以及NSDictionary 可變類(lèi)型為:NS...
    輕皺眉頭淺憂(yōu)思閱讀 1,478評(píng)論 0 3
  • OC基礎(chǔ)總結(jié) 重新回過(guò)頭看這些基礎(chǔ)知識(shí),對(duì)許多知識(shí)點(diǎn)都有新的認(rèn)識(shí),擁有堅(jiān)實(shí)的基礎(chǔ)才能更快的成長(zhǎng)。 #improt ...
    xx_cc閱讀 6,242評(píng)論 10 56

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