Runtime

我們都知道Objective-C 是一門動態(tài)語言荡陷,這就意味著它是一類在運行時可以改變其結(jié)構(gòu)的語言湾戳,你可以按需要把消息重定向給合適的對象,甚至可以交換方法的實現(xiàn)韧衣。我們知道方法調(diào)用的本質(zhì)就是對象發(fā)送消息盅藻,比如:[object message] 實際上被編譯器轉(zhuǎn)化成了:objc_msgSend(object, selector)

一. 動態(tài)特性

首先我們來了解一下動態(tài)特性可以大致分為動態(tài)類型(Dynamic typing),動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)畅铭。

  1. 動態(tài)類型:

即是運行時才決定對象的類型氏淑,比如我們常用的id類型。這里需要說到幾個方法:

-isMemberOfClass:NSObject 的方法硕噩,用以確定某個 NSObject 對象是否是某個類的成員假残。

-isKindOfClass: 可以用以確定某個對象是否是某個類或其子類的成員。

respondsToSelector: 檢查對象能否響應(yīng)指定的消息炉擅。

conformsToProtocol: 檢查對象是否實現(xiàn)了指定協(xié)議類的方法辉懒。

methodForSelector: 返回指定方法的函數(shù)指針。

動態(tài)類型.png
  1. 動態(tài)綁定

基于動態(tài)類型谍失,在某個實例對象被確定后耗帕,其類型便被確定了。該對象對應(yīng)的屬性和響應(yīng)的消息也被完全確定袱贮,這就是動態(tài)綁定。傳統(tǒng)的函數(shù)一般在編譯時就已經(jīng)把參數(shù)信息和函數(shù)實現(xiàn)打包到編譯后的源碼中了,而在OC中使用的是消息機制攒巍。調(diào)用一個實例方法嗽仪,其實是向該實例的指針發(fā)送消息,實例在收到消息之后柒莉,會從自身的實現(xiàn)中去尋找響應(yīng)這條消息的方法闻坚。而動態(tài)綁定所做的,就是在實例所屬類確定后兢孝,將某些屬性和相應(yīng)的方法綁定到實例上窿凤。

  1. 動態(tài)加載

根據(jù)需求加載所需要的資源,比如不同設(shè)備加載不同尺寸圖片跨蟹。

二. 具體結(jié)構(gòu)

//objc/runtime.h
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;   //isa指針

#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;//指向objc_method_list指針的指針
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;//方法緩存
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;//協(xié)議鏈表
#endif

} OBJC2_UNAVAILABLE;
//objc/objc.h

// Class其實是一個指向objc_class結(jié)構(gòu)體的指針
typedef struct objc_class *Class;

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;   //isa指針
};
//指向一個類實例的指針
typedef struct objc_object *id;

這么看來雳殊,類和對象都是一樣的結(jié)構(gòu),內(nèi)部都包含一個isa對象窗轩,那么類本身也是一個對象夯秃。

為了處理類和對象的關(guān)系,runtime 引入了元類 (Meta Class) 痢艺,類對象所屬的類型就叫做元類仓洼,它用來表述類對象本身所具備的元數(shù)據(jù)。類方法就定義于此堤舒,因為這些方法可以理解成類對象的實例方法色建。每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類舌缤。當對象的實例方法調(diào)用時箕戳,通過對象的 isa 在類中獲取方法的實現(xiàn)。類對象的類方法調(diào)用時友驮,通過類的 isa 在元類中獲取方法的實現(xiàn)漂羊。

當你發(fā)出一個類似[NSObject alloc]的消息時,你事實上是把這個消息發(fā)給了一個類對象 (Class Object) 卸留,這個類對象必須是一個元類的實例走越,而這個元類同時也是一個根元類 (root meta class) 的實例。所有的元類最終都指向根元類為其超類耻瑟。所有的元類的方法列表都有能夠響應(yīng)消息的類方法旨指。所以當 [NSObject alloc] 這條消息發(fā)給類對象的時候,objc_msgSend()會去它的元類里面去查找能夠響應(yīng)消息的方法喳整,如果找到了谆构,然后對這個類對象執(zhí)行方法調(diào)用。

class-diagram.jpg

其他關(guān)鍵字:

1. SEL

SEL又叫選擇器框都,是表示一個方法的selector的指針搬素,其定義如下:

typedef struct objc_selector *SEL;

方法的selector用于表示運行時方法的名字。Objective-C在編譯時熬尺,會依據(jù)每一個方法的名字摸屠、參數(shù)序列,生成一個唯一的整型標識(Int類型的地址)粱哼,這個標識就是SEL季二。

