Objective-C的Runtime機制

蘋果官方文檔查找地址:https://developer.apple.com/library/mac/navigation/
Runtime官方文檔https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

簡介

Objective-C是基于C加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機制的動態(tài)語言泪喊,除編譯器之外祖很,還需用Runtime系統(tǒng)來動態(tài)創(chuàng)建類和對象乘碑,進行消息發(fā)送和轉(zhuǎn)發(fā)。Runtime 又叫運行時奴烙,是一套底層的 C 語言 API,其為 iOS 內(nèi)部的核心之一,我們平時編寫的 OC 代碼,底層都是基于它來實現(xiàn)的努酸。比如:

OC中方法的調(diào)用:
[receiver message];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
// 如果其還有參數(shù)比如:
[receiver message:(id)arg...];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
id objc_msgSend ( id self, SEL op, ... );

以上你可能看不出它的價值,但是我們需要了解的是 Objective-C 是一門動態(tài)語言罩缴,它會將一些工作放在代碼運行時才處理而并非編譯時蚊逢。也就是說,有很多類和成員變量在我們編譯的時是不知道的箫章,而在運行時,我們所編寫的代碼會轉(zhuǎn)換成完整的確定的代碼運行镜会。

Runtime 的作用

Objc 在三種層面上與 Runtime 系統(tǒng)進行交互:

1.通過 Objective-C 源代碼
2.通過 Foundation 框架的 NSObject 類定義的方法
3.通過對 Runtime 庫函數(shù)的直接調(diào)用

Objective-C 源代碼

多數(shù)情況我們只需要編寫 OC 代碼即可檬寂,編譯時Runtime 系統(tǒng)自動在幕后搞定一切,還記得簡介中如果我們調(diào)用方法戳表,編譯器會將 OC 代碼轉(zhuǎn)換成運行時代碼桶至,在運行時確定數(shù)據(jù)結(jié)構(gòu)和函數(shù)。

通過 Foundation 框架的 NSObject 類定義的方法

Cocoa 程序中絕大部分類都是 NSObject 類的子類匾旭,所以都繼承了 NSObject 的行為镣屹。(NSProxy 類時個例外,它是個抽象超類)

一些情況下价涝,NSObject 類僅僅定義了完成某件事情的模板女蜈,并沒有提供所需要的代碼。例如 -description 方法色瘩,該方法返回類內(nèi)容的字符串表示伪窖,該方法主要用來調(diào)試程序。NSObject 類并不知道子類的內(nèi)容居兆,所以它只是返回類的名字和對象的地址覆山,NSObject 的子類可以重新實現(xiàn)。

還有一些 NSObject 的方法可以從 Runtime 系統(tǒng)中獲取信息泥栖,允許對象進行自我檢查簇宽。例如:

  • -class方法返回對象的類勋篓;
  • -isKindOfClass: 和 -isMemberOfClass: 方法檢查對象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者當前類的成員變量);
  • -respondsToSelector: 檢查對象能否響應指定的消息魏割;
  • -conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法生巡;
  • -methodForSelector: 返回指定方法實現(xiàn)的地址。
通過對 Runtime 庫函數(shù)的直接調(diào)用

Runtime 系統(tǒng)是具有公共接口的動態(tài)共享庫见妒。頭文件存放于/usr/include/objc目錄下孤荣,這意味著我們使用時只需要引入objc/Runtime.h頭文件即可。

許多函數(shù)可以讓你使用純 C 代碼來實現(xiàn) Objc 中同樣的功能须揣。除非是寫一些 Objc 與其他語言的橋接或是底層的 debug 工作盐股,你在寫 Objc 代碼時一般不會用到這些 C 語言函數(shù)。對于公共接口都有哪些請參考蘋果官方的 API 文檔耻卡。

一些 Runtime 的術(shù)語的數(shù)據(jù)結(jié)構(gòu)

