国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

Objective-C——Runtime理解

2019-11-14 18:32:29
字體:
供稿:網(wǎng)友

動態(tài)語言

OC是一門不折不扣的動態(tài)語言,所以它的很多機(jī)制都是動態(tài)運(yùn)行時決定的。這點(diǎn)和C語言不一樣,C語言是靜態(tài)綁定,也就是編譯后所有的一切都已經(jīng)決定了。這一點(diǎn)和C語言的函數(shù)指針有些類似,很多時候函數(shù)指針在編譯的時候并不知道會指向哪個函數(shù),所以此時就是動態(tài)綁定。

舉幾個OC動態(tài)類型的例子,最為直接的就是id類型了、還有關(guān)聯(lián)對象、動態(tài)綁定、消息轉(zhuǎn)發(fā)、方法調(diào)配、這些技術(shù)都是動態(tài)類型很好的證明

 

OC對象結(jié)構(gòu)

在介紹動動態(tài)性之前,我們先來看看OC對象的一些結(jié)構(gòu)。

#import<objc/runtime>這是OC運(yùn)行時函數(shù)庫,里面定義了很多結(jié)構(gòu)體。

首先看對象的結(jié)構(gòu):

typdef struct objc_object {      Class isa;     }  *id;

對象結(jié)構(gòu)中非常簡單,只有一個isa指針,isa指針后面我們會介紹。

接下來我們看類的結(jié)構(gòu)體

struct objc_class {    Class isa#if !__OBJC2__    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               #endif}typedef struct objc_class *Class;

可以看到很多信息都在Class中定義著,里面信息如下

字段含義
isaisa指針
super_class父類指針
name類名
version類的版本信息,默認(rèn)為0
info供運(yùn)行期使用的一些位標(biāo)識
instance_size實(shí)例的大小
ivars實(shí)例變量列表
methodLists方法列表
cache指向最近調(diào)用的方法,用于優(yōu)化調(diào)用方法的速度
protocols協(xié)議列表

 

 

 

 

 

 

 

 

 

 

接下來我們逐個介紹一下:

 

isa指針和super_class

在OC中,嚴(yán)格意義上講是沒有類這種概念的,每一個類都是一個對象,只不過類對象是一個單例。

isa指針存在于每一個對象中,類普通實(shí)例的isa指針指向類,類的isa指針指向它的元類(類方法全部都在元類中存放)。元類的isa指針指向根元類,也就是NSObject的isa所指向的元類。

super_class只有類和元類才有,它們分別指向自己的父類和父元類,而為了讓NSObject成為所有類的根類,讓NSObject的元類的父類指針也指向了NSObject。

這樣說可能也不是很好理解,看下面這張圖應(yīng)該就很快理解了。

等下我們說到消息傳遞的時候還會在說到isa和super_class。

ivars屬性列表

struct objc_ivar_list {    int ivar_count                       #ifdef __LP64__    int space                                       #endif    /* variable length structure */    struct objc_ivar ivar_list[1]               }    

space作用還不太清楚...求指教啊。

下面是實(shí)例變量結(jié)構(gòu)

struct objc_ivar {    char *ivar_name                                          OBJC2_UNAVAILABLE;    char *ivar_type                                          OBJC2_UNAVAILABLE;    int ivar_offset                                          OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif}  

可以看到有一個ivar_offset,這個是實(shí)例變量在編譯時的偏移量,是由編譯時決定的。

 methodLists方法列表

struct objc_method_list {    struct objc_method_list *obsolete                           int method_count                                       #ifdef __LP64__    int space                                            #endif    /* variable length structure */    struct objc_method method_list[1]                  }  

該結(jié)構(gòu)有方法鏈表和方法總數(shù)。

struct objc_method {    SEL method_name                                          OBJC2_UNAVAILABLE;    char *method_types                                       OBJC2_UNAVAILABLE;    IMP method_imp                                           OBJC2_UNAVAILABLE;}       

里面有函數(shù)名,返回值類型和函數(shù)實(shí)現(xiàn),接下來看看SEL和IMP定義

typedef struct objc_selector *SEL;typedef id (*IMP)(id, SEL, ...); 

可以看到SEL是objc_selector,(*IMP)(id,SEL,...)是id,我沒有找到objc_selector的結(jié)構(gòu)所以這里也沒法說什么...

cache緩存列表

typedef struct objc_cache *Cache                            #define CACHE_BUCKET_NAME(B)  ((B)->method_name)#define CACHE_BUCKET_IMP(B)   ((B)->method_imp)#define CACHE_BUCKET_VALID(B) (B)#ifndef __LP64__#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))#else#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))#endifstruct objc_cache {    unsigned int mask /* total = mask + 1 */                 unsigned int occupied                                     Method buckets[1]                                   };

 

