Runtime概念

runtime

運(yùn)行時(shí)語言,實(shí)現(xiàn)Object-C的C語言庫,將OC轉(zhuǎn)換成C進(jìn)行編譯的過渡者。

作為一門動(dòng)態(tài)編程語言,Objective-C 會(huì)盡可能的將編譯和鏈接時(shí)要做的事情推遲到運(yùn)行時(shí)。只要有可能,Objective-C 總是使用動(dòng)態(tài) 的方式來解決問題。這意味著 Objective-C 語言不僅需要一個(gè)編譯環(huán)境,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來執(zhí)行編譯好的代碼,這就是runtime

運(yùn)行時(shí)系統(tǒng)(runtime)扮演的角色類似于 Objective-C 語言的操作系統(tǒng),Objective-C 基于該系統(tǒng)來工作。因此,runtime好比Objective-C的靈魂,很多東西都是在這個(gè)基礎(chǔ)上出現(xiàn)的。所以它是值的你花功夫去理解的。

實(shí)現(xiàn)

主要是用C語言實(shí)現(xiàn),部分由匯編語言。實(shí)際上正是runtime將OC中面向?qū)ο蟮念愞D(zhuǎn)換成C語言中面向過程的結(jié)構(gòu)體。無論是實(shí)例對象還是類對象,實(shí)際上對應(yīng)的都是C中的結(jié)構(gòu)體。

使用

iskindofclass和isMemberOfClass就是兩個(gè)典型的運(yùn)行時(shí)方法。
同時(shí)注意使用class_addMethod等方法時(shí)要引入#import <objc/runtime.h>頭文件。

關(guān)于32位與64位

runtime分為早期版本和現(xiàn)行版本,32位使用早期,64位使用現(xiàn)行。32位已被淘汰。
64位與32位的區(qū)別在于,操作系統(tǒng)上64位用于3D動(dòng)畫等先進(jìn)功能32位用于日常簡單使用;處理器上64位比32位更寬處理能力更強(qiáng);軟件上基于32位和64位開發(fā)的不同;內(nèi)存容量不同。現(xiàn)在必須支持arm64,代碼中主要注意類型長度上的區(qū)別。ios推薦使用的NSInteger區(qū)別于int的地方也就是在于前者可以根據(jù)系統(tǒng)位數(shù)使用較大的長度。

與傳統(tǒng)靜態(tài)語言C語言的區(qū)別

很常見的一個(gè)消息發(fā)送語句:

[receiver message]

會(huì)被編譯器轉(zhuǎn)化成

objc_msgSend(receiver, selector)

如果有參數(shù)則為

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

receiver:消息接受者(receiver),self,某個(gè)類
selector:方法名(message)
arg:參數(shù)
傳統(tǒng)的靜態(tài)語言,編譯器會(huì)自上而下,在存在調(diào)用函數(shù)時(shí)會(huì)進(jìn)入調(diào)用函數(shù),逐句實(shí)現(xiàn)編譯。
而OC在編譯過程中并不會(huì)這樣做,只是編輯成明確方法名調(diào)用者參數(shù)的形式,該函數(shù)被如何實(shí)現(xiàn)以及是否實(shí)現(xiàn)并不關(guān)心。只有在程序運(yùn)行時(shí)才會(huì)根據(jù)方法名進(jìn)入具體函數(shù)中,這也就是會(huì)在運(yùn)行中找不到對應(yīng)函數(shù)時(shí)會(huì)造成崩潰的原因。
OC實(shí)際上是使用消息傳遞機(jī)制代替?zhèn)鹘y(tǒng)C語言的函數(shù)調(diào)用。
_CMD,SEL類型,可以獲取當(dāng)前方法名的關(guān)鍵字。

- (void)message  
{  
    self.name = @"James";//通過self關(guān)鍵字給當(dāng)前對象的屬性賦值  
    SEL currentSel = _cmd;//通過_cmd關(guān)鍵字取到當(dāng)前函數(shù)對應(yīng)的SEL  
    NSLog(@"currentSel is :%s",(char *)currentSel);  
}

打印結(jié)果

ObjcRunTime[693:403] currentSel is :message

初步理解,其實(shí)objc_msgSend 所做的事情,就是通過我們傳入的self 指針,找到class 的method_list 然后根據(jù)SEL 做比較,沒有的話,就在super class 找,如此往復(fù)。直到找到匹配的SEL,然后,call imp。當(dāng)然,runtime在其中又做了很多優(yōu)化。

OC中的消息傳遞

OC中的消息傳遞與C中函數(shù)調(diào)用的最大區(qū)別在于OC中的消息傳遞在時(shí)運(yùn)行時(shí)中進(jìn)行的,而函數(shù)調(diào)用是在編譯時(shí)就可以進(jìn)行的。

id num = @123;
//輸出123
NSLog(@"%@", num);
//程序崩潰,報(bào)錯(cuò)[__NSCFNumber appendString:]: unrecognized selector sent to instance 0x7b27
[num appendString:@"Hello World"];