要想全面了解 Runtime 機制疯汁,我們必須先了解 Runtime 的一些術(shù)語,他們都對應著數(shù)據(jù)結(jié)構(gòu)卵酪。

SEL

它是selector在 Objc 中的表示(Swift 中是 Selector 類)幌蚊。selector 是方法選擇器,其實作用就和名字一樣溃卡,日常生活中溢豆,我們通過人名辨別誰是誰,注意 Objc 在相同的類中不會有命名相同的兩個方法瘸羡。selector 對方法名進行包裝漩仙,以便找到對應的方法實現(xiàn)。它的數(shù)據(jù)結(jié)構(gòu)是:

/// An opaque type that represents a method selector.(表示方法選擇器的不透明類型)
typedef struct objc_selector *SEL;

兩個類之間犹赖,不管它們是父類與子類的關(guān)系队他,還是之間沒有這種關(guān)系,只要方法名相同峻村,那么方法的SEL就是一樣的麸折。每一個方法都對應著一個SEL。所以在 Objective-C同一個類(及類的繼承體系)中粘昨,不能存在2個同名的方法垢啼,即使參數(shù)類型不同也不行。

不同類的實例對象執(zhí)行相同的selector時雾棺,會在各自的方法列表中去根據(jù)selector去尋找自己對應的IMP膊夹。

工程中的所有的SEL組成一個Set集合,Set的特點就是唯一捌浩,因此SEL是唯一的放刨。因此,如果我們想到這個方法集合中查找某個方法時尸饺,只需要去 找到這個方法對應的SEL就行了进统,SEL實際上就是根據(jù)方法名hash化了的一個字符串助币,而對于字符串的比較僅僅需要比較他們的地址就可以了。
我們可以通過以下方法獲取SEL

1.SEL sel_registerName(const char *str)//向runtime system注冊一個方法名螟碎。如果方法名已經(jīng)注冊眉菱,則放回已經(jīng)注冊的SEL
2.SEL sel_getUid(const char *str)//同上
3.@selector(<#selector#>)//oc編譯器提供的
4.SEL NSSelectorFromString(NSString *aSelectorName)//OC字符串轉(zhuǎn)化
5.SEL method_getName ( Method m );//根據(jù)Method結(jié)構(gòu)體獲取
等等
id

objc_msgSend第一個參數(shù)的數(shù)據(jù)類型id,id 是一個參數(shù)類型,它是指向某個類的實例的指針掉分,id是通用類型指針俭缓,能夠表示任何對象

typedef struct objc_object *id;
struct objc_object { Class isa; };

id其實就是一個指向objc_object結(jié)構(gòu)體指針,它包含一個Class isa成員酥郭,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類.

注意:根據(jù)Apple的官方文檔Key-Value Observing Implementation Details提及华坦,key-value observing是使用isa-swizzling的技術(shù)實現(xiàn)的,isa指針在運行時被修改不从,指向一個中間類而不是真正的類惜姐。所以,你不應該使用isa指針來確定類的關(guān)系椿息,而是使用class方法來確定實例對象的類歹袁。

Class
typedef struct objc_class *Class;

Class 其實是指向 objc_class 結(jié)構(gòu)體的指針。objc_class 的數(shù)據(jù)結(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;

從 objc_class 可以看到寝优,一個運行時類中關(guān)聯(lián)了它的父類指針条舔、類名、成員變量倡勇、方法逞刷、緩存以及附屬的協(xié)議。

其中 objc_ivar_list 和 objc_method_list 分別是成員變量列表和方法列表:

// 成員變量列表
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;
}                                                            OBJC2_UNAVAILABLE;
// 方法列表
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;
}

由此可見妻熊,我們可以動態(tài)修改 *methodList 的值來添加成員方法,這也是 Category 實現(xiàn)的原理仑最,同樣解釋了 Category 不能添加屬性的原因扔役。這里可以參考下美團技術(shù)團隊的文章:深入理解 Objective-C: Category