 緩存列表里面包含了已緩存的方法,用于快速的調(diào)用,不需要在去方法列表里面查詢了。

protocol協(xié)議列表

struct objc_protocol_list {    struct objc_protocol_list *next;    long count;    Protocol *list[1];};

 

協(xié)議列表包含了協(xié)議數(shù)量和協(xié)議的指針。

 

id

id類型實(shí)際上就是一個objc_object的typedef,是一個對象實(shí)例,而且還是一個指針,所以用id來定義對象的時候就不需要加*號了。

id類型往往需要我們使用”自省“機(jī)制來保證使用安全,所謂自省其實(shí)就是看看這個對象是不是某個類的實(shí)例,或者是不是其子類。

自省用一下兩個方法:

isKindOfClass:(Class)class          判斷是不是其類族對象

isMemberOfClass:(Class)class     判斷是不是類本身對象

 

關(guān)聯(lián)對象

關(guān)聯(lián)對象在我之前的博客中已經(jīng)有介紹了,這里就不再說了。

 

OC消息機(jī)制和消息轉(zhuǎn)發(fā)

別的語言調(diào)用函數(shù),OC則叫做發(fā)送消息,這是因?yàn)樗蠴C的方法調(diào)用實(shí)際上底層都是通過

objc_msgSend(id self , SEL cmd,...)來發(fā)送的。

該函數(shù)的作用就是傳遞給一個對象某個方法,后面的不定參數(shù)列表是方法所需要的參數(shù)。

這里說一下OC的消息傳遞機(jī)制,首先對一個對象發(fā)送消息,它會先檢查自己的類中有沒有該方法,如果沒有就找他的父類中有沒有,如果還沒有則會進(jìn)行消息轉(zhuǎn)發(fā)。

在看例子之前先說一下,Xcode6貌似默認(rèn)行為不讓我們使用objc_msgSend了,所以需要先設(shè)置一下

 

把這一項(xiàng)設(shè)置為No就可以了。

這里看個例子,

        EqualObject *object1 = [EqualObject new];        EqualObject *object2 = [EqualObject new];        object1.name = @"xiaoming";        object2.name = @"xiaoming";                BOOL isEqual = objc_msgSend(object1,@selector(isEqualToEqualObject:),object2);                if(isEqual)        {            NSLog(@"equal");        }

 

EqualObject是我們自己實(shí)現(xiàn)的類,它有一個判斷是否相等的方法isEqualToEqualObject:,如果name相等就人為兩個對象相等。

這里我們直接傳遞消息,不通過OC語法,運(yùn)行程序可以看到equal被打印了出來。

然后我們再看看消息轉(zhuǎn)發(fā)機(jī)制。

當(dāng)該對象包括其父類都沒有這個方法的時候會啟動,消息轉(zhuǎn)發(fā)機(jī)制分為兩大階段。

第一階段先看對象所屬類是否有能力動態(tài)添加方法,已處理這個位置的選擇子,這叫做動態(tài)解析(dynamic method resolution)。

第二階段設(shè)計(jì)“完整的消息轉(zhuǎn)發(fā)機(jī)制”。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接受者自己就沒法再以動態(tài)新增方法的手段來處理與消息相關(guān)的方法調(diào)用。這又分為兩個小步。

首先,請接受者看看有沒有其他對象能處理這條消息。若有,則在運(yùn)行時轉(zhuǎn)給那個對象,于是消息轉(zhuǎn)發(fā)過程結(jié)束。若沒有“備用的接受者”,則啟動完成的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行起系統(tǒng)會把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對象中,再給接受者最后一次機(jī)會,令其設(shè)法解決當(dāng)前還未處理的這條消息。

 

動態(tài)方法解析

遇到無法解析的信息后,首先將調(diào)用其所屬類的下列類方法:
+(BOOL)resolveInstanceMethod:(SEL)selector

該方法參數(shù)就是未知的選擇子,返回BOOL類型那個,表示這個類是否能新增一個實(shí)力方法已處理這個選擇子。假如是類方法,那么會調(diào)用

+(BOOL)resolveClassMethod:(SEL)selector

使用這種方法的前提是相關(guān)的實(shí)現(xiàn)已經(jīng)寫好了,只等運(yùn)行時動態(tài)的插入就行,比如CoreData中NSManagedObjects對象的屬性時就可以這么做,因?yàn)閷?shí)現(xiàn)這些屬性所需的存取方法在編譯期就能確定。

