iOS runtime探究(二): 從runtime開始深入理解OC消息轉(zhuǎn)發(fā)機(jī)制

你要知道的runtime都在這里

轉(zhuǎn)載請(qǐng)注明出處 http://www.reibang.com/p/eac6ed137e06

本文主要講解runtime相關(guān)知識(shí),從原理到實(shí)踐,由于包含內(nèi)容過多分為以下五篇文章詳細(xì)講解供炼,可自行選擇需要了解的方向:

本文是系列文章的第二篇文章從runtime開始: 深入理解OC消息轉(zhuǎn)發(fā)機(jī)制,主要從runtime出發(fā)講解OC的消息傳遞和消息轉(zhuǎn)發(fā)機(jī)制擎值。

你不知道的msg_send

我們知道在OC中的實(shí)例對(duì)象調(diào)用一個(gè)方法稱作消息傳遞,比如有如下代碼:

NSMutableString *str = [[NSMutableString alloc] initWithString: @"Jiaming Chen"];
[str appendString:@" is a good guy."];

上述代碼中的第二句str稱為消息的接受者配猫,appendString:稱作選擇子也就是我們常用的selector幅恋,selector參數(shù)共同構(gòu)成了消息,所以第二句話可以理解為將消息:"增加一個(gè)字符串: is a good guy"發(fā)送給消息的接受者str泵肄。
OC中里的消息傳遞采用動(dòng)態(tài)綁定機(jī)制來決定具體調(diào)用哪個(gè)方法捆交,OC的實(shí)例方法在轉(zhuǎn)寫為C語(yǔ)言后實(shí)際就是一個(gè)函數(shù),但是OC并不是在編譯期決定調(diào)用哪個(gè)函數(shù)腐巢,而是在運(yùn)行期決定品追,因?yàn)榫幾g期根本不能確定最終會(huì)調(diào)用哪個(gè)函數(shù),這是由于運(yùn)行期可以修改方法的實(shí)現(xiàn)冯丙,在后文會(huì)有講解肉瓦。舉個(gè)栗子,有如下代碼:

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

上述代碼在編譯期沒有任何問題胃惜,因?yàn)?code>id類型可以指向任何類型的實(shí)例對(duì)象泞莉,NSString有一個(gè)方法appendString:,在編譯期不確定這個(gè)num到底具體指代什么類型的實(shí)例對(duì)象船殉,并且在運(yùn)行期還可以給NSNumber類型添加新的方法鲫趁,因此編譯期發(fā)現(xiàn)有appendString:的函數(shù)聲明就不會(huì)報(bào)錯(cuò),但在運(yùn)行時(shí)找不到在NSNumber類中找不到appendString:方法利虫,就會(huì)報(bào)錯(cuò)挨厚。這也就是消息傳遞的強(qiáng)大之處和弊端堡僻,編譯期無法檢查到未定義的方法,運(yùn)行期可以添加新的方法疫剃。

講了這么多OC究竟是怎么將實(shí)例方法轉(zhuǎn)換為C語(yǔ)言的函數(shù)钉疫,又是如何調(diào)用這些函數(shù)的呢?這些都依靠強(qiáng)大的runtime巢价。

在深入代碼之前介紹一個(gè)clang編譯器的命令:

clang -rewrite-objc main.m
該命令可以將.m的OC文件轉(zhuǎn)寫為.cpp文件

有如下代碼:

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

- (void)showMyself;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

- (void)showMyself {
    NSLog(@"My name is %@ I am %ld years old.", self.name, self.age);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         //為了方便查看轉(zhuǎn)寫后的C語(yǔ)言代碼牲阁,將alloc和init分兩步完成
        Person *p = [Person alloc];
        p = [p init];
        p.name = @"Jiaming Chen";
        [p showMyself];
    }
    return 0;
}

通過上述clang命令可以轉(zhuǎn)寫代碼,然后找到如下定義:

static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }

// @synthesize age = _age;
static NSUInteger _I_Person_age(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, NSUInteger age) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }

static void _I_Person_showMyself(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_0, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")), ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}

// @end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

        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"));

    }
    return 0;
}