成員變量和屬性的區(qū)別:@property聲明的屬性默認會生成一個“_”類型的成員變量警医,同時也會生成setter/getter方法亿胸。

objc_ivar_list 結(jié)構(gòu)體用來存儲成員變量的鏈表,而 objc_ivar 則是存儲了單個成員變量的信息预皇;同理侈玄,objc_method_list 結(jié)構(gòu)體存儲著方法數(shù)組的鏈表,而單個方法的信息則由 objc_method 結(jié)構(gòu)體存儲吟温。

值得注意的時序仙,objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象鲁豪。為了處理類和對象的關(guān)系潘悼,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西律秃,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的元數(shù)據(jù)治唤。

我們所熟悉的類方法棒动,就源自于 Meta Class。我們可以理解為類方法就是類對象的實例方法宾添。每個類僅有一個類對象船惨,而每個類對象僅有一個與之相關(guān)的元類。

當你發(fā)出一個類似 [NSObject alloc]類方法的消息時缕陕,實際上粱锐,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實例榄檬,而這個元類同時也是一個根元類(Root Meta Class)的實例卜范。所有元類的 isa 指針最終都指向根元類。

所以當 [NSObject alloc] 這條消息發(fā)送給類對象的時候鹿榜,運行時代碼 objc_msgSend() 會去它元類中查找能夠響應消息的方法實現(xiàn)海雪,如果找到了,就會對這個類對象執(zhí)行方法調(diào)用舱殿。

87C124E5-491E-4D0C-A386-FE5161537C98.png

上圖實現(xiàn)是 super_class 指針奥裸,虛線時 isa 指針。有幾個關(guān)鍵點需要解釋以下:

  1. Root class (class)其實就是NSObject沪袭,NSObject是沒有超類的湾宙,所以Root class(class)的superclass指向nil。
  1. 每個Class都有一個isa指針指向唯一的Meta class冈绊。
  2. Root class(meta)的superclass指向Root class(class)侠鳄,也就是NSObject,形成一個回路死宣。
  3. 每個Meta class根元類的isa指針都指向Root class (meta)伟恶。

最后 objc_class 中還有一個 objc_cache ,緩存毅该,cache用來緩存經(jīng)常訪問的方法博秫,它指向objc_cache結(jié)構(gòu)體,它的作用很重要眶掌,后面會提到挡育。

IMP

IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的首地址朴爬。當你向某個對象發(fā)送一條信息即寒,可以由這個函數(shù)指針來指定方法的實現(xiàn),它最終就會執(zhí)行那段代碼,這樣可以繞開消息傳遞階段而去執(zhí)行另一個方法實現(xiàn)蒿叠。其定義如下:

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

第一個參數(shù)是指向self的指針(如果是實例方法明垢,則是類實例的內(nèi)存地址;如果是類方法市咽,則是指向元類的指針)痊银,第二個參數(shù)是方法選擇器(selector),接下來是方法的實際參數(shù)列表施绎。

IMP原理:SEL是方法名的唯一標識溯革,相同的方法名如果要對應不同的實現(xiàn)還需關(guān)聯(lián)不同的實現(xiàn)地址,IMP就是為此而生的谷醉。當我們知道一個IMP時候就會知道他對應的SEL名致稀。

IMP的作用:
我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針 了。
通過取得IMP俱尼,我們可以跳過Runtime的消息傳遞機制抖单,直接執(zhí)行IMP指向的函數(shù)實現(xiàn),這樣省去了Runtime消息傳遞過程中所做的一系列查找操作遇八,會比直接向?qū)ο蟀l(fā)送消息高效一些矛绘。

獲取IMP的函數(shù)如下

IMP imp_implementationWithBlock(id block)//根據(jù)代碼塊獲取IMP,其實就是代碼塊與IMP關(guān)聯(lián)
IMP method_getImplementation(Method m) //根據(jù)Method獲取IMP
[[objc Class] instanceMethodForSelector:SEL]//根據(jù)OC方式獲取IMP
//待續(xù)