兩個類之間,只要方法名相同揭措,那么方法的SEL就是一樣的胯舷,每一個方法都對應(yīng)著一個SEL。所以在Objective-C同一個類(及類的繼承體系)中绊含,不能存在2個同名的方法桑嘶,即使參數(shù)類型不同也不行
如在某一個類中定義以下兩個方法會報錯

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;

當然,不同的類可以擁有相同的selector艺挪,這個沒有問題不翩。不同類的實例對象執(zhí)行相同的selector時,會在各自的方法列表中去根據(jù)selector尋找自己對應(yīng)的IMP麻裳。

工程中的所有的SEL組成一個Set集合口蝠,如果我們想到這個方法集合中查找某個方法時,只需要去找到這個方法對應(yīng)的SEL就行了津坑,SEL實際上就是根據(jù)方法名hash化了的一個字符串妙蔗,而對于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度上無語倫比疆瑰!

本質(zhì)上眉反,SEL只是一個指向方法的指針(準確的說,只是一個根據(jù)方法名hash化了的KEY值穆役,能唯一代表一個方法)寸五,它的存在只是為了加快方法的查詢速度。
通過下面三種方法可以獲取SEL:
a耿币、sel_registerName函數(shù)
b梳杏、Objective-C編譯器提供的@selector()
c、NSSelectorFromString()方法

2. Method

Method用于表示類定義中的方法:

typedef struct objc_method *Method
struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法實現(xiàn)
}

我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP淹接,實際上相當于在SEL和IMP之間作了一個映射十性。有了SEL,我們便可以找到對應(yīng)的IMP塑悼,從而調(diào)用方法的實現(xiàn)代碼劲适。

3. IMP

IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的地址厢蒜。

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

第一個參數(shù):是指向self的指針(如果是實例方法霞势,則是類實例的內(nèi)存地址烹植;如果是類方法,則是指向元類的指針)
第二個參數(shù):是方法選擇器(selector)
接下來的參數(shù):方法的參數(shù)列表支示。

4. Ivar

Ivar是一種代表類中實例變量的類型刊橘。

typedef struct objc_ivar *Ivar;

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
}                                                            OBJC2_UNAVAILABLE;

可以根據(jù)實例查找其在類中的名字,也就是“反射”:

-(NSString *)nameWithInstance:(id)instance {
    unsigned int numIvars = 0;
    NSString *key=nil;
    Ivar * ivars = class_copyIvarList([self class], &numIvars);
    for(int i = 0; i < numIvars; i++) {
        Ivar thisIvar = ivars[i];
        const char *type = ivar_getTypeEncoding(thisIvar);
        NSString *stringType =  [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
        if (![stringType hasPrefix:@"@"]) {
            continue;
        }
        if ((object_getIvar(self, thisIvar) == instance)) {//此處若 crash 不要慌颂鸿!
            key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
            break;
        }
    }
    free(ivars);
    return key;
}

class_copyIvarList 函數(shù)獲取的不僅有實例變量,還有屬性攒庵。但會在原本的屬性名前加上一個下劃線嘴纺。

class_copyPropertyList 函數(shù)只能獲取類的屬性。

5. Cache

Cache為方法調(diào)用的性能進行優(yōu)化浓冒,通俗地講栽渴,每當實例對象接收到一個消息時,它不會直接在isa指向的類的方法列表中遍歷查找能夠響應(yīng)消息的方法稳懒,因為這樣效率太低了闲擦,而是優(yōu)先在Cache中查找。

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

6. _cmd

_cmd在Objective-C的方法中表示當前方法的selector场梆,正如同self表示當前方法調(diào)用的對象實例墅冷。

三. 調(diào)用流程

1. 消息傳遞

消息直到運行時才綁定到方法實現(xiàn)上。編譯器會將消息表達式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用或油,即objc_msgSend寞忿。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù),如以下所示

objc_msgSend(receiver, selector)

如果消息中還有其它參數(shù)顶岸,則該方法的形式如下所示:

objc_msgSend(receiver, selector, arg1, arg2,...)

另方法列表objc_method_list 本質(zhì)上是一個裝載 objc_method 元素的可變長度的數(shù)組腔彰。一個 objc_method 結(jié)構(gòu)體中包含函數(shù)名,也就是SEL辖佣,表示函數(shù)類型的字符串 (見 Type Encoding) 霹抛,以及函數(shù)的實現(xiàn)IMP。