上述代碼沒有任何問題,id可以使任何類型包括NSString,但是在運(yùn)行過程中在NSNumber中找不到appendString這個(gè)方法,所以會(huì)報(bào)錯(cuò)。所以消息傳遞的強(qiáng)大之處在于可以在運(yùn)行時(shí)添加新方法,缺點(diǎn)在于無法再編譯時(shí)發(fā)現(xiàn)錯(cuò)誤。

OC中的消息傳遞轉(zhuǎn)化為C的函數(shù)調(diào)用

簡單的創(chuàng)建Person對象

//為了方便查看轉(zhuǎn)寫后的C語言代碼,將alloc和init分兩步完成
Person *p = [Person alloc];
p = [p init];
p.name = @"Jiaming Chen";
[p showMyself];

使用clang -rewrite-objc main.m可以轉(zhuǎn)化成C

Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("showMyself"));

第一行代碼簡要表示為

Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));

第一步,獲取Person類,第二步注冊alloc方法,然后通過objc_msgSend將alloc消息發(fā)送給消息接受者Person。
第二行代碼簡要表示為

p = objc_msgSend(p, sel_registerName("init"));

由此可見,對于OC而言都是以消息傳遞實(shí)現(xiàn),而正是runtime通過objc_msgSend將一個(gè)面向?qū)ο蟮南鬟f轉(zhuǎn)換成了一個(gè)面向過程的的函數(shù)調(diào)用。

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

我們知道消息傳遞實(shí)際上做的事就是利用objc_msgSend,將消息傳遞給接受者,在接受者的結(jié)構(gòu)體中的objc_method_list中查找方法。如果找到就可以直接調(diào)用,如果找不到就會(huì)通過super_class指針去它的父類中查找,直至基類NSObject。如果還是沒有找到就會(huì)調(diào)用NSObject的doesNotRecognizeSelector報(bào)出unrecognized的錯(cuò)誤。也就是我們常出現(xiàn)的unrecognized selector sent to instance錯(cuò)誤(對象提前被釋放或者為空導(dǎo)致無法識(shí)別其方法)。
但是在此之前,還有三次機(jī)會(huì)來處理這個(gè)消息來避免出現(xiàn)崩潰,這就是所謂的消息轉(zhuǎn)發(fā)。詳細(xì)見后文。

runtime源碼學(xué)習(xí)
  • selector
    selector可以叫做選擇器,其實(shí)指的就是對象的方法,也可以理解為C語言里面的函數(shù)指針,面向?qū)ο罄锩娴膶?yīng)概念。
    @selector(xxxx)的作用是找到名字為xxxx的方法。一般用于[a performSelector:@selector(b)];就是說去調(diào)用a對象的b方法,和[a b];的意思一樣,但是這樣更加動(dòng)態(tài)一些。@selector(xxxx)返回的類型是SEL,看方法說明的時(shí)候如果參數(shù)類型是SEL,那么就是要接受@selector(xxxx)返回的值。
    在Objc中 SEL的定義是:
typedef struct objc_selector *SEL;  

我們注意到對于SEL的定義,實(shí)際上就是使用typedef將結(jié)構(gòu)體objc_selector重新命名為*SEL。而SEL則是指向這個(gè)結(jié)構(gòu)體的指針,所以SEL的對象不需要再加*。
在Mac OS X中SEL其實(shí)被映射為一個(gè)C字符串,可以看作是方法的名字(編號),它并不一個(gè)指向具體方法實(shí)現(xiàn)(IMP類型才是)。對于所有的類,只要方法名是相同的,產(chǎn)生的selector都是一樣的。
簡而言之,你可以理解 @selector()就是取類方法的編號,他的行為基本可以等同C語言的中函數(shù)指針,只不過C語言中,可以把函數(shù)名直接賦給一個(gè)函數(shù)指針,而Object-C的類不能直接應(yīng)用函數(shù)指針,這樣只能做一個(gè)@selector語法來取。
它的結(jié)果是一個(gè)SEL類型。這個(gè)類型本質(zhì)是類方法的名字(編號)。

注意1. @selector是查找當(dāng)前類的方法,而[object @selector(方法名:方法參數(shù)..) ] ;是取object對應(yīng)類的相應(yīng)方法.
注意2.查找類方法時(shí),除了方法名,方法參數(shù)也查詢條件之一.
注意3. 可以用字符串來找方法 SEL 變量名 = NSSelectorFromString(方法名字的字符串);
注意4. 可以運(yùn)行中用SEL變量反向查出方法名字字符串
NSString *變量名 = NSStringFromSelector(SEL參數(shù));

