Runtime知識點

Runtime是什么

Runtime 又叫運行時仔燕,是一套底層的 C 語言 API,其為 iOS 內(nèi)部的核心之一顺囊,我們平時編寫的 OC 代碼,底層都是基于它來實現(xiàn)的蕉拢。比如:

[receiver message];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
// 如果其還有參數(shù)比如:
[receiver message:(id)arg...];
// 底層運行時會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)

為什么需要Runtime

Objective-C 是一門動態(tài)語言特碳,它會將一些工作放在代碼運行時才處理而并非編譯時。也就是說企量,有很多類和成員變量在我們編譯的時是不知道的测萎,而在運行時,我們所編寫的代碼會轉(zhuǎn)換成完整的確定的代碼運行届巩。

因此硅瞧,編譯器是不夠的,我們還需要一個運行時系統(tǒng)(Runtime system)來處理編譯后的代碼恕汇。

Runtime 基本是用 C 和匯編寫的腕唧,由此可見蘋果為了動態(tài)系統(tǒng)的高效而做出的努力或辖。蘋果和 GNU 各自維護一個開源的 Runtime 版本,這兩個版本之間都在努力保持一致枣接。

Runtime 的作用

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

1.通過 Objective-C 源代碼

只需要編寫 OC 代碼颂暇,Runtime 系統(tǒng)自動在幕后搞定一切,調(diào)用方法但惶,編譯器會將 OC 代碼轉(zhuǎn)換成運行時代碼耳鸯,在運行時確定數(shù)據(jù)結(jié)構(gòu)和函數(shù)。

2.通過 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: 方法檢查對象是否存在于指定的類的繼承體系中(是否是其子類或者父類和是否是當(dāng)前類的成員變量)瓣蛀;
  • respondsToSelector: 檢查對象能否響應(yīng)指定的消息;
  • conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法雷厂;
  • methodForSelector: 返回指定方法實現(xiàn)的地址惋增。

3.通過對 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ù)稽亏。
Runtime的相關(guān)術(shù)語

1). SEL

它是selector在 Objc 中的表示(Swift 中是 Selector 類)。selector 是方法選擇器缕题,其實作用就和名字一樣截歉,日常生活中,我們通過人名辨別誰是誰烟零,注意 Objc 在相同的類中不會有命名相同的兩個方法瘪松。selector 對方法名進行包裝咸作,以便找到對應(yīng)的方法實現(xiàn)。它的數(shù)據(jù)結(jié)構(gòu)是:
typedef struct objc_selector *SEL;
我們可以看出它是個映射到方法的 C 字符串宵睦,你可以通過 Objc 編譯器器命令@selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來獲取一個 SEL 類型的方法選擇器记罚。
注意:不同類中相同名字的方法所對應(yīng)的 selector 是相同的,由于變量的類型不同壳嚎,所以不會導(dǎo)致它們調(diào)用方法實現(xiàn)混亂桐智。

2). id

id 是一個參數(shù)類型,它是指向某個類的實例的指針烟馅。定義如下:

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

以上定義酵使,看到 objc_object 結(jié)構(gòu)體包含一個 isa 指針,根據(jù) isa 指針就可以找到對象所屬的類焙糟。
注意:isa 指針在代碼運行時并不總指向?qū)嵗龑ο笏鶎俚念愋涂谟妫圆荒芤揽克鼇泶_定類型,要想確定類型還是需要用對象的 -class 方法穿撮。PS:KVO 的實現(xiàn)機理就是將被觀察對象的 isa 指針指向一個中間類而不是真實類型缺脉。

3). 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 不能添加屬性的原因逛钻。

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)的元類淋纲。

當(dāng)你發(fā)出一個類似 NSObject alloc 的消息時,實際上院究,這個消息被發(fā)送給了一個類對象(Class Object)洽瞬,這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類(Root Meta Class)的實例业汰。所有元類的 isa 指針最終都指向根元類伙窃。

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

最后 objc_class 中還有一個 objc_cache 放祟,緩存鳍怨,它的作用很重要,后面會提到跪妥。

4). 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;
}

objc_method 存儲了方法名鞋喇,方法類型和方法實現(xiàn):

方法名類型為 SEL
方法類型 method_types 是個 char 指針,存儲方法的參數(shù)類型和返回值類型
method_imp 指向了方法的實現(xiàn)眉撵,本質(zhì)是一個函數(shù)指針
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
}

其中 ivar_offset 是基地址偏移字節(jié)

5). IMP

IMP在objc.h中的定義是:

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

它就是一個函數(shù)指針,這是由編譯器生成的纽疟。當(dāng)你發(fā)起一個 ObjC 消息之后罐韩,最終它會執(zhí)行的那段代碼,就是由這個函數(shù)指針指定的仰挣。而 IMP 這個函數(shù)指針就指向了這個方法的實現(xiàn)伴逸。