比如:調(diào)用[obj foo];

  1. 首先是轉(zhuǎn)換成objc_msgSend(obj, foo),通過對象的isa指針獲取到類的結(jié)構(gòu)體卷谈,再從當前class的cache方法列表(cache methodLists)里去找杯拐。
  2. 如果找到對應(yīng)的selector,則實現(xiàn)IMP雏搂;未找到則在 class 的 method list 找 foo 藕施。
  3. 如果 class 中沒到 foo,繼續(xù)往它的 superclass 中找 凸郑,即objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類裳食,并在父類的分發(fā)表里面查找方法的selector。
  4. 依次沿著類的繼承體系到NSObject芙沥,一旦找到 foo 這個函數(shù)诲祸,就去執(zhí)行它的實現(xiàn)IMP 浊吏。并把 foo 的 method_name 作為 key ,method_imp 作為 value 存進cache
  5. 如果都未找到救氯,則會走消息轉(zhuǎn)發(fā)流程

2. 消息轉(zhuǎn)發(fā)

我們知道找田,當對象發(fā)送一個消息而沒有實現(xiàn)該方法時,編譯器會報如下錯誤:

unrecognized selector send to instance XXX

Tip: 正確的做法是當我們不確定一個對象是否能接收某個消息時着憨,應(yīng)該先判斷是否能響應(yīng)該方法:

if([self respondsToSelector:@selector(method)]){
      [self performSelector:@selector(method)];
}

當一個對象無法接收某個消息時墩衙,就會啟動 消息轉(zhuǎn)發(fā)(message forwarding)”機制。

如下圖:

iOS消息轉(zhuǎn)發(fā)流程.png

可以看到甲抖,當一個函數(shù)的實現(xiàn)找不到時漆改,OC提供了三種補救的方式:

  1. 調(diào)用 resolveInstanceMethod 或是 resolveClassMethod 嘗試去 resolve 這個消息。

  2. 如果 resolve 方法返回 NO准谚,則調(diào)用 forwardingTargetForSelector 允許你把這個消息轉(zhuǎn)發(fā)給另一個對象挫剑。

  3. 如果沒有新的目標對象返回,則調(diào)用 methodSignatureForSelectorforwardInvocation 靈活的將目標函數(shù)以其他形式執(zhí)行柱衔。

  4. 如果都不中樊破,那就GG了,Runtime會調(diào)用 doesNotRecognizeSelector: 拋出異常唆铐。

    ?

下面我們看一個具體實例是如何進行補救的:

  1. 動態(tài)方法解析哲戚,如果在自己定義的Obj類中,沒有實現(xiàn)foo方法或链,我們可以實現(xiàn) resolveInstanceMethod方法惫恼,使用 class_addMethod添加一個函數(shù)實現(xiàn),并返回YES澳盐,就能夠成功進行補救祈纯。
#pragma mark 1. **動態(tài)方法解析**
//1.首先,Runtime會調(diào)用 +resolveInstanceMethod: 或者 +resolveClassMethod:叼耙,讓你有機會提供一個函數(shù)實現(xiàn)腕窥。如果你添加了函數(shù)并返回 YES, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(foo)) {
        class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
        //        參數(shù)說明: (IMP)fooMethod 表示的是fooMethod的地址指針; "v@:" 意思是筛婉,v代表無返回值void簇爆,如果是i則代表int;@代表 id sel; : 代表 SEL _cmd; “v@:@@” 意思是爽撒,兩個參數(shù)的沒有返回值入蛆。
        
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooMethod(id obj,SEL _cmd){
    NSLog(@"成功添加了foo");
}
resolveInstanceMethod@2x.png
  1. 如果 resolveInstanceMethod 方法返回 NO ,并且沒有調(diào)用 class_addMethod 添加實現(xiàn)方法硕勿;那么運行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding):如果目標對象實現(xiàn)了 -forwardingTargetForSelector: 哨毁,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會源武。此處可以叫做Fast forwarding扼褪,因為這一步不會創(chuàng)建任何新的對象想幻,所以相比Normal forwarding 會快一些,注意:此處調(diào)用的是其他對象的實例方法话浇,所以也必須實例化該對象脏毯。
forwardingTargetForSelector@2x.png
  1. 如果 forwardingTargetForSelector 返回了nil或self, 就會繼續(xù) Normal Fowarding ,這是最后一次挽救機會了幔崖。相比上一步的fast forwarding食店,這里會創(chuàng)建一個 NSInvocation 對象。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型岖瑰。如果該方法返回 nil 叛买,Runtime 則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了蹋订;如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送 -forwardInvocation: 消息給目標對象刻伊。

    NSInvocation 實際上就是對一個消息的描述露戒,包括selector 以及參數(shù)等信息。所以你可以在 -forwardInvocation: 里修改傳進來的 NSInvocation 對象捶箱,然后發(fā)送 -invokeWithTarget: 消息給它智什,傳進去一個新的目標。

    所以我們需要重寫這兩個方法:methodSignatureForSelector:forwardInvocation:丁屎。