關(guān)于屬性property生成的getter蹄溉、setter和實(shí)例變量相關(guān)代碼在另一篇博客iOS @property探究(二): 深入理解中有詳細(xì)介紹咨油,本文不再贅述您炉,本文僅針對(duì)自定義的方法來講解柒爵。

可以發(fā)現(xiàn)轉(zhuǎn)寫后的C語(yǔ)言代碼將實(shí)例方法轉(zhuǎn)寫為了一個(gè)靜態(tài)函數(shù)。接下來一行一行的分析上述代碼赚爵,第一行代碼可以簡(jiǎn)要表示為如下代碼:

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

這一行代碼做了三件事情棉胀,第一獲取Person類,第二注冊(cè)alloc方法冀膝,第三發(fā)送消息唁奢,將消息alloc發(fā)送給類對(duì)象,可以簡(jiǎn)單的將注冊(cè)方法理解為窝剖,通過方法名獲取到轉(zhuǎn)寫后C語(yǔ)言函數(shù)的函數(shù)指針麻掸。
第二行代碼就可以簡(jiǎn)寫為如下代碼:

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

這一行代碼與上一行類似,注冊(cè)了init方法赐纱,然后通過objc_msgSend函數(shù)將消息init發(fā)送給消息的接受者p脊奋。
第三行是一個(gè)對(duì)setter的調(diào)用,同樣的也可以簡(jiǎn)寫為如下代碼:

//這一行是用來查找參數(shù)的地址疙描,取名為name
(NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_1)
objc_msgSend(p, sel_registerName("setName:"), name);

這一行代碼同樣是先注冊(cè)方法setName:然后通過objc_msgSend函數(shù)將消息setName:發(fā)送給消息的接收者诚隙,只是多了一個(gè)參數(shù)的傳遞。
同理起胰,最后一行代碼也可以簡(jiǎn)寫為如下:

objc_msgSend(p, sel_registerName("showMyself"));

解釋與上述相同久又,不再贅述。

到這里效五,我們應(yīng)該就可以看出OC的runtime通過objc_msgSend函數(shù)將一個(gè)面向?qū)ο蟮南鬟f轉(zhuǎn)為了面向過程的函數(shù)調(diào)用地消。
objc_msgSend函數(shù)根據(jù)消息的接受者和selector選擇適當(dāng)?shù)姆椒▉碚{(diào)用,那它又是如何選擇的呢畏妖?這就涉及到前一篇博客講解的內(nèi)容iOS runtime探究(一): 從runtime開始: 理解面向?qū)ο蟮念惖矫嫦蜻^程的結(jié)構(gòu)體脉执,這一篇博客中詳細(xì)講解了OC的runtime是如何將面向?qū)ο蟮念愑成錇槊嫦蜻^程的結(jié)構(gòu)體的,再來回顧一下幾個(gè)主要的結(jié)構(gòu)體:

文件objc/runtime.h中有如下定義:
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
}
/* Use `Class` instead of `struct objc_class *` */

文件objc/objc.h文件中有如下定義
/// An opaque type that represents an Objective-C class.
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;

注意結(jié)構(gòu)體struct objc_class中包含一個(gè)成員變量struct objc_method_list **methodLists瓜客,通過名稱我們分析出這個(gè)成員變量保存了實(shí)例方法列表适瓦,繼續(xù)查找結(jié)構(gòu)體struct objc_method_list的定義如下:

static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        5,
        {{(struct objc_selector *)"showMyself", "v16@0:8", (void *)_I_Person_showMyself},
        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
        {(struct objc_selector *)"age", "Q16@0:8", (void *)_I_Person_age},
        {(struct objc_selector *)"setAge:", "v24@0:8Q16", (void *)_I_Person_setAge_}}
};

struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
};