runtime 在實(shí)現(xiàn)selector時(shí),實(shí)現(xiàn)了一個(gè)很大的Set,簡單的說就是一個(gè)經(jīng)過了優(yōu)化過的hash表。而Set的特點(diǎn)就是唯一,也就是SEL是唯一的。那么對于字符串的比較僅僅需要比較他們的地址就可以了。 所以O(shè)C中不允許使用方法名一樣參數(shù)不一樣的函數(shù),編譯器會(huì)根據(jù)每個(gè)方法的方法名生成唯一的SEL。所以,在速度上是無與倫比的。
selector主要用于兩個(gè)對象之間進(jìn)行松耦合的通訊.這種方法很多開發(fā)環(huán)境用到。比如GTK,Delphi.基本上整個(gè)Cocoa庫之間對象,控制之間通訊都是在這個(gè)基礎(chǔ)構(gòu)建的。
用戶行為統(tǒng)計(jì)中的經(jīng)典運(yùn)用:

+ (void)setUpAnalytics
{
   __weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     //讀取配置文件,獲取需要統(tǒng)計(jì)的事件列表
     for (NSString *classNameString in analyticsData.allKeys) {
         //使用運(yùn)行時(shí)創(chuàng)建類對象
         const char *className = [classNameString UTF8String];
         //從一個(gè)字串返回一個(gè)類
         Class newClass = objc_getClass(className);
         NSArray *pageEventList = [[analyticsData objectForKey:classNameString] objectForKey:Event];
         for (NSDictionary *eventDict in pageEventList) {
             //事件方法名稱
             NSString *eventMethodName = eventDict[MethodName];
             SEL seletor = NSSelectorFromString(eventMethodName);
             NSString *eventId = eventDict[EventId];
             [weakSelf trackEventWithClass:newClass selector:seletor event:eventId];
             [weakSelf uAnalyticstrackEventWithEventdata:eventDict];
         }
     }
   });
}
  • id
    id是通用類型指針,能夠表示任何對象,換句話說,id 類型的變量可以存放任何數(shù)據(jù)類型的對象。在內(nèi)部處理上,這種類型被定義為指向?qū)ο蟮闹羔?/strong>,實(shí)際上是一個(gè)指向這種對象的實(shí)例變量的指針。
    查看到id數(shù)據(jù)結(jié)構(gòu)如下:
// 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; 

實(shí)際上*id就是結(jié)構(gòu)體objc_object的別名。而id則是一個(gè)指向objc_object結(jié)構(gòu)體指針,它包含一個(gè)Class isa成員,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。需要注意的是id 是一個(gè)指針,所以在使用id的時(shí)候不需要加星號。

NSString *str = [[NSString alloc] init];

這里我們定義的對象str,實(shí)際上同樣是一個(gè)objc_object結(jié)構(gòu)體指針。

  • Class
    isa指針的數(shù)據(jù)類型是Class,Class表示對象所屬的類。
// An opaque type that represents an Objective-C class.  
typedef struct objc_class *Class;  

可以查看到Class其實(shí)就是一個(gè)objc_class結(jié)構(gòu)體指針。class類型和id類型一樣,本身繼承于指針類型,用于儲(chǔ)存類對象的指針,故在聲明class類型變量時(shí)和id一樣不需要*號。

NSString *str = [[NSString alloc] init];
Class c = [str Class];

這里我們定義的c,實(shí)際上同樣是一個(gè)objc_class結(jié)構(gòu)體指針。
查看到objc_class結(jié)構(gòu)體定義如下:

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;  
/* Use `Class` instead of `struct objc_class *` */  

OC的類其實(shí)也是一個(gè)對象,即類對象,意思就是你可以向一個(gè)類發(fā)送消息。為了可以調(diào)用類方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的類結(jié)構(gòu)體,也就是元類(meta-class)的概念。meta-class之所以重要,是因?yàn)樗4嬷鴦?chuàng)建類對象以及類方法所需的所有信息。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己所屬的類。所有的meta-class使用基類的meta-class作為它們的父類,而基類的meta-class也是屬于它自己,也就是說基類的meta-class的isa指針指向它自己。


meta圖解說明.png

上圖實(shí)線是super_class指針,虛線是isa指針。有幾個(gè)關(guān)鍵點(diǎn)需要解釋以下:
1.Root class (class)其實(shí)就是NSObject,NSObject是沒有超類的,所以Root class(class)的superclass指向nil。
2.每個(gè)Class都有一個(gè)isa指針指向唯一的Meta class
3.Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個(gè)回路。
4.每個(gè)Meta class的isa指針都指向Root class (meta)。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        Class c1 = [p class];
        Class c2 = [Person class];
        //輸出 1
        NSLog(@"%d", c1 == c2);
    }
    return 0;
}

由此可見,類對象是單例。對于Person這個(gè)類對象來說,只有一個(gè),所有類對象均是如此。
介紹兩個(gè)函數(shù):

OBJC_EXPORT BOOL class_isMetaClass(Class cls) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
    
OBJC_EXPORT Class object_getClass(id obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

class_isMetaClass用于判斷Class對象是否為元類,object_getClass用于獲取對象的isa指針指向的對象。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //輸出1
        NSLog(@"%d", [p class] == object_getClass(p));
        //輸出0
        NSLog(@"%d", class_isMetaClass(object_getClass(p)));
        //輸出1
        NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
        //輸出0
        NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
    }
    return 0;
}