當我們獲取一個方法的IMP時候可以直接調(diào)用IMP

IMP imp = method_getImplementation(Method m);
id objc = imp(id,SEL,argument);//objc用來保存方法的返回值刃永,id表示調(diào)用這個方法的對象货矮,SEL是Method的選擇器,argument是方法的參數(shù)斯够。

注意:
1.對于一個對象方法其實他的第一個參數(shù)是對象本身,第二個參數(shù)開始被存放在IMP的…里面囚玫。所以對于不屬于任何對象的IMP我們要直接調(diào)用的參數(shù)是從IMP的第一參數(shù)id算起,忽略SEL傳nil读规,然后接著后面的第二第三個參數(shù)抓督。對于對象方法(包括類方法,類方法對象其實就是元類)則我們可以對id和sel直接傳nil束亏,忽略他然后直接調(diào)用該對象方法達到像使用C函數(shù)一樣使用對象方法本昏。
2.在OC里面對象方法是至少兩個參數(shù)的C函數(shù),兩個參數(shù)是背隱藏起來的,即對象的自身self和方法名_cmd,我們在開發(fā)中常用到self枪汪,但是很少用到_cmd。
3.用imp掉用方法時候參數(shù)一點要傳全,沒有值的可以傳nil怔昨,否則會出現(xiàn)錯誤或者一些想不到的結(jié)果雀久。

Method

Method定義如下:它主要是用于描述類里面的方法,代表類中某個方法的類型

typedef struct objc_method *Method;

objc_method結(jié)構(gòu)體定義如下:

struct objc_method {
    SEL method_name                           OBJC2_UNAVAILABLE;//方法名
    char *method_types                        OBJC2_UNAVAILABLE;//參數(shù)返回值字符串描述
    IMP method_imp                            OBJC2_UNAVAILABLE;//方法的實現(xiàn)
}
objc_method 存儲了方法名趁舀,方法類型和方法實現(xiàn):

方法名 method_name 類型為 SEL
方法類型 method_types 是個 char 指針赖捌,存儲方法的參數(shù)類型和返回值類型
method_imp 指向了方法的實現(xiàn),本質(zhì)是一個函數(shù)指針

我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP,實際上相當于在SEL和IMP之間作了一個映射越庇。有了Method罩锐,SEL可以通過Method找到對應的IMP,從而調(diào)用方法的實現(xiàn)代碼卤唉。

Method操作函數(shù)如下

方法操作主要有以下函數(shù):

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );

// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

objc_method_description

objc_method_description結(jié)構(gòu)體用來描述Method的一些信息涩惑,這樣我么就可以直接讀出來

struct objc_method_description {
    SEL name;               /**< The name of the method 方法名*/ 
    char *types;            /**< The types of the method arguments 參數(shù)類型字符串*/
};

獲取函數(shù)

// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
Ivar

Ivar表示類中的實例變量,在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其實就是一個指向objc_ivar結(jié)構(gòu)體指針桑驱,它包含了變量名(ivar_name)竭恬、變量類型(ivar_type)等信息。

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其實就是一個存儲Method的鏈表胯杭,主要是為了優(yōu)化方法調(diào)用的性能。當對象receiver調(diào)用方法message時秦忿,首先根據(jù)對象receiver的isa指針查找到它對應的類,然后在類的methodLists中搜索方法,如果沒有找到盒揉,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法骑歹。如果沒有找到预烙,有可能消息轉(zhuǎn)發(fā),也可能忽略它道媚。但這樣查找方式效率太低扁掸,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%最域。所以使用Cache來緩存經(jīng)常調(diào)用的方法谴分,當調(diào)用方法時,優(yōu)先在Cache查找镀脂,如果沒有找到牺蹄,再到methodLists查找。

Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個更常用

