runtime---簡單學(xué)習(xí)筆記

前言


runtime其實(shí)在我們?nèi)粘i_發(fā)過程中很少使用到,尤其是像我現(xiàn)在比較初級的程序猿就更用不到了。但是去面試很多面試官都會(huì)問這個(gè)問題,這就很尷尬了,這就有一種裝逼的感覺,問這些對于現(xiàn)在的我們根本用不上的東西,要是很懂,那我們完全就是大牛了嘛。但是,話雖如此,runtime是object-c底層實(shí)現(xiàn)核心,如果我們明白了,對于以后我們解決問題,理解問題有很大的幫助,所以出于什么心態(tài)我們都應(yīng)該有必要去了解,甚至在一定時(shí)候去深入理解它(這個(gè)就是以后你欲求不滿,追求技術(shù)的最高境界時(shí)~~~)。

思考


1 runtime時(shí)候干嗎的?
2 為什么要有runtime?

開始了解runtime


簡單的理解:

眾所周知,簡單而高深的C語言是面向過程的編程語言,程序是按順序依次執(zhí)行,一直到結(jié)束。而在這就為了便于說明就不得不提c++這門語言,c++和object-c兩門語言都思想差不多,都是面向?qū)ο缶幊痰?,都是基于C語言加入面向?qū)ο蟮乃枷?,但是c++和object-c在實(shí)現(xiàn)機(jī)制上有很大的差別,C++是一門靜態(tài)語言,簡而言之就是在編譯的時(shí)候編譯器就已經(jīng)把函數(shù)的地址編碼進(jìn)可執(zhí)行文件了,這個(gè)時(shí)候函數(shù)調(diào)用就知道調(diào)用那段代碼了。而object-c和C++是相反的,object-c是一門動(dòng)態(tài)語言,編譯器無法將函數(shù)的地址編碼進(jìn)可執(zhí)行文件的,所以不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來動(dòng)態(tài)得創(chuàng)建類和對象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā),而是在運(yùn)行的時(shí)候根據(jù)runtime條件判斷,再去找相應(yīng)的調(diào)用的那段代碼。
總結(jié):
1 Objective-C Runtime是一個(gè)將C語言轉(zhuǎn)化為面向?qū)ο笳Z言的擴(kuò)展。
2 Objective-C是基于C語言加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機(jī)制的動(dòng)態(tài)語言,這意味著它不僅需要一個(gè)編譯器,還需要Runtime系統(tǒng)來動(dòng)態(tài)創(chuàng)建類和對象,進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)。
3 [receiver message]真的不是一個(gè)簡簡單單的方法調(diào)用。因?yàn)檫@只是在編譯階段確定了要向接收者發(fā)送message這條消息,而receive將要如何響應(yīng)這條消息,那就要看運(yùn)行時(shí)發(fā)生的情況來決定了。
4 因?yàn)镺bjc是一門動(dòng)態(tài)語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)。也就是說只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來執(zhí)行編譯后的代碼。

與Runtime交互


Objc 從三種不同的層級上與 Runtime 系統(tǒng)進(jìn)行交互,分別是通過 Objective-C 源代碼,通過 Foundation 框架的NSObject類定義的方法,通過對 runtime 函數(shù)的直接調(diào)用。

  • Objective-C源代碼
    大部分情況下你就只管寫你的Objc代碼就行,runtime 系統(tǒng)自動(dòng)在幕后辛勤勞作著。
    還記得引言中舉的例子吧,消息的執(zhí)行會(huì)使用到一些編譯器為實(shí)現(xiàn)動(dòng)態(tài)語言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù),Objc中的類、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來定義,這些內(nèi)容在后面會(huì)講到。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥)
  • NSObject的方法
    Cocoa 中大多數(shù)類都繼承于NSObject類,也就自然繼承了它的方法。最特殊的例外是NSProxy,它是個(gè)抽象超類,它實(shí)現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法,可以通過繼承它來實(shí)現(xiàn)一個(gè)其他類的替身類或是虛擬出一個(gè)不存在的類,說白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無限,但是把活兒都交給幕后小弟去干。
    有的NSObject中的方法起到了抽象接口的作用,比如description方法需要你重載它并為你定義的類提供描述內(nèi)容。NSObject還有些方法能在運(yùn)行時(shí)獲得類的信息,并檢查一些特性,比如class返回對象的類;isKindOfClass:和isMemberOfClass:則檢查對象是否在指定的類繼承體系中;respondsToSelector:檢查對象能否響應(yīng)指定的消息;conformsToProtocol:檢查對象是否實(shí)現(xiàn)了指定協(xié)議類的方法;methodForSelector:則返回指定方法實(shí)現(xiàn)的地址。
  • Runtime的函數(shù)
    Runtime 系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,具有公共接口的動(dòng)態(tài)共享庫。頭文件存放于/usr/include/objc目錄下。許多函數(shù)允許你用純C代碼來重復(fù)實(shí)現(xiàn) Objc 中同樣的功能。雖然有一些方法構(gòu)成了NSObject類的基礎(chǔ),但是你在寫 Objc 代碼時(shí)一般不會(huì)直接用到這些函數(shù)的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作。在Objective-C Runtime Reference中有對 Runtime 函數(shù)的詳細(xì)文檔。