由上看出,實(shí)例的isa指針指向類,類的isa指針指向元類。

super_class表示實(shí)例對象對應(yīng)的父類;
name表示類名,我們可以在運(yùn)行期,通過這個(gè)名稱查找到該類(通過:id objc_getClass(const char *aClassName))或該類的 metaclass(id objc_getMetaClass(const char *aClassName));
ivars表示多個(gè)成員變量,它指向objc_ivar_list結(jié)構(gòu)體。在runtime.h可以看到它的定義:

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

objc_ivar_list其實(shí)就是一個(gè)鏈表,存儲(chǔ)多個(gè)objc_ivar,而objc_ivar結(jié)構(gòu)體存儲(chǔ)類的單個(gè)成員變量信息。
(* struct objc_ivar ivar_list[1] 這個(gè)稱為結(jié)構(gòu)體數(shù)組,顧名思義,結(jié)構(gòu)數(shù)組是指能夠存放多個(gè)結(jié)構(gòu)體類型(objc_ivar)的一種數(shù)組(ivar_list)形式。
methodLists表示方法列表,它指向objc_method_list結(jié)構(gòu)體的二級指針,可以動(dòng)態(tài)修改
methodLists的值來添加成員方法,也是Category實(shí)現(xiàn)原理,同樣也解釋Category不能添加屬性的原因。
Category只能在運(yùn)行時(shí)中添加屬性。代碼如下:

///例如可能是這樣的使用
  static const void *propertyKey = &propertyKey;
  /// 將value通過運(yùn)行時(shí)綁定到self
  objc_setAssociatedObject(self, propertyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  /// 將value在運(yùn)行時(shí)中通過propertyKey取出綁定的值
  id value = objc_getAssociatedObject(self, propertyKey);

在runtime.h可以看到它的定義

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

同理,objc_method_list也是一個(gè)鏈表,存儲(chǔ)多個(gè)objc_method,而objc_method結(jié)構(gòu)體存儲(chǔ)類的某個(gè)方法的信息。
cache用來緩存經(jīng)常訪問的方法,它指向objc_cache結(jié)構(gòu)體,后面會(huì)重點(diǎn)講到。
protocols表示類遵循哪些協(xié)議。

  • Method
    Method表示類中的某個(gè)方法,在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;  
}  

其實(shí)Method就是一個(gè)指向objc_method結(jié)構(gòu)體指針,它存儲(chǔ)了方法名(method_name)、方法類型(method_types)和方法實(shí)現(xiàn)(method_imp)等信息。而method_imp的數(shù)據(jù)類型是IMP,它是一個(gè)函數(shù)指針,后面會(huì)重點(diǎn)提及。

  • Ivar
    Ivar表示類中的實(shí)例變量,在runtime.h文件中找到它的定義:
/// An opaque type that represents an instance variable.  
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  
}  

Ivar其實(shí)就是一個(gè)指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息。

  • IMP
    在上面講Method時(shí)就說過,IMP本質(zhì)上就是一個(gè)函數(shù)指針,指向方法的實(shí)現(xiàn),在objc.h找到它的定義:
/// A pointer to the function of a method implementation.   
#if !OBJC_OLD_DISPATCH_PROTOTYPES  
typedef void (*IMP)(void /* id, SEL, ... */ );   
#else  
typedef id (*IMP)(id, SEL, ...);   
#endif

當(dāng)你向某個(gè)對象發(fā)送一條信息,可以由這個(gè)函數(shù)指針來指定方法的實(shí)現(xiàn),它最終就會(huì)執(zhí)行那段代碼,這樣可以繞開消息傳遞階段而去執(zhí)行另一個(gè)方法實(shí)現(xiàn)。

  • cache
    顧名思義,Cache主要用來緩存,那它緩存什么呢?我們先在runtime.h文件看看它的定義
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;  
};  

Cache其實(shí)就是一個(gè)存儲(chǔ)Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對象receiver調(diào)用方法message時(shí),首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類,然后在類的methodLists中搜索方法,如果沒有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒有找到,再到methodLists查找。