下面我們看個例子

void showMessage(id self, SEL _cmd, id value){    if([value isKindOfClass:[NSString class]])    {        NSLog(@"%@",(NSString *)value);    }}+(BOOL)resolveInstanceMethod:(SEL)sel{    NSString *selString = NSStringFromSelector(sel);    if([selString isEqualToString:@"showMessage:"])    {        class_addMethod(self, sel, (IMP)showMessage, "v@:@");        return YES;    }    else    {        return [super resolveInstanceMethod:sel];    }}

 

向剛才EqualObject添加以上實(shí)現(xiàn)帶代碼,然后在客戶端調(diào)用:

        objc_msgSend(object1,@selector(showMessage:),@"Hello");

 

能夠看到程序并沒有報錯,而且還打印出了Hello!

 

備援接受者

當(dāng)沒有使用動態(tài)方法解析后,還是出發(fā)備用接受者,該步驟會觸發(fā)該方法

-(id)forwardingTargetForSelector:(SEL)selector

方法參數(shù)是未知的選擇子,如果找到備用對象返回對象,否則返回nil。

可以利用該方法來模擬多重繼承機(jī)制(實(shí)際為組合),因?yàn)橥獠靠床坏剑愿杏X上就像是本身處理該消息。

下面看一個例子:

把剛才添加的代碼注釋掉,添加一個新類OtherObject,添加showMessage方法,然后在EqualObject中添加以下代碼

-(id)forwardingTargetForSelector:(SEL)aSelector{    NSString *selectString = NSStringFromSelector(aSelector);    if([selectString isEqualToString:@"showMessage:"])    {        return other;    }    return nil;}

 

會發(fā)現(xiàn)Hello依舊出現(xiàn)了!,而且如果你在OtherObject中的showMessage方法中打上斷點(diǎn),會發(fā)現(xiàn)方法執(zhí)行到了OtherObject中...

 

完整的消息轉(zhuǎn)發(fā)

如果消息沒有轉(zhuǎn)發(fā),那么回來到這一步,首先創(chuàng)建NSInvocation對象,然后把未處理的信息細(xì)節(jié)全部都封裝于其中。

此對象包含選擇子、目標(biāo)(target)和參數(shù)。在觸發(fā)NSInvocation對象時,“消息派發(fā)系統(tǒng)”會把消息派給目標(biāo)。

此步驟會調(diào)用以下方法:

-(void)forwardInvocation:(NSInvocation *)invocation

這個方法實(shí)現(xiàn)簡單,只要改變調(diào)用目標(biāo),使消息在新目標(biāo)上得以調(diào)用即可。但是這樣和備用接受者實(shí)現(xiàn)就一樣了,所以一般都不會這樣寫。

比較有用的實(shí)現(xiàn)是再出發(fā)消息前,先以某種方式改變消息內(nèi)容,比如追加另一個參數(shù),或是改裝選擇子。

如果發(fā)現(xiàn)不該由該類執(zhí)行,那么需要調(diào)用超類的該方法,繼承體系中每個類都有機(jī)會處理此調(diào)用請求,如果到NSObject還不能處理會調(diào)用doesNotRecognizerSelector拋出異常。

實(shí)現(xiàn)上面那個方法的時候需要同時實(shí)現(xiàn)。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

 

需要先對NSInvocation簽名然后才能使用NSInvocation

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {    NSMethodSignature *sig;    sig = [other methodSignatureForSelector:aSelector];    return sig;}-(void)forwardInvocation:(NSInvocation *)anInvocation{    [anInvocation invokeWithTarget:other];}

 