runtime術(shù)語了解


id objc_msgSend ( id self, SEL op, ... );

這是個(gè)最基本的用于發(fā)送消息的函數(shù)。
其實(shí)編譯器會(huì)根據(jù)情況在objc_msgSend, objc_msgSend_stret,,objc_msgSendSuper, 或 objc_msgSendSuper_stret 四個(gè)方法中選擇一個(gè)來調(diào)用。如果消息是傳遞給超類,那么會(huì)調(diào)用名字帶有 Super 的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí),那么會(huì)調(diào)用名字帶有stret的函數(shù)。
例子:

objc_msgSend(person,@selector(sayHello));

簡單過程如下:
第一個(gè)參數(shù)是要發(fā)送消息的實(shí)例,也就是 person 對象。 objc_msgSend 會(huì)先查詢它的 methodLists 方法列表,使用第二個(gè)參數(shù) sayHello 逐個(gè)和 person 的 methodLists 中的每一個(gè)方法信息的 SEL 進(jìn)行對比,如果找到對應(yīng)的方法,就調(diào)用它所對應(yīng)的函數(shù),也就是 IMP,然后調(diào)用這個(gè)函數(shù)。

  • id和Class
    objc_msgSend第一個(gè)參數(shù)類型為id,大家對它都不陌生,它是一個(gè)指向類實(shí)例的指針:
typedef struct objc_object *id;

那objc_object又是啥呢:

struct objc_object { Class isa; };

Class是一個(gè)指向objc_class結(jié)構(gòu)體的指針,而id是一個(gè)指向objc_object結(jié)構(gòu)體的指針,其中的isa是一個(gè)指向objc_class結(jié)構(gòu)體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。
PS:isa指針不總是指向?qū)嵗龑ο笏鶎俚念?,不能依靠它來確定類型,而是應(yīng)該用class方法來確定實(shí)例對象的類。因?yàn)镵VO的實(shí)現(xiàn)機(jī)理就是將被觀察對象的isa指針指向一個(gè)中間類而不是真實(shí)的類,這是一種叫做 isa-swizzling 的技術(shù).
objc_class定義如下:

typedef struct objc_class *Class;
struct objc_class { 
 Class isa                                 OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
 Class super_class                         OBJC2_UNAVAILABLE; // 父類
 const char *name                          OBJC2_UNAVAILABLE; // 類名
 long version                              OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0,可以通過runtime函數(shù)class_setVersion或者class_getVersion進(jìn)行修改、讀取
 long info                                 OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行時(shí)期使用的一些位標(biāo)識,如CLS_CLASS (0x1L) 表示該類為普通 class,其中包含實(shí)例方法和變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
 long instance_size                        OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大?。ò◤母割惱^承下來的實(shí)例變量)
 struct objc_ivar_list *ivars              OBJC2_UNAVAILABLE; // 該類的成員變量地址列表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法地址列表,與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲(chǔ)實(shí)例方法,如CLS_META (0x2L),則存儲(chǔ)類方法;
 struct objc_cache *cache                  OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址,用于提升效率;
 struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE; // 存儲(chǔ)該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

不知道你是否注意到了objc_class中也有一個(gè)isa對象,這是因?yàn)橐粋€(gè) ObjC 類本身同時(shí)也是一個(gè)對象,為了處理類和對象的關(guān)系,runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西,類對象所屬類型就叫做元類,它用來表述類對象本身所具備的元數(shù)據(jù)。類方法就定義于此處,因?yàn)檫@些方法可以理解成類對象的實(shí)例方法。每個(gè)類僅有一個(gè)類對象,而每個(gè)類對象僅有一個(gè)與之相關(guān)的元類。當(dāng)你發(fā)出一個(gè)類似[NSObject alloc]的消息時(shí),你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類對象 (Class Object) ,這個(gè)類對象必須是一個(gè)元類的實(shí)例,而這個(gè)元類同時(shí)也是一個(gè)根元類 (root meta class) 的實(shí)例。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類對象的時(shí)候,objc_msgSend()會(huì)去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了,然后對這個(gè)類對象執(zhí)行方法調(diào)用。
由以上代碼可見,類與對象的區(qū)別就是類比對象多了很多特征成員,類也可以當(dāng)做一個(gè)objc_object來對待,也就是說類和對象都是對象,分別稱作類對象(class object)和實(shí)例對象(instance object),這樣我們就可以區(qū)別對象和類了。
isa:objc_object(實(shí)例對象)中isa指針指向的類結(jié)構(gòu)稱為class(也就是該對象所屬的類)其中存放著普通成員變量與動(dòng)態(tài)方法(“-”開頭的方法);此處isa指針指向的類結(jié)構(gòu)稱為metaclass,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)。
super_class: 指向該類的父類的指針,如果該類是根類(如NSObject或NSProxy),那么super_class就為nil。
類與對象的繼承層次關(guān)系如下圖:

class-diagram

  • SEL
    objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的 ID,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;

objc_selector的定義如下:

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;// 名稱
    char *types;                      OBJC2_UNAVAILABLE;// 類型
};

其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè)SEL類型的方法選擇器。
不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長長的方法哦。
如果你知道selector對應(yīng)的方法名是什么,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串,再用NSLog打印。

  • Method
    Method是一種代表類中的某個(gè)方法的類型。
typedef struct objc_method *Method;

而objc_method在上面的方法列表中提到過,它存儲(chǔ)了方法名,方法類型和方法實(shí)現(xiàn):

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

方法名類型為SEL,前面提到過相同名字的方法即使在不同類中定義,它們的方法選擇器也相同。
方法類型method_types是個(gè)char指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型。
最后一個(gè)參數(shù)是 IMP 類型,它表示這個(gè) Selector 對應(yīng)的函數(shù)的地址。 對,是函數(shù)沒錯(cuò)。 Objective-C 中定義的所有類的方法在底層實(shí)現(xiàn)上就是一個(gè)函數(shù)。

  • methodLists
struct objc_method_list **methodLists

methodLists 屬性表示當(dāng)前實(shí)例的方法列表,它是一個(gè) objc_method_list 類型的結(jié)構(gòu):

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

這個(gè)結(jié)構(gòu)的定義可能大家會(huì)有些地方不太明白,比如 space 屬性是干什么的。咱們可以暫時(shí)拋開這些問題,只關(guān)心和消息分發(fā)相關(guān)的屬性 - method_count 屬性表示當(dāng)前這個(gè)實(shí)例中方法的個(gè)數(shù),method_list 結(jié)構(gòu)表示當(dāng)前實(shí)例上面所有方法的列表:

struct objc_method method_list[1]
  • Ivar
    Ivar是一種代表類中實(shí)例變量的類型。
typedef struct objc_ivar *Ivar;

objc_ivar的數(shù)據(jù)結(jié)構(gòu)如下:

struct objc_ivar {
    char *ivar_name                   OBJC2_UNAVAILABLE; // 變量名
    char *ivar_type                   OBJC2_UNAVAILABLE; // 變量類型
    int ivar_offset                   OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE; // 占用空間
#endif
}   

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

-(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;
} 
  • IMP
    IMPobjc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);

SEL和IMP一一對應(yīng)

它就是一個(gè)函數(shù)指針,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的。而 IMP 這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開消息傳遞階段,直接執(zhí)行方法,這在后面會(huì)提到。
你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類型相同,參數(shù)都包含id和SEL類型。每個(gè)方法名都對應(yīng)一個(gè)SEL類型的方法選擇器,而每個(gè)實(shí)例對象中的SEL對應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過一組id和SEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址;反之亦然。

  • Cache
    在runtime.h中Cache的定義如下:
typedef struct objc_cache *Cache

還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache *cache吧,它到底是緩存啥的呢,先看看objc_cache的實(shí)現(xiàn):

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

Cache為方法調(diào)用的性能進(jìn)行優(yōu)化,通俗地講,每當(dāng)實(shí)例對象接收到一個(gè)消息時(shí),它不會(huì)直接在isa指向的類的方法列表中遍歷查找能夠響應(yīng)消息的方法,因?yàn)檫@樣效率太低了,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過的 CPU 繞過主存先訪問Cache的道理挺像,而我猜蘋果為提高Cache命中率應(yīng)該也做了努力吧。

  • Catagory
    這個(gè)就是我們平時(shí)所說的類別了,很熟悉吧。它可以動(dòng)態(tài)的為已存在的類添加新的方法。
    它的定義如下:
typedef struct objc_category *Category;

objc_category的定義如下:

struct objc_category {
    char *category_name                           OBJC2_UNAVAILABLE; // 類別名稱
    char *class_name                              OBJC2_UNAVAILABLE; // 類名
    struct objc_method_list *instance_methods     OBJC2_UNAVAILABLE; // 實(shí)例方法列表
    struct objc_method_list *class_methods        OBJC2_UNAVAILABLE; // 類方法列表
    struct objc_protocol_list *protocols          OBJC2_UNAVAILABLE; // 協(xié)議列表
}
  • objc_property_t
    objc_property_t是屬性,它的定義如下:
typedef struct objc_property *objc_property_t;

objc_property是內(nèi)置的類型,與之關(guān)聯(lián)的還有一個(gè)objc_property_attribute_t,它是屬性的attribute,也就是其實(shí)是對屬性的詳細(xì)描述,包括屬性名稱、屬性編碼類型、原子類型/非原子類型等。它的定義如下:

typedef struct {
    const char *name; // 名稱
    const char *value;  // 值(通常是空的)
} objc_property_attribute_t;

消息


Objc 中發(fā)送消息是用中括號([])把接收者和消息括起來,而直到運(yùn)行時(shí)才會(huì)把消息與方法實(shí)現(xiàn)綁定。消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 IMP 的過程,有了函數(shù)指針就可以執(zhí)行對應(yīng)的方法實(shí)現(xiàn);消息轉(zhuǎn)發(fā)(Message Forwarding)是在查找 IMP 失敗后執(zhí)行一系列轉(zhuǎn)發(fā)流程的慢速通道,如果不作轉(zhuǎn)發(fā)處理,則會(huì)打日志和拋出異常。

objc_msgSend函數(shù)

此函數(shù)是消息發(fā)送必經(jīng)之路,但只要一提 objc_msgSend,都會(huì)說它的偽代碼如下或類似的邏輯,反正就是獲取 IMP 并調(diào)用:

id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}
  • objc_msgSend匯編語言編寫
    當(dāng)需要發(fā)送消息時(shí),編譯器會(huì)生成中間代碼,根據(jù)情況分別調(diào)用 objc_msgSend, objc_msgSend_stret,objc_msgSendSuper, 或 objc_msgSendSuper_stret 其中之一。
    這也是為什么`` objc_msgSend``` 要用匯編語言而不是 OC、C 或 C++ 語言來實(shí)現(xiàn),因?yàn)閱为?dú)一個(gè)方法定義滿足不了多種類型返回值,有的方法返回 id,有的返回 int??紤]到不同類型參數(shù)返回值排列組合映射不同方法簽名(method signature)的問題,那 switch 語句得老長了。。。這些原因可以總結(jié)為 Calling Convention,也就是說函數(shù)調(diào)用者與被調(diào)用者必須約定好參數(shù)與返回值在不同架構(gòu)處理器上的存取規(guī)則,比如參數(shù)是以何種順序存儲(chǔ)在棧上,或是存儲(chǔ)在哪些寄存器上。除此之外還有其他原因,比如其可變參數(shù)用匯編處理起來最方便,因?yàn)檎业?IMP 地址后參數(shù)都在棧上。要是用 C++ 傳遞可變參數(shù)那就悲劇了,prologue 機(jī)制會(huì)弄亂地址(比如 i386 上為了存儲(chǔ) ebp 向后移位 4byte),最后還要用 epilogue 打掃戰(zhàn)場。而且匯編程序執(zhí)行效率高,在 Objective-C Runtime 中調(diào)用頻率較高的函數(shù)好多都用匯編寫的。

lookUpImpOrForward函數(shù)

使用 lookUpImpOrForward 快速查找 IMP。
其實(shí)_class_lookupMethodAndLoadCache3 函數(shù)(objc_msgSend匯編語言中查找IMP的函數(shù))其實(shí)只是簡單的調(diào)用了 lookUpImpOrForward 函數(shù):

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

注意lookUpImpOrForward 調(diào)用時(shí)使用緩存參數(shù)傳入為 NO,因?yàn)橹耙呀?jīng)嘗試過查找緩存了。IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) 實(shí)現(xiàn)了一套查找 IMP 的標(biāo)準(zhǔn)路徑,也就是在消息轉(zhuǎn)發(fā)(Forward)之前的邏輯。

消息發(fā)送

1 檢測這個(gè) selector 是不是要忽略的。比如 Mac OS X 開發(fā),有了垃圾回收就不理會(huì) retain, release 這些函數(shù)了。
2 檢測這個(gè) target 是不是 nil 對象。ObjC 的特性是允許對一個(gè) nil 對象執(zhí)行任何一個(gè)方法不會(huì) Crash,因?yàn)闀?huì)被忽略掉。
3 如果上面兩個(gè)都過了,那就開始查找這個(gè)類的 IMP,先從 cache 里面找,完了找得到就跳到對應(yīng)的函數(shù)去執(zhí)行。
4 如果 cache 找不到就找一下方法分發(fā)表。
5 如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找,直到找到NSObject類為止。
6 如果還找不到就要開始進(jìn)入動(dòng)態(tài)方法解析了,后面會(huì)提到。
PS:這里說的分發(fā)表其實(shí)就是Class中的方法列表,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來。

messaging1

其實(shí)編譯器會(huì)根據(jù)情況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或objc_msgSendSuper_stret四個(gè)方法中選擇一個(gè)來調(diào)用。如果消息是傳遞給超類,那么會(huì)調(diào)用名字帶有”Super”的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí),那么會(huì)調(diào)用名字帶有”stret”的函數(shù)。排列組合正好四個(gè)方法。

值得一提的是在 i386 平臺處理返回類型為浮點(diǎn)數(shù)的消息時(shí),需要用到objc_msgSend_fpret函數(shù)來進(jìn)行處理,這是因?yàn)榉祷仡愋蜑楦↑c(diǎn)數(shù)的函數(shù)對應(yīng)的 ABI(Application Binary Interface) 與返回整型的函數(shù)的 ABI 不兼容。此時(shí)objc_msgSend不再適用,于是objc_msgSend_fpret被派上用場,它會(huì)對浮點(diǎn)數(shù)寄存器做特殊處理。不過在 PPC 或 PPC64 平臺是不需要麻煩它的。

PS:有木有發(fā)現(xiàn)這些函數(shù)的命名規(guī)律哦?帶“Super”的是消息傳遞給超類;“stret”可分為“st”+“ret”兩部分,分別代表“struct”和“return”;“fpret”就是“fp”+“ret”,分別代表“floating-point”和“return”。

方法中的隱藏參數(shù)

我們經(jīng)常在方法中使用self關(guān)鍵字來引用實(shí)例本身,但從沒有想過為什么self就能取到調(diào)用當(dāng)前方法的對象吧。其實(shí)self的內(nèi)容是在方法運(yùn)行時(shí)被偷偷的動(dòng)態(tài)傳入的。
當(dāng)objc_msgSend找到方法對應(yīng)的實(shí)現(xiàn)時(shí),它將直接調(diào)用該方法實(shí)現(xiàn),并將消息中所有的參數(shù)都傳遞給方法實(shí)現(xiàn),同時(shí),它還將傳遞兩個(gè)隱藏的參數(shù):
接收消息的對象(也就是self指向的內(nèi)容)
方法選擇器(_cmd指向的內(nèi)容)
如:

[person sayHello];
/*
最后調(diào)用sayHello時(shí),是sayHello(person,@selector(sayHello));這樣
*/

之所以說它們是隱藏的是因?yàn)樵谠创a方法的定義中并沒有聲明這兩個(gè)參數(shù)。它們是在代碼被編譯時(shí)被插入實(shí)現(xiàn)中的。盡管這些參數(shù)沒有被明確聲明,在源代碼中我們?nèi)匀豢梢砸盟鼈?。在下面的例子中?code>self引用了接收者對象,而_cmd引用了方法本身的選擇器:

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

在這兩個(gè)參數(shù)中,self 更有用。實(shí)際上,它是在方法實(shí)現(xiàn)中訪問消息接收者對象的實(shí)例變量的途徑。
而當(dāng)方法中的super關(guān)鍵字接收到消息時(shí),編譯器會(huì)創(chuàng)建一個(gè)objc_super結(jié)構(gòu)體:

struct objc_super { id receiver; Class class; };

這個(gè)結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定超類的定義。但receiver仍然是self本身,這點(diǎn)需要注意,因?yàn)楫?dāng)我們想通過[super class]獲取超類時(shí),編譯器只是將指向self的id指針和class的SEL傳遞給了objc_msgSendSuper函數(shù),因?yàn)橹挥性贜SObject類才能找到class方法,然后class方法調(diào)用object_getClass(),接著調(diào)用objc_msgSend(objc_super->receiver, @selector(class)),傳入的第一個(gè)參數(shù)是指向self的id指針,與調(diào)用[self class]相同,所以我們得到的永遠(yuǎn)都是self的類型。

獲取方法地址

在IMP那節(jié)提到過可以避開消息綁定而直接獲取方法的地址并調(diào)用方法。這種做法很少用,除非是需要持續(xù)大量重復(fù)調(diào)用某方法的極端情況,避開消息發(fā)送泛濫而直接調(diào)用該方法會(huì)更高效。
NSObject類中有個(gè)methodForSelector:實(shí)例方法,你可以用它來獲取某個(gè)方法選擇器對應(yīng)的IMP,舉個(gè)栗子:

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

當(dāng)方法被當(dāng)做函數(shù)調(diào)用時(shí),上節(jié)提到的兩個(gè)隱藏參數(shù)就需要我們明確給出了。上面的例子調(diào)用了1000次函數(shù),你可以試試直接給target發(fā)送1000次setFilled:消息會(huì)花多久。
PS:methodForSelector:方法是由 Cocoa 的 Runtime 系統(tǒng)提供的,而不是 Objc 自身的特性。

動(dòng)態(tài)方法解析


你可以動(dòng)態(tài)地提供一個(gè)方法的實(shí)現(xiàn)。例如我們可以用@dynamic關(guān)鍵字在類的實(shí)現(xiàn)文件中修飾一個(gè)屬性:

@dynamic propertyName;

這表明我們會(huì)為這個(gè)屬性動(dòng)態(tài)提供存取方法,也就是說編譯器不會(huì)再默認(rèn)為我們生成setPropertyName:propertyName方法,而需要我們動(dòng)態(tài)提供。我們可以通過分別重載resolveInstanceMethod:resolveClassMethod:方法分別添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn)。因?yàn)楫?dāng) Runtime 系統(tǒng)在Cache和方法分發(fā)表中(包括超類)找不到要執(zhí)行的方法時(shí),Runtime會(huì)調(diào)用resolveInstanceMethod:resolveClassMethod:來給程序員一次動(dòng)態(tài)添加方法實(shí)現(xiàn)的機(jī)會(huì)。我們需要用class_addMethod函數(shù)完成向特定類添加特定方法實(shí)現(xiàn)的操作:

ynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

上面的例子為resolveThisMethodDynamically方法添加了實(shí)現(xiàn)內(nèi)容,也就是dynamicMethodIMP方法中的代碼。其中 “v@:” 表示返回值和參數(shù),這個(gè)符號涉及 Type Encoding
PS:動(dòng)態(tài)方法解析會(huì)在消息轉(zhuǎn)發(fā)機(jī)制浸入前執(zhí)行。如果 respondsToSelector:instancesRespondToSelector:方法被執(zhí)行,動(dòng)態(tài)方法解析器將會(huì)被首先給予一個(gè)提供該方法選擇器對應(yīng)的IMP的機(jī)會(huì)。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制,那么就讓resolveInstanceMethod:返回NO。
評論區(qū)有人問如何用 resolveClassMethod: 解析類方法,我將他貼出有問題的代碼做了糾正和優(yōu)化后如下,可以順便將實(shí)例方法和類方法的動(dòng)態(tài)方法解析對比下:
頭文件:

#import <Foundation/Foundation.h>

@interface Student : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
m 文件:
#import "Student.h"
#import <objc/runtime.h>

@implementation Student
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(learnClass:)) {
        class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
        return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(goToSchool:)) {
        class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

+ (void)myClassMethod:(NSString *)string {
    NSLog(@"myClassMethod = %@", string);
}

- (void)myInstanceMethod:(NSString *)string {
    NSLog(@"myInstanceMethod = %@", string);
}
@end

需要深刻理解[self class]object_getClass(self) 甚至 object_getClass([self class]) 的關(guān)系,其實(shí)并不難,重點(diǎn)在于 self 的類型:
當(dāng) self 為實(shí)例對象時(shí),[self class]object_getClass(self)等價(jià),因?yàn)榍罢邥?huì)調(diào)用后者。object_getClass([self class])得到元類。
當(dāng) self 為類對象時(shí),[self class] 返回值為自身,還是 self。object_getClass(self)object_getClass([self class])等價(jià)。
凡是涉及到類方法時(shí),一定要弄清楚元類、selector、IMP 等概念,這樣才能做到舉一反三,隨機(jī)應(yīng)變。

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


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

重定向()

在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì),即通過重載- (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對象:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

畢竟消息轉(zhuǎn)發(fā)要耗費(fèi)更多時(shí)間,抓住這次機(jī)會(huì)將消息重定向給別人是個(gè)不錯(cuò)的選擇,不過千萬別返回self,因?yàn)槟菢訒?huì)死循環(huán)。 如果此方法返回nil或self,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制(forwardInvocation:);否則將向返回的對象重新發(fā)送消息。
如果想替換類方法的接受者,需要覆寫+ (id)forwardingTargetForSelector:(SEL)aSelector 方法,并返回類對象:

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(xxx)) {
        return NSClassFromString(@"Class name");
    }
    return [super forwardingTargetForSelector:aSelector];
}

轉(zhuǎn)發(fā)

當(dāng)動(dòng)態(tài)方法解析不作處理返回NO時(shí),消息轉(zhuǎn)發(fā)機(jī)制會(huì)被觸發(fā)。在這時(shí)forwardInvocation:方法會(huì)被執(zhí)行,我們可以重寫這個(gè)方法來定義我們的轉(zhuǎn)發(fā)邏輯:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

該消息的唯一參數(shù)是個(gè)NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數(shù)。我們可以實(shí)現(xiàn)forwardInvocation:方法來對不能處理的消息做一些默認(rèn)的處理,也可以將消息轉(zhuǎn)發(fā)給其他對象來處理,而不拋出錯(cuò)誤。
這里需要注意的是參數(shù)anInvocation是從哪的來的呢?其實(shí)在forwardInvocation:消息發(fā)送前,Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對象。所以我們在重寫forwardInvocation:的同時(shí)也要重寫methodSignatureForSelector:方法,否則會(huì)拋異常。
當(dāng)一個(gè)對象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過forwardInvocation:消息通知該對象。每個(gè)對象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實(shí)現(xiàn)只是簡單地調(diào)用了doesNotRecognizeSelector:。通過實(shí)現(xiàn)我們自己的forwardInvocation:方法,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對象。
forwardInvocation:方法就像一個(gè)不能識別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對象?;蛘咚部梢韵笠粋€(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤。forwardInvocation:方法也可以對不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對象鏈接到消息鏈的能力。
注意: forwardInvocation:方法只有在消息接收對象中無法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用。 所以,如果我們希望一個(gè)對象將negotiate消息轉(zhuǎn)發(fā)給其它對象,則這個(gè)對象不能有negotiate方法。否則,forwardInvocation:將不可能會(huì)被調(diào)用。

Method Swizzling


之前所說的消息轉(zhuǎn)發(fā)雖然功能強(qiáng)大,但需要我們了解并且能更改對應(yīng)類的源代碼,因?yàn)槲覀冃枰獙?shí)現(xiàn)自己的轉(zhuǎn)發(fā)邏輯。當(dāng)我們無法觸碰到某個(gè)類的源代碼,卻想更改這個(gè)類某個(gè)方法的實(shí)現(xiàn)時(shí),該怎么辦呢?可能繼承類并重寫方法是一種想法,但是有時(shí)無法達(dá)到目的。這里介紹的是 Method Swizzling ,它通過重新映射方法對應(yīng)的實(shí)現(xiàn)來達(dá)到“偷天換日”的目的。跟消息轉(zhuǎn)發(fā)相比,Method Swizzling 的做法更為隱蔽,甚至有些冒險(xiǎn),也增大了debug的難度。
交換方法的幾種實(shí)現(xiàn)方式

  • 利用 method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)
  • 利用 class_replaceMethod 替換方法的實(shí)現(xiàn)
  • 利用 method_setImplementation 來直接設(shè)置某個(gè)方法的IMP
交換方法

這里摘抄一個(gè) NSHipster 的例子:

#import <objc/runtime.h> 
 
@implementation UIViewController (Tracking) 
 
+ (void)load { 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        Class aClass = [self class]; 
 
        SEL originalSelector = @selector(viewWillAppear:); 
        SEL swizzledSelector = @selector(xxx_viewWillAppear:); 
 
        Method originalMethod = class_getInstanceMethod(aClass, originalSelector); 
        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); 
        
        // When swizzling a class method, use the following:
        // Class aClass = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(aClass, originalSelector);
        // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
 
        BOOL didAddMethod = 
            class_addMethod(aClass, 
                originalSelector, 
                method_getImplementation(swizzledMethod), 
                method_getTypeEncoding(swizzledMethod)); 
 
        if (didAddMethod) { 
            class_replaceMethod(aClass, 
                swizzledSelector, 
                method_getImplementation(originalMethod), 
                method_getTypeEncoding(originalMethod)); 
        } else { 
            method_exchangeImplementations(originalMethod, swizzledMethod); 
        } 
    }); 
} 
 
#pragma mark - Method Swizzling 
 
- (void)xxx_viewWillAppear:(BOOL)animated { 
    [self xxx_viewWillAppear:animated]; 
    NSLog(@"viewWillAppear: %@", self); 
} 
 
@end

上面的代碼通過添加一個(gè)Tracking類別到UIViewController類中,將UIViewController類的viewWillAppear:方法和Tracking類別中xxx_viewWillAppear:方法的實(shí)現(xiàn)相互調(diào)換。Swizzling 應(yīng)該在+ load方法中實(shí)現(xiàn),因?yàn)?code>+ load是在一個(gè)類最開始加載時(shí)調(diào)用。dispatch_once是GCD中的一個(gè)方法,它保證了代碼塊只執(zhí)行一次,并讓其為一個(gè)原子操作,線程安全是很重要的。
如果類中不存在要替換的方法,那就先用class_addMethodclass_replaceMethod函數(shù)添加和替換兩個(gè)方法的實(shí)現(xiàn);如果類中已經(jīng)有了想要替換的方法,那么就調(diào)用method_exchangeImplementations函數(shù)交換了兩個(gè)方法的 IMP,這是蘋果提供給我們用于實(shí)現(xiàn) Method Swizzling 的便捷方法。
可能有人注意到了這行:

// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);

object_getClass((id)self)[self class] 返回的結(jié)果類型都是 Class,但前者為元類,后者為其本身,因?yàn)榇藭r(shí) self 為 Class 而不是實(shí)例.注意 [NSObject class][object class]的區(qū)別:

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

PS:如果類中沒有想被替換實(shí)現(xiàn)的原方法時(shí),class_replaceMethod相當(dāng)于直接調(diào)用class_addMethod向類中添加該方法的實(shí)現(xiàn);否則調(diào)用method_setImplementation方法,types參數(shù)會(huì)被忽略。method_exchangeImplementations方法做的事情與如下的原子操作等價(jià):

IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

最后xxx_viewWillAppear:方法的定義看似是遞歸調(diào)用引發(fā)死循環(huán),其實(shí)不會(huì)的。因?yàn)?code>[self xxx_viewWillAppear:animated]消息會(huì)動(dòng)態(tài)找到xxx_viewWillAppear:方法的實(shí)現(xiàn),而它的實(shí)現(xiàn)已經(jīng)被我們與viewWillAppear:方法實(shí)現(xiàn)進(jìn)行了互換,所以這段代碼不僅不會(huì)死循環(huán),如果你把[self xxx_viewWillAppear:animated]換成[self viewWillAppear:animated]反而會(huì)引發(fā)死循環(huán)。

runtime下class的各種常用操作


// 獲取方法列表
    unsigned int methodListCount;
    Method *methodList = class_copyMethodList([self class], &methodListCount);
    for (unsigned int i = 0; i < methodListCount; i++) {
        Method method = methodList[i];
        NSLog(@"method----->%@", NSStringFromSelector(method_getName(method)));
    }

    // 獲取成員變量列表
    unsigned int ivarCount;
    Ivar *ivarList = class_copyIvarList([self class], &ivarCount);
    for (unsigned int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivarList[i];
        const char *ivarName = ivar_getName(ivar);
        NSLog(@"Ivar----->%@", [NSString stringWithUTF8String:ivarName]);
    }

    // 獲取協(xié)議列表
    unsigned int protocolListCount;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &protocolListCount);
    for (unsigned int i = 0; i < protocolListCount; i++) {
        Protocol *protocol = protocolList[1];
        const char *protocolName = protocol_getName(protocol);
        NSLog(@"protocol----->%@", [NSString stringWithUTF8String:protocolName]);
    }

    // 獲取類方法
    Method classMethod = class_getClassMethod([self class], @selector(cancelPreviousPerformRequestsWithTarget:));
    NSLog(@"class method----->%@", NSStringFromSelector(method_getName(classMethod)));

    // 獲取實(shí)例方法
    Method instanceMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    NSLog(@"instance method----->%@", NSStringFromSelector(method_getName(instanceMethod)));
    
    // 替換方法的實(shí)現(xiàn)
    Method newMethod = class_getInstanceMethod([self class], @selector(newReplaceMethod));
    class_replaceMethod([self class], @selector(oldReplaceMethod), method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
    [self oldReplaceMethod];
    
    
    // 交換兩個(gè)方法
    Method oldChangeMethod = class_getInstanceMethod([self class], @selector(oldexchangeMethod));
    Method newChangeMethod = class_getInstanceMethod([self class], @selector(newexchangeMethod));
    method_exchangeImplementations(oldChangeMethod, newChangeMethod);
    [self oldexchangeMethod];
    [self newexchangeMethod];

總結(jié)


我們之所以讓自己的類繼承NSObject不僅僅因?yàn)樘O果幫我們完成了復(fù)雜的內(nèi)存分配問題,更是因?yàn)檫@使得我們能夠用上 Runtime 系統(tǒng)帶來的便利??赡芪覀兤綍r(shí)寫代碼時(shí)可能很少會(huì)考慮一句簡單的[receiver message]背后發(fā)生了什么,而只是當(dāng)做方法或函數(shù)調(diào)用。深入理解 Runtime 系統(tǒng)的細(xì)節(jié)更有利于我們利用消息機(jī)制寫出功能更強(qiáng)大的代碼,比如 Method Swizzling 等。

轉(zhuǎn)載:Objective-C Runtime
參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,751評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 755評論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 772評論 0 1
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,225評論 0 7
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 816評論 0 4