消息發(fā)送
  • objc_msgSend函數(shù)
    在前面已經(jīng)提過,當(dāng)某個(gè)對象使用語法[receiver message]來調(diào)用某個(gè)方法時(shí),其實(shí)[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend ( id self, SEL op, ... );  

現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:
1.首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class;
2.優(yōu)先在class的cache查找message方法,如果找不到,再到methodLists查找;
3.如果沒有在class找到,再到super_class查找;
4.一旦找到message這個(gè)方法,就執(zhí)行它實(shí)現(xiàn)的IMP。

  • self與super
    為了讓大家更好地理解self和super,借用sunnyxx博客的iOS程序員6級考試一道題目:下面的代碼分別輸出什么?
@implementation Son : Father  
-(id)init  
{  
    self = [super init];  
    if (self)  
    {  
        NSLog(@"%@", NSStringFromClass([self class]));  
        NSLog(@"%@", NSStringFromClass([super class]));  
    }  
    return self;  
}  
@end  

self表示當(dāng)前這個(gè)類的對象,而super是一個(gè)編譯器標(biāo)示符,和self指向同一個(gè)消息接受者(son)。在本例中,無論是[self class]還是[super class],接受消息者都是Son對象,但super與self不同的是,self調(diào)用class方法時(shí),是在子類Son中查找方法,而super調(diào)用class方法時(shí),是在父類Father中查找方法。
當(dāng)調(diào)用[self class]方法時(shí),會(huì)轉(zhuǎn)化為objc_msgSend函數(shù),這個(gè)函數(shù)定義如下:

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

這時(shí)會(huì)從當(dāng)前Son類的方法列表中查找,如果沒有,就到Father類查找,還是沒有,最后在NSObject類查找到。我們可以從NSObject.mm文件中看到- (Class)class的實(shí)現(xiàn):

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

所以NSLog(@"%@", NSStringFromClass([self class]));會(huì)輸出Son。
當(dāng)調(diào)用[super class]方法時(shí),會(huì)轉(zhuǎn)化為objc_msgSendSuper,這個(gè)函數(shù)定義如下:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...) 

objc_msgSendSuper函數(shù)第一個(gè)參數(shù)super的數(shù)據(jù)類型是一個(gè)指向objc_super的結(jié)構(gòu)體,從message.h文件中查看它的定義:

/// Specifies the superclass of an instance.   
struct objc_super {  
    /// Specifies an instance of a class.  
    __unsafe_unretained id receiver;  
  
    /// Specifies the particular superclass of the instance to message.   
#if !defined(__cplusplus)  &&  !__OBJC2__  
    /* For compatibility with old objc-runtime.h header */  
    __unsafe_unretained Class class;  
#else  
    __unsafe_unretained Class super_class;  
#endif  
    /* super_class is the first class to search */  
};  
#endif 

結(jié)構(gòu)體包含兩個(gè)成員,第一個(gè)是receiver,表示某個(gè)類的實(shí)例。第二個(gè)是super_class表示當(dāng)前類的父類。
這時(shí)首先會(huì)構(gòu)造出objc_super結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體第一個(gè)成員是self,第二個(gè)成員是(id)class_getSuperclass(objc_getClass("Son")),實(shí)際上該函數(shù)會(huì)輸出Father。然后在Father類查找class方法,查找不到,最后在NSObject查到。此時(shí),內(nèi)部使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用,與[self class]調(diào)用相同,所以結(jié)果還是Son。
Method Resolution
Fast Forwarding
Normal Forwarding
我的理解:objc_super結(jié)構(gòu)體中receiver是son,superclass是father,而objc_msgSend(objc_super->receiver, @selector(class))內(nèi)部是使用objc_super中的receiver作為消息發(fā)送者,也就是son調(diào)用NSObject中的class方法,所以結(jié)果是son。

方法解析與消息轉(zhuǎn)發(fā)

[receiver message]調(diào)用方法時(shí),如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎么辦?一般情況下,程序在運(yùn)行時(shí)就會(huì)Crash掉,拋出unrecognized selector sent to…類似這樣的異常信息。但在拋出異常之前,還有三次機(jī)會(huì)按以下順序讓你拯救程序。
1.Method Resolution 即所屬類動(dòng)態(tài)方法解析
2.Fast Forwarding 即備援接受者
3.Normal Forwarding

  • Method Resolution首先Objective-C在運(yùn)行時(shí)調(diào)用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實(shí)現(xiàn)。如果你添加方法并返回YES,那系統(tǒng)在運(yùn)行時(shí)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程。
    舉一個(gè)簡單例子,定義一個(gè)類Message,它主要定義一個(gè)方法sendMessage,下面就是它的設(shè)計(jì)與實(shí)現(xiàn):
@interface Message : NSObject  
-(void)sendMessage:(NSString *)word;  
@end  
@implementation Message  
-(void)sendMessage:(NSString *)word  
{  
    NSLog(@"normal way : send message = %@", word);  
}  
@end  

如果我在viewDidLoad方法中創(chuàng)建Message對象并調(diào)用sendMessage方法:

-(void)viewDidLoad {  
    [super viewDidLoad];  
    Message *message = [Message new];  
    [message sendMessage:@"Sam Lau"];  
}  

控制臺(tái)會(huì)打印以下信息:

normal way : send message = Sam Lau

但現(xiàn)在我將原來sendMessage方法實(shí)現(xiàn)給注釋掉,覆蓋resolveInstanceMethod方法:

#pragma mark - Method Resolution  
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation  
+(BOOL)resolveInstanceMethod:(SEL)sel  
{  
    if (sel == @selector(sendMessage:)) {  
        class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {  
            NSLog(@"method resolution way : send message = %@", word);  
        }), "v@*");  
    }  
    return YES;  
} 

控制臺(tái)就會(huì)打印以下信息:

method resolution way : send message = Sam Lau