方法調(diào)配

在運(yùn)行時,我們還可以使用方法調(diào)配技術(shù)來改變SEL指向的IMP,比如說目前方法名和對應(yīng)IMP如下

方法IMP
methodAIMPA
methodBIMPB

 

 

 

但是當(dāng)我們使用方法調(diào)配后,就可以出現(xiàn)一下情況

方法IMP
methodAIMPB
methodBIMPA

 

 

 

其實(shí)我們修改的本質(zhì)是方發(fā)表的映射,修改了選擇子的指向

該技術(shù)主要用到的方法如下

void method_exchangeImplementations(Method m1, Method m2)

該函數(shù)的兩個參數(shù)表示待交換的兩個方法實(shí)現(xiàn),而方法實(shí)現(xiàn)則可通過下列函數(shù)獲得:

Method class_getInstanceMethod(Class aClass , SEL aSelector)

現(xiàn)在我們向EqualObject中添加methodA和methodB

-(void)methodA{    NSLog(@"methodA");}-(void)methodB{    NSLog(@"methodB");}

 

然后客戶端這樣寫

        EqualObject *object1 = [EqualObject new];        Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA));        Method methodB = class_getInstanceMethod([EqualObject class], @selector(methodB));        method_exchangeImplementations(methodA, methodB);                [object1 methodA];

 

運(yùn)行后就會打印出methodB。

利用該技術(shù)進(jìn)行黑盒調(diào)試

要進(jìn)行黑盒調(diào)試,主要用到的就是該技術(shù)和category

下面來看看具體該如何編寫:

首先添加一個EqualObject的category,然后添加一個新方法plusMethodA

-(void)plusMethodA{    [self plusMethodA];    NSLog(@"this is plus version");}

 

這里看著像是會無限遞歸,但是實(shí)際上plusMethodA選擇子已經(jīng)指向了methodA的IMP,所以并不會出現(xiàn)無限調(diào)用的情況。

客戶端代碼

        EqualObject *object1 = [EqualObject new];        Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA));        Method methodB = class_getInstanceMethod([EqualObject class], @selector(plusMethodA));        method_exchangeImplementations(methodA, methodB);                [object1 methodA]

 

輸出結(jié)果為

2015-08-13 09:06:01.672 Equal[8282:5413229] methodA2015-08-13 09:06:01.673 Equal[8282:5413229] this is plus version

 

可以看到我們沒有繼承一個類就做到了擴(kuò)展某個方法,用于調(diào)試打印一些輸出信息會很有用!

 

常用Runtime總結(jié)

關(guān)聯(lián)對象:

設(shè)置一個關(guān)聯(lián)對象

void objc_setAssociatedObject(id object, void *key ,id value, objc_AssociationPolicy policy)

獲取關(guān)聯(lián)對象

void objc_getAssociatedObject(id object, void *key)

刪除該對象所有的關(guān)聯(lián)對象

void objc_removeAssociatedObjects(id object)

 

消息傳遞

向某個對象/父類 發(fā)送消息

objc_msgSend(Super)

 

方法調(diào)配

交換兩個方法的實(shí)現(xiàn)

void method_exchangeImplementation(Method m1, Method m2)

得到該法的指針

Method class_getInstanceMethod(Class aClass, SEL aSelector)

 

動態(tài)創(chuàng)建對象

創(chuàng)建新的類

Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

給類增加新的方法

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

注冊新的類

void objc_registerClassPair(Class cls)

獲得對象的isa指針?biāo)赶虻膶ο?/p>

Class object_getClass(id obj)


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 邳州市| 晋州市| 监利县| 墨脱县| 莫力| 葫芦岛市| 绥化市| 政和县| 四会市| 隆尧县| 池州市| 奉化市| 南丰县| 漳浦县| 平阳县| 麻阳| 龙陵县| 宜宾县| 霍州市| 邹城市| 永城市| 酒泉市| 文成县| 弋阳县| 武义县| 类乌齐县| 垫江县| 崇州市| 辰溪县| 伊吾县| 新竹市| 抚宁县| 浑源县| 阿图什市| 隆德县| 揭阳市| 定日县| 法库县| 刚察县| 土默特左旗| 鸡西市|