如果得到了執(zhí)行某個實例某個方法的入口缠沈,我們就可以繞開消息傳遞階段膘壶,直接執(zhí)行方法,這在后面 Cache 中會提到洲愤。

你會發(fā)現(xiàn) IMP 指向的方法與 objc_msgSend 函數(shù)類型相同颓芭,參數(shù)都包含 id 和 SEL 類型。每個方法名都對應(yīng)一個 SEL 類型的方法選擇器柬赐,而每個實例對象中的 SEL 對應(yīng)的方法實現(xiàn)肯定是唯一的亡问,通過一組 id和 SEL 參數(shù)就能確定唯一的方法實現(xiàn)地址。

而一個確定的方法也只有唯一的一組 id 和 SEL 參數(shù)。

6). Cache

Cache 定義如下:

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

Cache 為方法調(diào)用的性能進行優(yōu)化州藕,每當(dāng)實例對象接收到一個消息時束世,它不會直接在 isa 指針指向的類的方法列表中遍歷查找能夠響應(yīng)的方法,因為每次都要查找效率太低了床玻,而是優(yōu)先在 Cache 中查找毁涉。

Runtime 系統(tǒng)會把被調(diào)用的方法存到 Cache 中,如果一個方法被調(diào)用锈死,那么它有可能今后還會被調(diào)用贫堰,下次查找的時候就會效率更高。就像計算機組成原理中 CPU 繞過主存先訪問 Cache 一樣待牵。

7). 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é)果如下:

test[2321:451525] 3
test[2321:451525] name--------T@"NSString",&,N,V_name
test[2321:451525] age--------Ti,N,V_age
test[2321:451525] 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é)議中獲得屬性的引用壮不。
Runtime與消息

一些 Runtime 術(shù)語講完了汗盘,接下來就要說到消息了。體會蘋果官方文檔中的 messages aren’t bound to method implementations until Runtime询一。消息直到運行時才會與方法實現(xiàn)進行綁定隐孽。
這里要清楚一點,objc_msgSend 方法看清來好像返回了數(shù)據(jù)健蕊,其實objc_msgSend 從不返回數(shù)據(jù)菱阵,而是你的方法在運行時實現(xiàn)被調(diào)用后才會返回數(shù)據(jù)。下面詳細敘述消息發(fā)送的步驟:

首先檢測這個 selector 是不是要忽略缩功。比如 Mac OS X 開發(fā)晴及,有了垃圾回收就不理會 retain,release 這些函數(shù)嫡锌。
檢測這個 selector 的 target 是不是 nil虑稼,Objc 允許我們對一個 nil 對象執(zhí)行任何方法不會 Crash,因為運行時會被忽略掉势木。
如果上面兩步都通過了蛛倦,那么就開始查找這個類的實現(xiàn) IMP,先從 cache 里查找啦桌,如果找到了就運行對應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼溯壶。
如果 cache 找不到就找類的方法列表中是否有對應(yīng)的方法。
如果類的方法列表中找不到就到父類的方法列表中查找,一直找到 NSObject 類為止且改。
如果還找不到验烧,就要開始進入動態(tài)方法解析了,后面會提到又跛。
在消息的傳遞中噪窘,編譯器會根據(jù)情況在
objc_msgSend , objc_msgSend_stret 效扫, objc_msgSendSuper 倔监, objc_msgSendSuper_stret這四個方法中選擇一個調(diào)用。如果消息是傳遞給父類菌仁,那么會調(diào)用名字帶有 Super 的函數(shù)浩习,如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時,會調(diào)用名字帶有 stret 的函數(shù)济丘。
方法中的隱藏參數(shù)

疑問:
我們經(jīng)常用到關(guān)鍵字 self 谱秽,但是 self 是如何獲取當(dāng)前方法的對象呢?
其實摹迷,這也是 Runtime 系統(tǒng)的作用疟赊,self 實在方法運行時被動態(tài)傳入的。

當(dāng) objc_msgSend 找到方法對應(yīng)實現(xiàn)時峡碉,它將直接調(diào)用該方法實現(xiàn)近哟,并將消息中所有參數(shù)都傳遞給方法實現(xiàn),同時鲫寄,它還將傳遞兩個隱藏參數(shù):

接受消息的對象(self 所指向的內(nèi)容吉执,當(dāng)前方法的對象指針)
方法選擇器(_cmd 指向的內(nèi)容,當(dāng)前方法的 SEL 指針)
因為在源代碼方法的定義中地来,我們并沒有發(fā)現(xiàn)這兩個參數(shù)的聲明戳玫。它們時在代碼被編譯時被插入方法實現(xiàn)中的。盡管這些參數(shù)沒有被明確聲明未斑,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?/p>

這兩個參數(shù)中咕宿, self更實用。它是在方法實現(xiàn)中訪問消息接收者對象的實例變量的途徑蜡秽。