該方法主要原型為
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
方法名為給class添加方法,返回一個(gè)Bool類型返回值。第一個(gè)參數(shù)為需要添加方法的類,第二個(gè)參數(shù)為實(shí)例方法的名字也就是+(BOOL)resolveInstanceMethod:(SEL)sel方法中的參數(shù)sel,第三個(gè)參數(shù)為IMP類型的變量也就是函數(shù)的實(shí)現(xiàn)。需要傳一個(gè)C函數(shù),該函數(shù)至少要有兩個(gè)變量,一個(gè)是id self,一個(gè)是SEL _cmd,也可以傳字符串。
另一種寫法。

void dynamicAdditionMethodIMP(id self, SEL _cmd) {
    //實(shí)現(xiàn)
    NSLog(@"dynamicAdditionMethodIMP");
}
class_addMethod([self class], name, (IMP)dynamicAdditionMethodIMP, "v@:");

第四個(gè)參數(shù)傳入一個(gè)字符串"v@*,它表示方法的參數(shù)和返回值,詳情請參考Type Encodings。
如果resolveInstanceMethod方法返回NO,運(yùn)行時(shí)就跳轉(zhuǎn)到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)。

  • Fast Forwarding
    當(dāng)對象所屬類不能動(dòng)態(tài)添加方法后,runtime就會(huì)詢問當(dāng)前的接受者是否有其他對象可以處理這個(gè)未知的selector。方法就是:
- (id)forwardingTargetForSelector:(SEL)aSelector;

如果目標(biāo)對象實(shí)現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會(huì)在運(yùn)行時(shí)調(diào)用這個(gè)方法,只要這個(gè)方法返回的不是nil或self,也會(huì)重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對象來處理。否則,就會(huì)繼續(xù)Normal Fowarding。
繼續(xù)上面Message類的例子,將sendMessage和resolveInstanceMethod方法注釋掉,然后添加forwardingTargetForSelector方法的實(shí)現(xiàn):

#pragma mark - Fast Forwarding  
-(id)forwardingTargetForSelector:(SEL)aSelector  
{  
    if (aSelector == @selector(sendMessage:)) {  
        return [MessageForwarding new];  
    }  
    return nil;  
}  

此時(shí)還缺一個(gè)轉(zhuǎn)發(fā)消息的類MessageForwarding,這個(gè)類的設(shè)計(jì)與實(shí)現(xiàn)如下:

@interface MessageForwarding : NSObject  
-(void)sendMessage:(NSString *)word;  
@end  
@implementation MessageForwarding  
-(void)sendMessage:(NSString *)word  
{  
    NSLog(@"fast forwarding way : send message = %@", word);  
}  
@end  

此時(shí),控制臺(tái)會(huì)打印以下信息:

fast forwarding way : send message = Sam Lau

這里叫Fast,是因?yàn)檫@一步不會(huì)創(chuàng)建NSInvocation對象,但Normal Forwarding會(huì)創(chuàng)建它,所以相對于更快點(diǎn)。

  • Normal Forwarding
    如果沒有使用Fast Forwarding來消息轉(zhuǎn)發(fā),最后只有使用Normal Forwarding來進(jìn)行消息轉(zhuǎn)發(fā)。它首先調(diào)用methodSignatureForSelector:方法來獲取函數(shù)的參數(shù)和返回值,如果返回為nil,程序會(huì)Crash掉,并拋出unrecognized selector sent to instance異常信息。如果返回一個(gè)函數(shù)簽名,系統(tǒng)就會(huì)創(chuàng)建一個(gè)NSInvocation對象并調(diào)用-forwardInvocation:方法。
    繼續(xù)前面的例子,將forwardingTargetForSelector方法注釋掉,添加methodSignatureForSelector和forwardInvocation方法的實(shí)現(xiàn):
#pragma mark - Normal Forwarding  
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
{  
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];  
    if (!methodSignature) {  
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];  
    }  
    return methodSignature;  
}  
-(void)forwardInvocation:(NSInvocation *)anInvocation  
{  
    MessageForwarding *messageForwarding = [MessageForwarding new];  
    if ([messageForwarding respondsToSelector:anInvocation.selector]) {  
        [anInvocation invokeWithTarget:messageForwarding];  
    }  
}  
三種方法的選擇

Runtime提供三種方式來將原來的方法實(shí)現(xiàn)代替掉,那該怎樣選擇它們呢?

  • Method Resolution
    由于Method Resolution不能像消息轉(zhuǎn)發(fā)那樣可以交給其他對象來處理,所以只適用于在原來的類中代替掉。
  • Fast Forwarding
    它可以將消息處理轉(zhuǎn)發(fā)給其他對象,使用范圍更廣,不只是限于原來的對象。
  • Normal Forwarding
    它跟Fast Forwarding一樣可以消息轉(zhuǎn)發(fā),但它能通過NSInvocation對象獲取更多消息發(fā)送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects

如果我們想對系統(tǒng)的類添加方法時(shí),可以使用擴(kuò)展,但是添加屬性時(shí),只能使用繼承。如果不想使用繼承,則可以用runtime來關(guān)聯(lián)對象,這與我們自定義類的添加屬性不同。本質(zhì)上是使用類別進(jìn)行擴(kuò)展,通過添加get方法和set方法從而在使用時(shí)可以使用點(diǎn)方法使用。與普通使用方法一致。
同時(shí)當(dāng)使用Category對某個(gè)類進(jìn)行擴(kuò)展時(shí),有時(shí)需要存儲(chǔ)屬性,Category是不支持的,這時(shí)需要使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個(gè)C語言API來向?qū)ο筇砑印@取和刪除關(guān)聯(lián)值:

  • void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
  • id objc_getAssociatedObject (id object, const void *key )
  • void objc_removeAssociatedObjects (id object )

其中objc_AssociationPolicy是個(gè)枚舉類型,它可以指定Objc內(nèi)存管理的引用計(jì)數(shù)機(jī)制。

  typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {  
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */  
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object. 
                                            *   The association is made atomically. */  
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied. 
                                            *   The association is made atomically. */  
};  

下面有個(gè)關(guān)于NSObject+AssociatedObject Category添加屬性associatedObject的示例代碼:
NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)  
@property (strong, nonatomic) id associatedObject;  
@end 

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)  
- (void)setAssociatedObject:(id)associatedObject  
{  
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  
}  
- (id)associatedObject  
{  
    return objc_getAssociatedObject(self, _cmd);  
}  
@end 

Associated Objects的key要求是唯一并且是常量,而SEL是滿足這個(gè)要求的,所以上面的采用隱藏參數(shù)_cmd作為key。
一個(gè)給scrollview添加refreshView的實(shí)際用例:

@interface UIScrollView (Refresh)  
@property (nonatomic) RefreshView * refreshView;  
@end  
#import <objc/runtime.h>  
static char kRefreshView;  
@implementation UIScrollView (Refresh)  
@dynamic refreshView;  
  
- (void)setRefreshView:(RefreshView *)aView {  
      
    objc_setAssociatedObject(self, &kRefreshView, aView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  
}  
  
  
- (RefreshView *)refreshView {  
      
    return objc_getAssociatedObject(self, &kRefreshView);  
}  
  
@end  
GetClass

得到一個(gè)實(shí)例的類。代碼如下:

#import "ViewController.h"  
#import <objc/runtime.h>  
#import "Person.h"  
  
@interface ViewController ()  
  
@end  
  
@implementation ViewController  
  
- (void)viewDidLoad {  
    [super viewDidLoad];  
    // Do any additional setup after loading the view, typically from a nib.  
      
      
    Person * p1 = [[Person alloc] init];  
      
    Class c1 = object_getClass(p1);  
    NSLog(@"%@", c1);  
    Person * p2 = [[[c1 class] alloc] init];  
    NSLog(@"%@", p2.name);  
      
} 
isKindOfClass和isMemberOfClass

先看看isKindOfClass和isMemberOfClass在Object.mm中的實(shí)現(xiàn):

- (BOOL)isKindOf:aClass
{
     Class cls;
     for (cls = isa; cls; cls = cls->superclass)
          if (cls == (Class)aClass)
               return YES;
     return NO;
}

- (BOOL)isMemberOf:aClass
{
     return isa == (Class)aClass;
}

可以看到isKindOfClass是取自身指針isa與參數(shù)指針做對比,如果相等返回YES,如果不等則取其superClass指針,直到superClass為空為止,循環(huán)結(jié)束返回空。

int main(int argc, const char * argv[]) {
     @autoreleasepool {
          BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
          BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
          BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
          BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
          NSLog(@"%d %d %d %d", res1, res2, res3, res4);
     }
     return 0;
}

//輸出
2014-11-05 14:45:08.474 Test[9412:721945] 1 0 0 0

res1中NSObject的isa指針指向NSOject的meta-class,繼續(xù)循環(huán),meta-class的superClass是NSObject所以為ture;
res3中Sark的isa指針指向sark的meta-class,接下來指向NSObject的meta-class,再接下來指向NSObject;
res2中NSObject的isa指針指向NSOject的meta-class,與NSObject不一致;
res4中Sark的isa指針指向sark的meta-class,與Sark不一致。
注意上面是類與類相比。下面是類的實(shí)例與類相比:

Person *person = [[Person alloc] init];  
Teacher *teacher = [[Teacher alloc] init];  
  
//YES   
if ([teacher isKindOfClass:[Teacher class]]) {  
    NSLog(@"teacher 是 Teacher類或Teacher的子類");  
}  
//YES   
if ([teacher isKindOfClass:[Person class]]) {  
    NSLog(@"teacher 是 Person類或Person的子類");  
}  
//YES   
if ([teacher isKindOfClass:[NSObject class]]) {  
    NSLog(@"teacher 是 NSObject類或NSObject的子類");  
} 

// YES   
if ( [teacher respondsToSelector: @selector( setName: )] == YES ) {  
    NSLog(@"teacher responds to setSize: method" );  
}  
  
// NO   
if ( [teacher respondsToSelector: @selector( abcde )] == YES ) {  
    NSLog(@"teacher responds to nonExistant method" );  
}  
  
// YES   
if ( [Teacher respondsToSelector: @selector( alloc )] == YES ) {  
    NSLog(@"teacher class responds to alloc method\n" );  
}  
Method Swizzling

Method Swizzling就是在運(yùn)行時(shí)將一個(gè)方法的實(shí)現(xiàn)代替為另一個(gè)方法的實(shí)現(xiàn)。由于Foundation等框架都是閉源的,我們沒有辦法直接修改代碼,通常情況下只能通過繼承,類別,關(guān)聯(lián)屬性等手段添加屬性和實(shí)例方法,較為繁瑣。而Method Swizzling可以通過將一個(gè)方法的實(shí)現(xiàn)代替另一個(gè)方法的實(shí)現(xiàn),從而達(dá)到修改閉源代碼的目的。
例如一個(gè)想要統(tǒng)計(jì)每個(gè)頁面出現(xiàn)的次數(shù),如果在所有頁面的viewWillAppear中都添加統(tǒng)計(jì)代碼,則太過繁瑣。那我們使用一個(gè)類別,并自定義一個(gè)方法替代系統(tǒng)的viewWillAppear,在其中做統(tǒng)計(jì),并在統(tǒng)計(jì)結(jié)束后調(diào)用系統(tǒng)的viewWillAppear即可。

@interface UIViewController (MyUIViewController)

@end

@implementation UIViewController(MyUIViewController)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        SEL originalSelector = @selector(viewWillAppear:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        
        SEL exchangeSelector = @selector(myViewWillAppear:);
        Method exchangeMethod = class_getInstanceMethod([self class], exchangeSelector);
        
        method_exchangeImplementations(originalMethod, exchangeMethod);
    });
}