可以通過class_copyPropertyList 和 protocol_copyPropertyList 方法獲取類和協(xié)議中的屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

注意:
返回的是屬性列表薄翅,列表中每個元素都是一個 objc_property_t 指針

#import <Foundation/Foundation.h>

@interface Person : NSObject

/** 姓名 */
@property (strong, nonatomic) NSString *name;

/** age */
@property (assign, nonatomic) int age;

/** weight */
@property (assign, nonatomic) double weight;

@end

以上是一個 Person 類沙兰,有3個屬性。讓我們用上述方法獲取類的運行時屬性翘魄。

    unsigned int outCount = 0;

    objc_property_t *properties = class_copyPropertyList([Person class], &outCount);

    NSLog(@"%d", outCount);

    for (NSInteger i = 0; i < outCount; i++) {
        NSString *name = @(property_getName(properties[i]));
        NSString *attributes = @(property_getAttributes(properties[i]));
        NSLog(@"%@--------%@", name, attributes);
    }
打印結(jié)果如下:

2017-02-13 16:54:08.684 Runtime體驗[7435:176645] 3
2017-02-13 16:54:08.684 Runtime體驗[7435:176645] name--------T@"NSString",&,N,V_name
2017-02-13 16:54:08.684 Runtime體驗[7435:176645] age--------Ti,N,V_age
2017-02-13 16:54:08.684 Runtime體驗[7435:176645] weight--------Td,N,V_weight

property_getName 用來查找屬性的名稱鼎天,返回 c 字符串。property_getAttributes 函數(shù)挖掘?qū)傩缘恼鎸嵜Q和 @encode 類型暑竟,返回 c 字符串斋射。

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

class_getProperty 和 protocol_getProperty 通過給出屬性名在類和協(xié)議中獲得屬性的引用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子罗岖,更是在濱河造成了極大的恐慌涧至,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桑包,死亡現(xiàn)場離奇詭異南蓬,居然都是意外死亡,警方通過查閱死者的電腦和手機捡多,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門蓖康,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人垒手,你說我怎么就攤上這事蒜焊。” “怎么了科贬?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵泳梆,是天一觀的道長。 經(jīng)常有香客問我榜掌,道長优妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任憎账,我火速辦了婚禮套硼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胞皱。我一直安慰自己邪意,他們只是感情好,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布反砌。 她就那樣靜靜地躺著雾鬼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宴树。 梳的紋絲不亂的頭發(fā)上策菜,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機與錄音酒贬,去河邊找鬼又憨。 笑死,一個胖子當著我的面吹牛锭吨,可吹牛的內(nèi)容都是我干的竟块。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼耐齐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起埠况,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤耸携,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辕翰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夺衍,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年喜命,在試婚紗的時候發(fā)現(xiàn)自己被綠了沟沙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡壁榕,死狀恐怖矛紫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牌里,我是刑警寧澤颊咬,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站牡辽,受9級特大地震影響喳篇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜态辛,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一麸澜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奏黑,春花似錦炊邦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至以故,卻和暖如春蜗细,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怒详。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工炉媒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昆烁。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓吊骤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親静尼。 傳聞我的和親對象是個殘疾皇子白粉,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

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

  • 我們常常會聽說 Objective-C 是一門動態(tài)語言传泊,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,199評論 0 7
  • 1. 什么是Runtime機制 Runtime[1]是一套比較底層的C語言庫, 由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成鸭巴,包...
    一只呱呱閱讀 865評論 0 1
  • 本文詳細整理了 Cocoa 的 Runtime 系統(tǒng)的知識眷细,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 806評論 0 4
  • 文中的實驗代碼我放在了這個項目中鹃祖。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 924評論 0 6
  • 本文轉(zhuǎn)載自:http://southpeak.github.io/2014/10/25/objective-c-r...
    idiot_lin閱讀 937評論 0 4