我們發(fā)現(xiàn)struct objc_method_list中還包含了一個(gè)未知的結(jié)構(gòu)體struct _objc_method同時(shí)也找到它的定義竿开,為了方便查看將兩者寫在一起。
結(jié)構(gòu)體struct objc_method_list里面包含以下幾個(gè)成員變量:結(jié)構(gòu)體struct _objc_method的大小玻熙、方法個(gè)數(shù)以及最重要的方法列表否彩,方法列表存儲(chǔ)的是方法描述結(jié)構(gòu)體struct _objc_method,該結(jié)構(gòu)體里保存了選擇子嗦随、方法類型以及方法的具體實(shí)現(xiàn)列荔。可以看出方法的具體實(shí)現(xiàn)就是一個(gè)函數(shù)指針枚尼,也就是我們自定義的實(shí)例方法贴浙,選擇子也就是selector可以理解為是一個(gè)字符串類型的名稱,用于查找對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)(由于蘋果沒有開源selector的相關(guān)代碼署恍,但是可以查到GNU OC中關(guān)于selector的定義崎溃,也是一個(gè)結(jié)構(gòu)體但是結(jié)構(gòu)體里存儲(chǔ)的就是一個(gè)字符串類型的名稱)。

這樣就能解釋objc_msgSend的工作原理的盯质,為了匹配消息的接收者和選擇子袁串,需要在消息的接收者所在的類中去搜索這個(gè)struct objc_method_list方法列表,如果能找到就可以直接跳轉(zhuǎn)到相關(guān)的具體實(shí)現(xiàn)中去調(diào)用呼巷,如果找不到囱修,那就會(huì)通過super_class指針沿著繼承樹向上去搜索,如果找到就跳轉(zhuǎn)王悍,如果到了繼承樹的根部(通常為NSObject)還沒有找到破镰,那就會(huì)調(diào)用NSObjec的一個(gè)方法doesNotRecognizeSelector:,這個(gè)方法就會(huì)報(bào)unrecognized selector錯(cuò)誤(其實(shí)在調(diào)用這個(gè)方法之前還會(huì)進(jìn)行消息轉(zhuǎn)發(fā)压储,還有三次機(jī)會(huì)來處理鲜漩,消息轉(zhuǎn)發(fā)在后文會(huì)有介紹)。

這樣一看渠脉,要發(fā)送消息真的好復(fù)雜宇整,需要經(jīng)過這么多步驟,難道不會(huì)影響性能嗎芋膘?當(dāng)然了鳞青,這樣一次次搜索和靜態(tài)綁定那樣直接跳轉(zhuǎn)到函數(shù)指針指向的位置去執(zhí)行來比肯定是耗時(shí)很多的,因此为朋,類對(duì)象也就是結(jié)構(gòu)體struct objc_class中有一個(gè)成員變量struct objc_cache臂拓,這個(gè)緩存里緩存的正是搜索方法的匹配結(jié)果,這樣在第二次及以后再訪問時(shí)就可以采用映射的方式找到相關(guān)實(shí)現(xiàn)的具體位置习寸。

到這里我們就已經(jīng)弄清楚了整個(gè)發(fā)送消息的過程胶惰,但是當(dāng)對(duì)象無法接收相關(guān)消息時(shí)又會(huì)發(fā)生什么?以及前文說的三次機(jī)會(huì)又是什么霞溪?下文將會(huì)介紹消息轉(zhuǎn)發(fā)孵滞。

消息轉(zhuǎn)發(fā): unrecognized selector的最后三次機(jī)會(huì)

還是那個(gè)栗子:

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

前文介紹了進(jìn)行一次發(fā)送消息會(huì)在相關(guān)的類對(duì)象中搜索方法列表中捆,如果找不到則會(huì)沿著繼承樹向上一直搜索知道繼承樹根部(通常為NSObject),如果還是找不到并且消息轉(zhuǎn)發(fā)都失敗了就回執(zhí)行doesNotRecognizeSelector:方法報(bào)unrecognized selector錯(cuò)坊饶。那么消息轉(zhuǎn)發(fā)到底是什么呢泄伪?接下來將會(huì)逐一介紹最后的三次機(jī)會(huì)。

第一次機(jī)會(huì): 所屬類動(dòng)態(tài)方法解析

首先匿级,如果沿繼承樹沒有搜索到相關(guān)方法則會(huì)向接收者所屬的類進(jìn)行一次請(qǐng)求蟋滴,看是否能夠動(dòng)態(tài)的添加一個(gè)方法,注意這是一個(gè)類方法痘绎,因?yàn)槭窍蚪邮照咚鶎俚念愡M(jìn)行請(qǐng)求津函。