- (void)myViewWillAppear:(BOOL)animated {
    //這里實(shí)際上是調(diào)用系統(tǒng)的viewWillAppear,不會(huì)遞歸調(diào)用。
    [self myViewWillAppear:animated];
    NSLog(@"MyViewWillAppear %@", [self class]);
}

使用load實(shí)現(xiàn)預(yù)加載。使用GCD的dispatch_once_t保證只會(huì)交換一次不會(huì)重復(fù)交換。使用method_exchangeImplementations來實(shí)現(xiàn)交換。
將上述代碼放入工程中就可以打印所有viewcontroller的加載情況。

Aspect-Oriented Programming(AOP)

類似記錄日志、身份驗(yàn)證、緩存等事務(wù)非常瑣碎,與業(yè)務(wù)邏輯無關(guān),很多地方都有,又很難抽象出一個(gè)模塊,這種程序設(shè)計(jì)問題,業(yè)界給它們起了一個(gè)名字叫橫向關(guān)注點(diǎn)(Cross-cutting concern),AOP作用就是分離橫向關(guān)注點(diǎn)(Cross-cutting concern)來提高模塊復(fù)用性,它可以在既有的代碼添加一些額外的行為(記錄日志、身份驗(yàn)證、緩存)而無需修改代碼。

runtime如何實(shí)現(xiàn)weak置為nil

runtime對注冊的類會(huì)進(jìn)行布局,對于weak修飾的對象會(huì)放入一個(gè)hash表中,用weak指向的對象內(nèi)存地址作為key。當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc,假如weak指向的對象內(nèi)存地址是a,那么就會(huì)以a為鍵在這個(gè)weak表中搜索,找到所有以a為鍵的weak對象,從而設(shè)置為nil。

NSString *name = [[NSString alloc] initWithString: @"Jiaming Chen"];
__weak NSString *weakStr = name;

當(dāng)為weakStr這一weak類型的對象賦值時(shí),編譯器會(huì)根據(jù)name的地址為key去查找weak哈希表,該表項(xiàng)的值為一個(gè)數(shù)組,將weakStr對象的地址加入到數(shù)組中,當(dāng)name變量超出變量作用域或引用計(jì)數(shù)為0時(shí),會(huì)執(zhí)行dealloc函數(shù),在執(zhí)行該函數(shù)時(shí),編譯器會(huì)以name變量的地址去查找weak哈希表的值,并將數(shù)組里所有 weak對象全部賦值為nil。

總結(jié)

雖然在平時(shí)項(xiàng)目不是經(jīng)常用到Objective-C的Runtime特性,但當(dāng)你閱讀一些iOS開源項(xiàng)目時(shí),你就會(huì)發(fā)現(xiàn)很多時(shí)候都會(huì)用到。所以深入理解Objective-C的Runtime數(shù)據(jù)結(jié)構(gòu)、消息轉(zhuǎn)發(fā)機(jī)制有助于你更容易地閱讀和學(xué)習(xí)開源項(xiàng)目。

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