這時我們可能會想到另一個關(guān)鍵字 super 府阀,實際上 super 關(guān)鍵字接收到消息時,編譯器會創(chuàng)建一個 objc_super 結(jié)構(gòu)體:

struct objc_super { id receiver; Class class; };]

這個結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定的父類载城。 receiver 仍然是 self 本身肌似,當(dāng)我們想通過 [super class] 獲取父類時,編譯器其實是將指向 self 的 id 指針和 class 的 SEL 傳遞給了 objc_msgSendSuper 函數(shù)诉瓦。只有在 NSObject 類中才能找到 class 方法,然后 class 方法底層被轉(zhuǎn)換為 object_getClass(), 接著底層編譯器將代碼轉(zhuǎn)換為 objc_msgSend(objc_super->receiver, @selector(class))睬澡,傳入的第一個參數(shù)是指向 self 的 id 指針固额,與調(diào)用 [self class] 相同,所以我們得到的永遠都是 self 的類型煞聪。因此你會發(fā)現(xiàn):

// 這句話并不能獲取父類的類型斗躏,只能獲取當(dāng)前類的類型名
NSLog(@"%@", NSStringFromClass([super class]));
獲取方法地址
NSObject 類中有一個實例方法:methodForSelector,你可以用它來獲取某個方法選擇器對應(yīng)的 IMP 昔脯,舉個例子:

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ù)也必須明確給出,上面的例子調(diào)用了1000次函數(shù)云稚,你也可以嘗試給 target 發(fā)送1000次 setFilled: 消息會花多久隧饼。

雖然可以更高效的調(diào)用方法,但是這種做法很少用静陈,除非時需要持續(xù)大量重復(fù)調(diào)用某個方法的情況燕雁,才會選擇使用以免消息發(fā)送泛濫。

注意:
methodForSelector:方法是由 Runtime 系統(tǒng)提供的鲸拥,而不是 Objc 自身的特性
動態(tài)方法解析
你可以動態(tài)提供一個方法實現(xiàn)拐格。如果我們使用關(guān)鍵字 @dynamic 在類的實現(xiàn)文件中修飾一個屬性,表明我們會為這個屬性動態(tài)提供存取方法刑赶,編譯器不會再默認為我們生成這個屬性的 setter 和 getter 方法了捏浊,需要我們自己提供。

@dynamic propertyName;
這時撞叨,我們可以通過分別重載 resolveInstanceMethod: 和 resolveClassMethod: 方法添加實例方法實現(xiàn)和類方法實現(xiàn)呛伴。

當(dāng) Runtime 系統(tǒng)在 Cache 和類的方法列表(包括父類)中找不到要執(zhí)行的方法時,Runtime 會調(diào)用 resolveInstanceMethod: 或 resolveClassMethod: 來給我們一次動態(tài)添加方法實現(xiàn)的機會谒所。我們需要用 class_addMethod 函數(shù)完成向特定類添加特定方法實現(xiàn)的操作:

void dynamicMethodIMP(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 方法添加了實現(xiàn)內(nèi)容热康,就是 dynamicMethodIMP 方法中的代碼。其中 "v@:" 表示返回值和參數(shù)劣领,這個符號表示的含義見:Type Encoding

注意:
動態(tài)方法解析會在消息轉(zhuǎn)發(fā)機制侵入前執(zhí)行姐军,動態(tài)方法解析器將會首先給予提供該方法選擇器對應(yīng)的 IMP 的機會。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機制尖淘,就讓 resolveInstanceMethod: 方法返回 NO奕锌。
消息轉(zhuǎn)發(fā)

1.重定向

消息轉(zhuǎn)發(fā)機制執(zhí)行前,Runtime 系統(tǒng)允許我們替換消息的接收者為其他對象村生。通過 - (id)forwardingTargetForSelector:(SEL)aSelector 方法惊暴。

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

如果此方法返回 nil 或者 self,則會計入消息轉(zhuǎn)發(fā)機制(forwardInvocation:)趁桃,否則將向返回的對象重新發(fā)送消息辽话。

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

當(dāng)動態(tài)方法解析不做處理返回 NO 時肄鸽,則會觸發(fā)消息轉(zhuǎn)發(fā)機制。這時 forwardInvocation: 方法會被執(zhí)行油啤,我們可以重寫這個方法來自定義我們的轉(zhuǎn)發(fā)邏輯:

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

唯一參數(shù)是個 NSInvocation 類型的對象典徘,該對象封裝了原始的消息和消息的參數(shù)。我們可以實現(xiàn) forwardInvocation: 方法來對不能處理的消息做一些處理益咬。也可以將消息轉(zhuǎn)發(fā)給其他對象處理逮诲,而不拋出錯誤。

注意:參數(shù) anInvocation 是從哪來的幽告?
在 forwardInvocation: 消息發(fā)送前梅鹦,Runtime 系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector: 消息,并取到返回的方法簽名用于生成 NSInvocation 對象冗锁。所以重寫 forwardInvocation: 的同時也要重寫 methodSignatureForSelector: 方法齐唆,否則會拋異常。
當(dāng)一個對象由于沒有相應(yīng)的方法實現(xiàn)而無法相應(yīng)某消息時蒿讥,運行時系統(tǒng)將通過 forwardInvocation: 消息通知該對象蝶念。每個對象都繼承了 forwardInvocation: 方法。但是芋绸, NSObject 中的方法實現(xiàn)只是簡單的調(diào)用了 doesNotRecognizeSelector:媒殉。通過實現(xiàn)自己的 forwardInvocation: 方法,我們可以將消息轉(zhuǎn)發(fā)給其他對象摔敛。

forwardInvocation: 方法就是一個不能識別消息的分發(fā)中心廷蓉,將這些不能識別的消息轉(zhuǎn)發(fā)給不同的接收對象,或者轉(zhuǎn)發(fā)給同一個對象马昙,再或者將消息翻譯成另外的消息桃犬,亦或者簡單的“吃掉”某些消息,因此沒有響應(yīng)也不會報錯行楞。這一切都取決于方法的具體實現(xiàn)攒暇。

注意:
forwardInvocation:方法只有在消息接收對象中無法正常響應(yīng)消息時才會被調(diào)用。所以子房,如果我們向往一個對象將一個消息轉(zhuǎn)發(fā)給其他對象時形用,要確保這個對象不能有該消息的所對應(yīng)的方法。否則证杭,forwardInvocation:將不可能被調(diào)用田度。
轉(zhuǎn)發(fā)和多繼承
轉(zhuǎn)發(fā)和繼承相似,可用于為 Objc 編程添加一些多繼承的效果解愤。就像下圖那樣镇饺,一個對象把消息轉(zhuǎn)發(fā)出去,就好像它把另一個對象中的方法接過來或者“繼承”過來一樣送讲。

這使得在不同繼承體系分支下的兩個類可以實現(xiàn)“繼承”對方的方法奸笤,在上圖中 Warrior 和 Diplomat 沒有繼承關(guān)系惋啃,但是 Warrior 將 negotiate 消息轉(zhuǎn)發(fā)給了 Diplomat 后,就好似 Diplomat 是 Warrior 的超類一樣揭保。

消息轉(zhuǎn)發(fā)彌補了 Objc 不支持多繼承的性質(zhì)肥橙,也避免了因為多繼承導(dǎo)致單個類變得臃腫復(fù)雜魄宏。
轉(zhuǎn)發(fā)與繼承

雖然轉(zhuǎn)發(fā)可以實現(xiàn)繼承的功能秸侣,但是 NSObject 還是必須表面上很嚴謹,像 respondsToSelector: 和 isKindOfClass: 這類方法只會考慮繼承體系宠互,不會考慮轉(zhuǎn)發(fā)鏈味榛。

Warrior 對象被問到是否能響應(yīng) negotiate消息:

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
回答當(dāng)然是 NO, 盡管它能接受 negotiate 消息而不報錯予跌,因為它靠轉(zhuǎn)發(fā)消息給 Diplomat 類響應(yīng)消息搏色。

如果你就是想要讓別人以為 Warrior 繼承到了 Diplomat 的 negotiate 方法,你得重新實現(xiàn) respondsToSelector: 和 isKindOfClass: 來加入你的轉(zhuǎn)發(fā)算法:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

除了 respondsToSelector: 和 isKindOfClass: 之外券册,instancesRespondToSelector: 中也應(yīng)該寫一份轉(zhuǎn)發(fā)算法频轿。如果使用了協(xié)議,conformsToProtocol: 同樣也要加入到這一行列中烁焙。

如果一個對象想要轉(zhuǎn)發(fā)它接受的任何遠程消息航邢,它得給出一個方法標簽來返回準確的方法描述 methodSignatureForSelector:,這個方法會最終響應(yīng)被轉(zhuǎn)發(fā)的消息骄蝇。從而生成一個確定的 NSInvocation 對象描述消息和消息參數(shù)膳殷。這個方法最終響應(yīng)被轉(zhuǎn)發(fā)的消息。它需要像下面這樣實現(xiàn):

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

健壯的實例變量(Non Fragile ivars)

在 Runtime 的現(xiàn)行版本中九火,最大的特點就是健壯的實例變量了赚窃。當(dāng)一個類被編譯時泽本,實例變量的內(nèi)存布局就形成了锰悼,它表明訪問類的實例變量的位置。實例變量一次根據(jù)自己所占空間而產(chǎn)生位移:

上圖左是 NSObject 類的實例變量布局矛绘。右邊是我們寫的類的布局虑鼎。這樣子有一個很大的缺陷辱匿,就是缺乏拓展性。哪天蘋果更新了 NSObject 類的話震叙,就會出現(xiàn)問題:

我們自定義的類的區(qū)域和父類的區(qū)域重疊了掀鹅。只有蘋果將父類改為以前的布局才能拯救我們,但這樣導(dǎo)致它們不能再拓展它們的框架了媒楼,因為成員變量布局被固定住了乐尊。在脆弱的實例變量(Fragile ivar)環(huán)境下,需要我們重新編譯繼承自 Apple 的類來恢復(fù)兼容划址。如果是健壯的實例變量的話扔嵌,如下圖:

在健壯的實例變量下限府,編譯器生成的實例變量布局跟以前一樣,但是當(dāng) Runtime 系統(tǒng)檢測到與父類有部分重疊時它會調(diào)整你新添加的實例變量的位移痢缎,那樣你再子類中新添加的成員變量就被保護起來了胁勺。

注意:
在健壯的實例變量下,不要使用 siof(SomeClass)独旷,而是用 class_getInstanceSize([SomeClass class]) 代替署穗;也不要使用 offsetof(SomeClass, SomeIvar),而要使用 ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar")) 來代替嵌洼。
總結(jié)
我們讓自己的類繼承自 NSObject 不僅僅是因為基類有很多復(fù)雜的內(nèi)存分配問題案疲,更是因為這使得我們可以享受到 Runtime 系統(tǒng)帶來的便利。

雖然平時我們很少會考慮一句簡單的調(diào)用方法麻养,發(fā)送消息底層所做的復(fù)雜的操作褐啡,但深入理解 Runtime 系統(tǒng)的細節(jié)使得我們可以利用消息機制寫出功能更強大的代碼。
runtime實現(xiàn)的機制是什么,怎么用鳖昌,一般用于干嘛. 你還能記得你所使用的相關(guān)的頭文件或者某些方法的名稱嗎备畦?

需要導(dǎo)入<objc/message.h><objc/runtime.h>
runtime,運行時機制许昨,它是一套C語言庫
實際上我們編寫的所有OC代碼懂盐,最終都是轉(zhuǎn)成了runtime庫的東西,比如類轉(zhuǎn)成了runtime庫里面的結(jié)構(gòu)體等數(shù)據(jù)類型车要,方法轉(zhuǎn)成了runtime庫里面的C語言函數(shù)允粤,平時調(diào)方法都是轉(zhuǎn)成了objc_msgSend函數(shù)(所以說OC有個消息發(fā)送機制)
因此,可以說runtime是OC的底層實現(xiàn)翼岁,是OC的幕后執(zhí)行者
有了runtime庫类垫,能做什么事情呢?runtime庫里面包含了跟類琅坡、成員變量悉患、方法相關(guān)的API,比如獲取類里面的所有成員變量榆俺,為類動態(tài)添加成員變量售躁,動態(tài)改變類的方法實現(xiàn),為類動態(tài)添加新的方法等
因此茴晋,有了runtime陪捷,想怎么改就怎么改
Objective-C 如何對已有的方法,添加自己的功能代碼以實現(xiàn)類似記錄日志這樣的功能诺擅?

這題目主要考察的是runtime如何交換方法市袖。先在分類中添加一個方法,注意不能重寫系統(tǒng)方法,會覆蓋

+(NSString *)myLog

{

  // 這里寫打印行號,什么方法,哪個類調(diào)用等等

}

// 加載分類到內(nèi)存的時候調(diào)用

    +(void)load

    {

    // 獲取imageWithName方法地址
    Method description = class_getClassMethod(self, @selector(description));

    // 獲取imageWithName方法地址
    Method myLog = class_getClassMethod(self, @selector(myLog));

    // 交換方法地址,相當(dāng)于交換實現(xiàn)方式
    method_exchangeImplementations(description, myLog);
    }

如何讓 Category 支持屬性烁涌?

使用runtime可以實現(xiàn)

頭文件

@interface NSObject (test)

@property (nonatomic, copy) NSString *name;

@end

.m文件

@implementation NSObject (test)

// 定義關(guān)聯(lián)的key

static const char *key = "name";

-(NSString *)name

{
    // 根據(jù)關(guān)聯(lián)的key苍碟,獲取關(guān)聯(lián)的值酒觅。

   return objc_getAssociatedObject(self, key);

}

-(void)setName:(NSString *)name

{

    // 第一個參數(shù):給哪個對象添加關(guān)聯(lián)
    // 第二個參數(shù):關(guān)聯(lián)的key,通過這個key獲取
    // 第三個參數(shù):關(guān)聯(lián)的value
    // 第四個參數(shù):關(guān)聯(lián)的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

Toll-Free Bridging 是什么微峰?什么情況下會使用舷丹?

Toll-Free Bridging用于在Foundation對象與Core Foundation對象之間交換數(shù)據(jù),俗稱橋接

在ARC環(huán)境下,Foundation對象轉(zhuǎn)成 Core Foundation對象

  • 使用__bridge橋接以后ARC會自動管理2個對象
  • 使用__bridge_retained橋接需要手動釋放Core Foundation對象
  • 在ARC環(huán)境下, Core Foundation對象轉(zhuǎn)成 Foundation對象
  • 使用__bridge橋接,如果Core Foundation對象被釋放,Foundation對象也同時不能使用了,需要手動管理Core Foundation對象
  • 使用__bridge_transfer橋接,系統(tǒng)會自動管理2個對象
    performSelector:withObject:afterDelay: 內(nèi)部大概是怎么實現(xiàn)的,有什么注意事項么蜓肆?

創(chuàng)建一個定時器,時間結(jié)束后系統(tǒng)會使用runtime通過方法名稱(Selector本質(zhì)就是方法名稱)去方法列表中找到對應(yīng)的方法實現(xiàn)并調(diào)用方法
注意事項
調(diào)用performSelector:withObject:afterDelay:方法時,先判斷希望調(diào)用的方法是否存在respondsToSelector:
這個方法是異步方法,必須在主線程調(diào)用,在子線程調(diào)用永遠不會調(diào)用到想調(diào)用的方法
什么是 Method Swizzle(黑魔法)颜凯,什么情況下會使用?

在沒有一個類的實現(xiàn)源碼的情況下症杏,想改變其中一個方法的實現(xiàn)装获,除了繼承它重寫瑞信、和借助類別重名方法暴力搶先之外厉颤,還有更加靈活的方法Method Swizzle。
Method swizzling指的是改變一個已存在的選擇器對應(yīng)的實現(xiàn)的過程凡简。OC中方法的調(diào)用能夠在運行時通過改變——通過改變類的調(diào)度表(dispatch table)中選擇器到最終函數(shù)間的映射關(guān)系逼友。
在OC中調(diào)用一個方法,其實是向一個對象發(fā)送消息秤涩,查找消息的唯一依據(jù)是selector的名字帜乞。利用OC的動態(tài)特性,可以實現(xiàn)在運行時偷換selector對應(yīng)的方法實現(xiàn)筐眷。
每個類都有一個方法列表黎烈,存放著selector的名字和方法實現(xiàn)的映射關(guān)系。IMP有點類似函數(shù)指針匀谣,指向具體的Method實現(xiàn)照棋。
我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP,
我們可以利用 class_replaceMethod 來修改類武翎,
我們可以利用 method_setImplementation 來直接設(shè)置某個方法的IMP烈炭,
歸根結(jié)底,都是偷換了selector的IMP
能否向編譯后得到的類中增加實例變量宝恶?能否向運行時創(chuàng)建的類中添加實例變量符隙?為什么?

  • 不能向編譯后得到的類中增加實例變量垫毙;
  • 能向運行時創(chuàng)建的類中添加實例變量霹疫;

解釋如下:
因為編譯后的類已經(jīng)注冊在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實例變量的鏈表 和 instance_size 實例變量的內(nèi)存大小已經(jīng)確定综芥,同時runtime 會調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用丽蝎。所以不能向存在的類中添加實例變量;
運行時創(chuàng)建的類是可以添加實例變量毫痕,調(diào)用 class_addIvar 函數(shù)征峦。但是得在調(diào)用 objc_allocateClassPair 之后迟几,objc_registerClassPair 之前,原因同上栏笆。
為什么其他語言里叫函數(shù)調(diào)用类腮, objective c里則是給對象發(fā)消息(或者談下對runtime的理解)

先來看看怎么理解發(fā)送消息的含義:
[receiver message]會被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
如果消息含有參數(shù),則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者能夠找到對應(yīng)的selector蛉加,那么就相當(dāng)于直接執(zhí)行了接收者這個對象的特定方法蚜枢;否則,消息要么被轉(zhuǎn)發(fā)针饥,或是臨時向接收者動態(tài)添加這個selector對應(yīng)的實現(xiàn)內(nèi)容厂抽,要么就干脆玩完崩潰掉。
現(xiàn)在可以看出[receiver message]真的不是一個簡簡單單的方法調(diào)用丁眼。因為這只是在編譯階段確定了要向接收者發(fā)送message這條消息筷凤,而receive將要如何響應(yīng)這條消息,那就要看運行時發(fā)生的情況來決定了苞七。
OC 的 Runtime 鑄就了它動態(tài)語言的特性藐守,Objc Runtime使得C具有了面向?qū)ο竽芰Γ诔绦蜻\行時創(chuàng)建蹂风,檢查卢厂,修改類、對象和它們的方法惠啄∩骱悖可以使用runtime的一系列方法實現(xiàn)。
順便附上OC中一個類的數(shù)據(jù)結(jié)構(gòu) /usr/include/objc/runtime.h
struct objc_class {

Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class撵渡,因為Objc的類的本身也是一個Object融柬,為了處理這個關(guān)系,runtime就創(chuàng)造了Meta Class姥闭,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時丹鸿,實際上是把這個消息發(fā)給了Class Object

 #if !__OBJC2__    
     Class super_class 
OBJC2_UNAVAILABLE; // 父類 
     const char *name 
OBJC2_UNAVAILABLE; // 類名     
     long version 
OBJC2_UNAVAILABLE; // 類的版本信息,默認為0     
     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; // 方法緩存靠欢,對象接到一個消息會根據(jù)isa指針查找消息對象,這時會在method       Lists中遍歷铜跑,如果cache了门怪,常用的方法調(diào)用時就能夠提高調(diào)用的效率。     
    struct objc_protocol_list *protocols 
OBJC2_UNAVAILABLE; // 協(xié)議鏈表     
 #endif

} OBJC2_UNAVAILABLE;
OC中一個類的對象實例的數(shù)據(jù)結(jié)構(gòu)(/usr/include/objc/objc.h):
typedef struct objc_class *Class;      /// 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;

向object發(fā)送消息時锅纺,Runtime庫會根據(jù)object的isa指針找到這個實例object所屬于的類掷空,然后在類的方法列表以及父類方法列表尋找對應(yīng)的方法運行。id是一個objc_object結(jié)構(gòu)類型的指針,這個類型的對象能夠轉(zhuǎn)換成任何一種對象坦弟。
然后再來看看消息發(fā)送的函數(shù):objc_msgSend函數(shù)
在引言中已經(jīng)對objc_msgSend進行了一點介紹护锤,看起來像是objc_msgSend返回了數(shù)據(jù),其實objc_msgSend從不返回數(shù)據(jù)而是你的方法被調(diào)用后返回了數(shù)據(jù)酿傍。下面詳細敘述下消息發(fā)送步驟:
檢測這個 selector 是不是要忽略的烙懦。比如 Mac OS X 開發(fā),有了垃圾回收就不理會 retain,release 這些函數(shù)了赤炒。
檢測這個 target 是不是 nil 對象氯析。ObjC 的特性是允許對一個 nil 對象執(zhí)行任何一個方法不會 Crash,因為會被忽略掉莺褒。
如果上面兩個都過了掩缓,那就開始查找這個類的 IMP,先從 cache 里面找遵岩,完了找得到就跳到對應(yīng)的函數(shù)去執(zhí)行你辣。
如果 cache 找不到就找一下方法分發(fā)表。
如果分發(fā)表找不到就到超類的分發(fā)表去找旷余,一直找绢记,直到找到NSObject類為止。
如果還找不到就要開始進入動態(tài)方法解析了正卧,后面會提到。
后面還有:
動態(tài)方法解析resolveThisMethodDynamically
消息轉(zhuǎn)發(fā)forwardingTargetForSelector
runtime如何實現(xiàn)weak屬性跪解?

? 通過關(guān)聯(lián)屬性來實現(xiàn):

?    // 聲明一個weak屬性炉旷,這里假設(shè)delegate,其實weak關(guān)鍵字可以不使用叉讥,
?    // 因為我們重寫了getter/setter方法
?    @property (nonatomic, weak) id delegate;
?     
?    - (id)delegate {
?      return objc_getAssociatedObject(self, @"__delegate__key");
?    }
?     
?    // 指定使用OBJC_ASSOCIATION_ASSIGN窘行,官方注釋是:
?    // Specifies a weak reference to the associated object.
?    // 也就是說對于對象類型,就是weak了
?    - (void)setDelegate:(id)delegate {
?      objc_setAssociatedObject(self, @"__delegate__key", delegate, OBJC_ASSOCIATION_ASSIGN);
?    }

? 通過objc_storeWeak函數(shù)來實現(xiàn)图仓,不過這種方式幾乎沒有遇到有人這么使用過罐盔,因為這里不細說了。
runtime如何通過selector找到對應(yīng)的IMP地址救崔?

每個selector都與對應(yīng)的IMP是一一對應(yīng)的關(guān)系惶看,通過selector就可以直接找到對應(yīng)的IMP:
objc_msgForward函數(shù)是做什么的,直接調(diào)用它將會發(fā)生什么六孵?

_objc_msgForward是IMP類型纬黎,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個對象發(fā)送一條消息,但它并沒有實現(xiàn)的時候劫窒,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)本今。
IMP msgForward = _objc_msgForward;
如果手動調(diào)用objcmsgForward,將跳過查找IMP的過程,而是直接觸發(fā)“消息轉(zhuǎn)發(fā)”冠息,進入如下流程:

? 第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel實現(xiàn)方法挪凑,指定是否動態(tài)添加方法。若返回NO逛艰,則進入下一步岖赋,若返回YES,則通過class_addMethod函數(shù)動態(tài)地添加方法瓮孙,消息得到處理唐断,此流程完畢。

? 第二步:在第一步返回的是NO時杭抠,就會進入- (id)forwardingTargetForSelector:(SEL)aSelector方法脸甘,這是運行時給我們的第二次機會,用于指定哪個對象響應(yīng)這個selector偏灿。不能指定為self丹诀。若返回nil,表示沒有響應(yīng)者翁垂,則會進入第三步铆遭。若返回某個對象,則會調(diào)用該對象的方法沿猜。

? 第三步:若第二步返回的是nil枚荣,則我們首先要通過- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法簽名,若返回nil啼肩,則表示不處理橄妆。若返回方法簽名,則會進入下一步祈坠。

? 第四步:當(dāng)?shù)谌椒祷胤椒ǚ椒ê灻蠛δ耄蜁{(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我們可以通過anInvocation對象做很多處理赦拘,比如修改實現(xiàn)方法慌随,修改響應(yīng)對象等

? 第五步:若沒有實現(xiàn)- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么會進入- (void)doesNotRecognizeSelector:(SEL)aSelector方法躺同。若我們沒有實現(xiàn)這個方法阁猜,那么就會crash,然后提示打不到響應(yīng)的方法笋籽。到此蹦漠,動態(tài)解析的流程就結(jié)束了。
runtime如何實現(xiàn)weak變量的自動置nil车海?

runtime對注冊的類會進行布局笛园,對于weak對象會放入一個hash表中隘击。 用weak指向的對象內(nèi)存地址作為key,當(dāng)此對象的引用計數(shù)為0的時候會dealloc研铆。假如weak指向的對象內(nèi)存地址是a埋同,那么就會以a為鍵,在這個 weak 表中搜索棵红,找到所有以a為鍵的weak對象凶赁,從而設(shè)置為nil。
weak修飾的指針默認值是nil(在Objective-C中向nil發(fā)送消息是安全的)
動態(tài)綁定

在運行時確定要調(diào)用的方法,動態(tài)綁定將調(diào)用方法的確定也推遲到運行時逆甜。在編譯時虱肄,方法的調(diào)用并不和代碼綁定在一起,只有在消實發(fā)送出來之后交煞,才確定被調(diào)用的代碼咏窿。通過動態(tài)類型和動態(tài)綁定技術(shù),代碼每次執(zhí)行都可以得到不同的結(jié)果素征。運行時因子負責(zé)確定消息的接收者和被調(diào)用的方法集嵌。運行時的消息分發(fā)機制為動態(tài)綁定提供支持。當(dāng)向一個動態(tài)類型確定了的對象發(fā)送消息時御毅,運行環(huán)境系統(tǒng)會通過接收者的isa指針定位對象的類根欧,并以此為起點確定被調(diào)用的方法,方法和消息是動態(tài)綁定的端蛆。而且凤粗,不必在Objective-C 代碼中做任何工作,就可以自動獲取動態(tài)綁定的好處欺税。在每次發(fā)送消息時侈沪,特別是當(dāng)消息的接收者是動態(tài)類型已經(jīng)確定的對象時,動態(tài)綁定就會例行而透明地發(fā)生

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晚凿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瘦馍,更是在濱河造成了極大的恐慌歼秽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件情组,死亡現(xiàn)場離奇詭異燥筷,居然都是意外死亡,警方通過查閱死者的電腦和手機院崇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門肆氓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人底瓣,你說我怎么就攤上這事谢揪。” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵拨扶,是天一觀的道長凳鬓。 經(jīng)常有香客問我,道長患民,這世上最難降的妖魔是什么缩举? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮匹颤,結(jié)果婚禮上仅孩,老公的妹妹穿的比我還像新娘。我一直安慰自己印蓖,他們只是感情好辽慕,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著另伍,像睡著了一般鼻百。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摆尝,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天温艇,我揣著相機與錄音,去河邊找鬼堕汞。 笑死勺爱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讯检。 我是一名探鬼主播琐鲁,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼人灼!你這毒婦竟也來了围段?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤投放,失蹤者是張志新(化名)和其女友劉穎奈泪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灸芳,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涝桅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烙样。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冯遂。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谒获,靈堂內(nèi)的尸體忽然破棺而出蛤肌,到底是詐尸還是另有隱情壁却,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布寻定,位于F島的核電站儒洛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狼速。R本人自食惡果不足惜琅锻,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望向胡。 院中可真熱鬧恼蓬,春花似錦、人聲如沸僵芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拇派。三九已至荷辕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間件豌,已是汗流浹背疮方。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茧彤,地道東北人骡显。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像曾掂,于是被迫代替她去往敵國和親惫谤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉珠洗,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,544評論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,131評論 0 9
  • 也不知道該如何開頭,大寶晚上有些鬧蛔糯,我讓母親把她送到樓上來睡,原本老二已經(jīng)睡著了窖式,大寶的到來打擾了他蚁飒,現(xiàn)在大寶已經(jīng)...
    蘇素的異想時空閱讀 400評論 0 5
  • MYSQL官方: Connector/J:JDBC Type 4驅(qū)動,純java實現(xiàn)Mysql協(xié)議萝喘,并且不依賴My...
    風(fēng)沙第一閱讀 1,058評論 1 2