+(BOOL)resolveInstanceMethod:(SEL)name

舉個(gè)栗子吧:

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;
//如果需要傳參直接在參數(shù)列表后面添加就好了
void dynamicAdditionMethodIMP(id self, SEL _cmd) {
    NSLog(@"dynamicAdditionMethodIMP");
}

+ (BOOL)resolveInstanceMethod:(SEL)name {
    NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(name));
    if (name == @selector(appendString:)) {
        class_addMethod([self class], name, (IMP)dynamicAdditionMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:name];
}

+ (BOOL)resolveClassMethod:(SEL)name {
    NSLog(@"resolveClassMethod %@", NSStringFromSelector(name));
    return [super resolveClassMethod:name];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id p = [[Person alloc] init];
        [p appendString:@""];
    }
    return 0;
}

先看一下最后的輸出結(jié)果吧:

2017-03-24 19:05:25.092404 OCTest[5142:1185077] resolveInstanceMethod: appendString:
2017-03-24 19:05:25.092810 OCTest[5142:1185077] dynamicAdditionMethodIMP

先看一下main函數(shù),首先創(chuàng)建了一個(gè)Person的實(shí)例對(duì)象孤页,一定要用id類型來聲明尔苦,否則會(huì)在編譯期就報(bào)錯(cuò),因?yàn)檎也坏较嚓P(guān)函數(shù)的聲明散庶,id類型由于可以指向任何類型的對(duì)象蕉堰,因此編譯時(shí)能夠找到NSString類的相關(guān)方法聲明就不會(huì)報(bào)錯(cuò)。
由于Person類沒有聲明和定義appendString:方法悲龟,所以運(yùn)行時(shí)應(yīng)該會(huì)報(bào)unrecognized selector錯(cuò)誤,但是并沒有冰寻,因?yàn)槲覀冎貙懥祟惙椒?code>+ (BOOL)resolveInstanceMethod:(SEL)name须教,當(dāng)找不到相關(guān)實(shí)例方法的時(shí)候就會(huì)調(diào)用該類方法去詢問是否可以動(dòng)態(tài)添加,如果返回True就會(huì)再次執(zhí)行相關(guān)方法斩芭,接下來看一下如何給一個(gè)類動(dòng)態(tài)添加一個(gè)方法轻腺,那就是調(diào)用runtime庫(kù)中的class_addMethod方法,該方法的原型是

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

通過參數(shù)名可以看出第一個(gè)參數(shù)是需要添加方法的類划乖,第二個(gè)參數(shù)是一個(gè)selector贬养,也就是實(shí)例方法的名字,第三個(gè)參數(shù)是一個(gè)IMP類型的變量也就是函數(shù)實(shí)現(xiàn)琴庵,需要傳入一個(gè)C函數(shù)误算,這個(gè)函數(shù)至少有兩個(gè)參數(shù),一個(gè)是id self一個(gè)是SEL _cmd迷殿,第四個(gè)參數(shù)是函數(shù)類型儿礼。具體設(shè)置方法可以看注釋。

第二次機(jī)會(huì): 備援接收者

當(dāng)對(duì)象所屬類不能動(dòng)態(tài)添加方法后庆寺,runtime就會(huì)詢問當(dāng)前的接受者是否有其他對(duì)象可以處理這個(gè)未知的selector蚊夫,相關(guān)方法聲明如下:

- (id)forwardingTargetForSelector:(SEL)aSelector;

該方法的參數(shù)就是那個(gè)未知的selector,這是一個(gè)實(shí)例方法懦尝,因?yàn)槭窃儐栐搶?shí)例對(duì)象是否有其他實(shí)例對(duì)象可以接收這個(gè)未知的selector知纷,如果沒有就返回nil壤圃,可以自行實(shí)驗(yàn)。

第三次機(jī)會(huì): 消息重定向

當(dāng)沒有備援接收者時(shí)琅轧,就只剩下最后一次機(jī)會(huì)埃唯,那就是消息重定向。這個(gè)時(shí)候runtime會(huì)將未知消息的所有細(xì)節(jié)都封裝為NSInvocation對(duì)象鹰晨,然后調(diào)用下述方法:

- (void)forwardInvocation: (NSInvocation*)invocation;

調(diào)用這個(gè)方法如果不能處理就會(huì)調(diào)用父類的相關(guān)方法墨叛,一直到NSObject的這個(gè)方法,如果NSObject都無法處理就會(huì)調(diào)用doesNotRecognizeSelector:方法拋出異常模蜡。

整個(gè)消息轉(zhuǎn)發(fā)流程如下圖所示:


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

總結(jié)

本文通過對(duì)runtime的分析漠趁,詳細(xì)解釋了整個(gè)發(fā)送消息和消息轉(zhuǎn)發(fā)的流程,對(duì)OC的runtime能有一個(gè)更清晰的掌握忍疾。

下一步

這兩篇文章分別介紹了runtime如何將面向?qū)ο蟮念愑成涞矫嫦蜻^程的結(jié)構(gòu)體以及runtime的消息發(fā)送和消息轉(zhuǎn)發(fā)流程闯传,下一篇文章將繼續(xù)介紹runtime對(duì)實(shí)例變量的處理。感興趣的讀者可以繼續(xù)學(xué)習(xí)下一篇文章從runtime開始: 理解OC的屬性property

備注

由于作者水平有限卤妒,難免出現(xiàn)紕漏甥绿,如有問題還請(qǐng)不吝賜教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末则披,一起剝皮案震驚了整個(gè)濱河市共缕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌士复,老刑警劉巖图谷,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阱洪,居然都是意外死亡便贵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門冗荸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來承璃,“玉大人,你說我怎么就攤上這事蚌本】猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵魂毁,是天一觀的道長(zhǎng)玻佩。 經(jīng)常有香客問我,道長(zhǎng)席楚,這世上最難降的妖魔是什么咬崔? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上垮斯,老公的妹妹穿的比我還像新娘郎仆。我一直安慰自己,他們只是感情好兜蠕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布扰肌。 她就那樣靜靜地躺著,像睡著了一般熊杨。 火紅的嫁衣襯著肌膚如雪曙旭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天晶府,我揣著相機(jī)與錄音桂躏,去河邊找鬼。 笑死川陆,一個(gè)胖子當(dāng)著我的面吹牛剂习,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播较沪,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼鳞绕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了尸曼?” 一聲冷哼從身側(cè)響起们何,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骡苞,沒想到半個(gè)月后垂蜗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡解幽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烘苹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躲株。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖镣衡,靈堂內(nèi)的尸體忽然破棺而出霜定,到底是詐尸還是另有隱情,我是刑警寧澤廊鸥,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布望浩,位于F島的核電站,受9級(jí)特大地震影響惰说,放射性物質(zhì)發(fā)生泄漏磨德。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望典挑。 院中可真熱鬧酥宴,春花似錦、人聲如沸您觉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琳水。三九已至肆糕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間在孝,已是汗流浹背诚啃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浑玛,地道東北人绍申。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像顾彰,于是被迫代替她去往敵國(guó)和親极阅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉涨享,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評(píng)論 0 9
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂樂的簡(jiǎn)書閱讀 2,129評(píng)論 0 9
  • 簡(jiǎn)介 Runtime 又叫運(yùn)行時(shí)筋搏,是一套底層的 C 語(yǔ)言 API,其為 iOS 內(nèi)部的核心之一厕隧,我們平時(shí)編寫的 O...
    隨風(fēng)飄蕩的小逗逼閱讀 937評(píng)論 0 0
  • 第四章 和尚內(nèi)心一陣戰(zhàn)栗奔脐,伸出手,顫抖著摸了摸諍諍的頭吁讨,然后就目光灼灼地盯著他髓迎,神情快速變換著,竟似陷入魔怔建丧。 “...
    戒煙帝國(guó)閱讀 207評(píng)論 0 0
  • 說到投資理財(cái)翎朱,很多人并不生疏橄维,那么,2017年個(gè)人怎樣投資理財(cái)呢拴曲?下面争舞,小編就為大家做解答。 1澈灼、先保證生活再理財(cái)...
    8846482d58c9閱讀 257評(píng)論 0 0