forwardInvocation.png

至此荠锭,Runtime的調(diào)用流程就結(jié)束了。
?

四. Method Swizzling

我們知道晨川,每個類都有一個方法列表证九,存放著selector的名字和方法實現(xiàn)的映射關(guān)系。IMP類似于函數(shù)指針共虑,指向具體的Method實現(xiàn)愧怜。每一個SEL與一個IMP一一對應(yīng),正常情況下通過SEL可以查找到對應(yīng)消息的IMP實現(xiàn)妈拌。而Method Swizzling就可以將對應(yīng)的關(guān)系解開并映射到我們自定義的函數(shù)IMP上拥坛, KVO其實就是Apple使用了一個中間類,并進行了Swizzling尘分。Method Swizzling的好處就在于:不需要改動對應(yīng)類的源代碼猜惋,就可以更改某個方法的實現(xiàn)。

1370993-99e53531835c3451.png

比如培愁,我們經(jīng)持ぃ可以在第三方框架中看到如下代碼,就是一段Method Swizzling:

//在整個文件被加載到運行時,在 main 函數(shù)調(diào)用之前被 ObjC 運行時調(diào)用的鉤子方法
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(XXX_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
          //主類本身沒有實現(xiàn)需要替換的方法竭钝,而是繼承了父類的實現(xiàn)梨撞,即 class_addMethod 方法返回 YES 雹洗。這時使用 class_getInstanceMethod 函數(shù)獲取到的 originalSelector 指向的就是父類的方法,我們再通過執(zhí)行 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 將父類的實現(xiàn)替換到我們自定義的 XXX_viewWillAppear 方法中卧波。這樣就達到了在 XXX_viewWillAppear 方法的實現(xiàn)中調(diào)用父類實現(xiàn)的目的时肿。
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
          //主類本身有實現(xiàn)需要替換的方法,也就是 class_addMethod 方法返回 NO 港粱。這種情況的處理比較簡單螃成,直接交換兩個方法的實現(xiàn)就可以了
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)XXX_viewWillAppear:(BOOL)animated
{
    // Method Swizzling之后, 調(diào)用XXX_viewWillAppear:實際執(zhí)行的代碼已經(jīng)是原來viewWillAppear中的代碼了
    [self XXX_viewWillAppear:animated];
    // 添加某些操作
}

Demo下載

參考文獻:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舶斧,一起剝皮案震驚了整個濱河市脓诡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌土砂,老刑警劉巖偿曙,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氮凝,死亡現(xiàn)場離奇詭異,居然都是意外死亡望忆,警方通過查閱死者的電腦和手機罩阵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來启摄,“玉大人稿壁,你說我怎么就攤上這事∏副福” “怎么了傅是?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕾羊。 經(jīng)常有香客問我喧笔,道長,這世上最難降的妖魔是什么肚豺? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任溃斋,我火速辦了婚禮,結(jié)果婚禮上吸申,老公的妹妹穿的比我還像新娘梗劫。我一直安慰自己,他們只是感情好截碴,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布梳侨。 她就那樣靜靜地躺著,像睡著了一般日丹。 火紅的嫁衣襯著肌膚如雪走哺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天哲虾,我揣著相機與錄音丙躏,去河邊找鬼择示。 笑死,一個胖子當著我的面吹牛晒旅,可吹牛的內(nèi)容都是我干的栅盲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼废恋,長吁一口氣:“原來是場噩夢啊……” “哼谈秫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鱼鼓,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拟烫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后迄本,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硕淑,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年嘉赎,在試婚紗的時候發(fā)現(xiàn)自己被綠了喜颁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡曹阔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隔披,到底是詐尸還是另有隱情赃份,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布奢米,位于F島的核電站抓韩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鬓长。R本人自食惡果不足惜谒拴,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涉波。 院中可真熱鬧英上,春花似錦、人聲如沸啤覆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窗声。三九已至相恃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笨觅,已是汗流浹背拦耐。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工耕腾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杀糯。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓扫俺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親火脉。 傳聞我的和親對象是個殘疾皇子牵舵,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言倦挂,那么這個「動態(tài)」表現(xiàn)在哪呢畸颅?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,558評論 33 466
  • 繼上Runtime梳理(四) 通過前面的學(xué)習,我們了解到Objective-C的動態(tài)特性:Objective-C不...
    小名一峰閱讀 754評論 0 3
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 733評論 0 2