iOS - Runtime 細(xì)致分析及具體運(yùn)用

前言

運(yùn)行時(shí)可以說(shuō)是 Objective-C 這門(mén)語(yǔ)言的一個(gè)核心部分,看了許多博客囚戚,也反反復(fù)復(fù)看了多次源碼(蘋(píng)果開(kāi)源的runtime源碼),對(duì)于這方面研究的文章博客也比較多,對(duì)技術(shù)懷有敬畏之心倦始,工作一年跟三年的對(duì)字符串的理解都是不一樣的,還是再寫(xiě)寫(xiě)吧讼溺!如有誤歡迎指正楣号,如果看完覺(jué)得對(duì)你學(xué)習(xí)有幫助,關(guān)注一波幫忙點(diǎn)個(gè)喜歡??唄。

初識(shí) Runtime - 運(yùn)行時(shí)

runtime 簡(jiǎn)稱(chēng)運(yùn)行時(shí)炫狱。其是系統(tǒng)運(yùn)行的時(shí)候的一些機(jī)制藻懒,主要體現(xiàn)的是對(duì)象的消息機(jī)制。(讀文章思考方式:看完上面一句話(huà)你腦海中應(yīng)該有這個(gè)三個(gè)關(guān)鍵詞運(yùn)行時(shí)视译、OC中對(duì)象嬉荆、消息發(fā)送 。本文也就是圍繞著三個(gè)關(guān)鍵詞進(jìn)行的思考與闡述的)酷含。

  • Objective-C是基于C語(yǔ)言加入了面向?qū)ο筇匦?(面向?qū)ο筘灤┱麄€(gè)操作系統(tǒng)鄙早,OC是重度對(duì)象語(yǔ)言) 和消息轉(zhuǎn)發(fā)機(jī)制 (消息分發(fā)) 的動(dòng)態(tài)語(yǔ)言
  • Objective-C 是如何具有面向?qū)ο蟮哪芰Φ哪兀縊C中對(duì)象通過(guò) C 語(yǔ)言的結(jié)構(gòu)體來(lái)表示椅亚,對(duì)象的方法實(shí)現(xiàn)通過(guò) C 函數(shù)來(lái)實(shí)現(xiàn)限番。
  • Objective-C 的面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機(jī)制動(dòng)態(tài)性決定了其不僅需要一個(gè)編譯器(疑問(wèn)思考其編譯的過(guò)程),而且還需要運(yùn)行時(shí)系統(tǒng)來(lái)動(dòng)態(tài)創(chuàng)建類(lèi)和對(duì)象呀舔,從而進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)弥虐。
  • 下面上具體代碼來(lái)感受下上面的幾句話(huà)的含義,在一個(gè)控制器VC中對(duì)自定義繼承自NSObjectCCSomeClass類(lèi)進(jìn)行實(shí)例化媚赖,然后調(diào)用其- (void)sendMessage:(NSString *)str;方法:
- (void)viewDidLoad {
    [super viewDidLoad];
  
    CCSomeClass *someClass = [[CCSomeClass alloc] init];
    [someClass sendMessage:@"zerocc"];
 }

下面的代碼實(shí)現(xiàn)等同于上面的實(shí)現(xiàn)效果

- (void)viewDidLoad {
    [super viewDidLoad];

    CCSomeClass *someClass = objc_msgSend([CCSomeClass class], sel_registerName("alloc"));
    someClass = objc_msgSend(someClass, sel_registerName("init"));
    SEL sel = sel_registerName("sendMessage:");
    objc_msgSend(someClass,sel,@"zerocc");
}

Objective-C 中類(lèi)和對(duì)象

類(lèi)與對(duì)象的概念

  • 類(lèi)是對(duì)同一類(lèi)事物高度的抽象霜瘪,類(lèi)中定義了這一類(lèi)對(duì)象所應(yīng)具有的靜態(tài)屬性(屬性)和動(dòng)態(tài)屬性(方法)。
  • 對(duì)象就是該類(lèi)的一個(gè)實(shí)例惧磺,對(duì)象的創(chuàng)建就是類(lèi)的實(shí)例化過(guò)程颖对。
  • 可以理解為類(lèi)就是一種數(shù)據(jù)類(lèi)型,它的變量就是對(duì)象磨隘。

Objective-C 中的類(lèi)會(huì)是什么缤底?

  • 在Objective-C 類(lèi)繼承中,所有的類(lèi)都是 NSObject (NSProxy 類(lèi)拋開(kāi)不考慮) 的子類(lèi)番捂;也就是說(shuō) NSObject 是 Objective-C 繼承中的根類(lèi),其他類(lèi)都從 NSObject 繼承一套基本的接口到 Objective-C 運(yùn)行時(shí)體系中训堆。這些類(lèi)的實(shí)例又都是從 NSObject 繼承而獲得 Objective-C 最根本的特性 - NSObject特性詳解
  • NSObject .h 中類(lèi)的定義(interface)文件白嘁,我們可以看到如下代碼:
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

每個(gè)NSObject類(lèi)都擁有一個(gè)Class類(lèi)作為成員變量, 并且這個(gè)根類(lèi)只有這么一個(gè) isa屬性坑鱼;

  • Class isa是什么呢?進(jìn)入到 objc.h文件中絮缅,看到如下:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

1. Classobjc_class類(lèi)型的結(jié)構(gòu)體的指針變量
? 2. 關(guān)鍵字typedef作用是給指向結(jié)構(gòu)體的objc_class類(lèi)型的指針起別名
? 3. Class isa 等價(jià)于 struct objc_class *isa,所以從這里得出 isa是一個(gè)指針鲁沥,是一個(gè)指向objc_class結(jié)構(gòu)體的指針變量。

  • objc_class類(lèi)型結(jié)構(gòu)體具體是? 進(jìn)入到runtime.h文件中耕魄,看其定義:
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;  // 父類(lèi) 是指向父類(lèi)的
    const char *name                                         OBJC2_UNAVAILABLE;  // 類(lèi)名
    long version                                             OBJC2_UNAVAILABLE;  // 類(lèi)的版本信息画恰,默認(rèn)為0
    long info                                                OBJC2_UNAVAILABLE;  // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size                                       OBJC2_UNAVAILABLE;  // 該類(lèi)的實(shí)例變量大小
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;  // 該類(lèi)的成員變量鏈表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;  // 協(xié)議鏈表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */ 

objc_class結(jié)構(gòu)體描述了一個(gè)類(lèi)的所有信息:父類(lèi)吸奴、名稱(chēng)允扇、信息缠局、變量大小、變量列表考润、方法列表狭园、協(xié)議列表等等;特別注意objc_class結(jié)構(gòu)體中第一個(gè)數(shù)據(jù)又是指向另一個(gè)Classisa指針(metaclass后面分析)糊治,所有的對(duì)象在內(nèi)存里面都有一個(gè)isa唱矛。概況的來(lái)說(shuō)這個(gè)isa指針對(duì)應(yīng)的內(nèi)存地址中存儲(chǔ)了這些信息(類(lèi)的信息)。

objc_ivar_list成員變量列表結(jié)構(gòu):
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;
objc_method_list方法列表結(jié)構(gòu):
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

**methodLists指針的指針,可以動(dòng)態(tài)修改*methodLists的值來(lái)添加成員方法,同樣解釋了Category不能添加屬性的原因,二級(jí)指針

objc_method方法列表中的鏈表,它存儲(chǔ)了方法名藤违,方法類(lèi)型和方法實(shí)現(xiàn)
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

1. 方法名類(lèi)型為SEL,前面提到過(guò)相同名字的方法即使在不同類(lèi)中定義窃肠,它們的方法選擇器也相同。
? 2. 方法類(lèi)型method_types是個(gè)char指針刷允,其實(shí)存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型铭拧。
? 3. method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)指針恃锉,后面會(huì)詳細(xì)講到

/// 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 

它就是一個(gè)函數(shù)指針,這是由編譯器生成的呕臂。當(dāng)你發(fā)起一個(gè) ObjC 消息之后破托,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的歧蒋。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)土砂。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開(kāi)消息傳遞階段谜洽,直接執(zhí)行方法萝映,這在后面會(huì)提到。
??你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類(lèi)型相同阐虚,參數(shù)都包含id和SEL類(lèi)型序臂。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類(lèi)型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的实束,通過(guò)一組id和SEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址奥秆;反之亦然。
??每個(gè)類(lèi)都有一個(gè)方法列表咸灿,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系构订。IMP有點(diǎn)類(lèi)似函數(shù)指針,指向具體的Method實(shí)現(xiàn)避矢,SEL與IMP之間的關(guān)系圖:
??獲取方法地址IMP避開(kāi)消息綁定而直接獲取方法的地址并調(diào)用方法悼瘾。這種做法很少用囊榜,除非是需要持續(xù)大量重復(fù)調(diào)用某方法的極端情況,避開(kāi)消息發(fā)送泛濫而直接調(diào)用該方法會(huì)更高效亥宿。NSObject類(lèi)中有個(gè)methodForSelector:實(shí)例方法卸勺,你可以用它來(lái)獲取某個(gè)方法選擇器對(duì)應(yīng)的IMP,舉個(gè)例子:

void (*setter)(id, SEL, BOOL);
        int i;
        setter = (void (*)(id, SEL, BOOL))[target
                                           methodForSelector:@selector(setFilled:)];
        for ( i = 0 ; i < 1000 ; i++ )
        setter(targetList[i], @selector(setFilled:), YES);
objc_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)用的性能進(jìn)行優(yōu)化,通俗地講,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí),它不會(huì)直接在isa指向的類(lèi)的方法列表中遍歷查找能夠響應(yīng)消息的方法箩绍,因?yàn)檫@樣效率太低了孔庭,而是優(yōu)先在Cache中查找。Runtime系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用材蛛,那么它有可能今后還會(huì)被調(diào)用)method_name作為key圆到,method_imp作為value給存起來(lái),下次查找的時(shí)候效率更高卑吭。這根計(jì)算機(jī)組成原理中學(xué)過(guò)的CPU繞過(guò)主存先訪問(wèn)Cache的道理挺像芽淡,高速緩存(cache) ->內(nèi)存->虛擬內(nèi)存->磁盤(pán)。

Objective-C方法動(dòng)態(tài)調(diào)用中涉及的術(shù)語(yǔ)介紹

OC只是在編譯階段確定了要向接收者發(fā)送message這條消息豆赏,而receive將要如何響應(yīng)這條消息挣菲,那就要看運(yùn)行時(shí)發(fā)生的情況來(lái)決定了。通過(guò)發(fā)送消息來(lái)達(dá)到動(dòng)態(tài)調(diào)用:

[obj doSomething]    // 編譯時(shí)runtime會(huì)將其轉(zhuǎn)化為 objc_msgSend(obj,@selector(doSomething));

也就是說(shuō) objc_msgSend 函數(shù)相當(dāng)于入口掷邦;對(duì)象調(diào)用某個(gè)方法都將被編譯器轉(zhuǎn)化為:

id objc_msgSend(id self, SEL op, ... );  // objc_msgSend(obj, selector, arg1, arg2, ...)

如果消息的接收者能夠找到對(duì)應(yīng)的selector白胀,那么就相當(dāng)于直接執(zhí)行了接收者這個(gè)對(duì)象的特定方法;否則抚岗,消息要么被轉(zhuǎn)發(fā)或杠,或是臨時(shí)向接收者動(dòng)態(tài)添加這個(gè) selector 對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容,要么就崩潰掉宣蔚。
下面將會(huì)逐漸展開(kāi)介紹一些術(shù)語(yǔ)向抢,及它們各自對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。

id

  • id 類(lèi)型參數(shù) objc_msgSend方法的第一個(gè)參數(shù)胚委,objc.h文件中查看:
/// 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;
  • 由此可知id是指向objc_object結(jié)構(gòu)體的指針變量
  • objc_object結(jié)構(gòu)體代表一個(gè)類(lèi)實(shí)例其包含了一個(gè)指向objc_class類(lèi)型結(jié)構(gòu)體的指針(上面已經(jīng)分析過(guò)Class isa)挟鸠;
  • 總結(jié)id為指向類(lèi)實(shí)例的指針,也就是他可以指向任何對(duì)象

SEL

  • SEL 類(lèi)型參數(shù)objc_msgSend方法的第一個(gè)參數(shù)亩冬,objc.h文件中查看:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
  • SELselector在Objc中的表示類(lèi)型艘希,selector是方法選擇器
  • SEL可以理解為方法編號(hào),其實(shí)它就是個(gè)映射到方法的C字符串,objc.h文件中:
    struct objc_method {
        SEL method_name;
        char *method_types;
        IMP method_imp;
    };
  • 可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器硅急。
  • 不同類(lèi)中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的枢冤,即使方法名字相同而變量類(lèi)型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類(lèi)型(NSNumber一堆抽象工廠方法)铜秆,Cocoa 中有好多長(zhǎng)長(zhǎng)的方法....淹真。

...是方法中的實(shí)參

objc_msgSend(receiver, selector, arg1, arg2,...)
// 例如 調(diào)someClass對(duì)象發(fā)送- (void)sendMessage:(NSString *)str;方法
objc_msgSend(someClass, @selector("sendMessage:"),@"zerocc");  // 

Objective-C 消息發(fā)送流程

調(diào)用方法的方式有兩種:

  • [object message] 的方式調(diào)用方法,如果一個(gè)對(duì)象無(wú)法按上述正常流程接受某一消息時(shí)连茧,就會(huì)啟動(dòng)所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制核蘸,通過(guò)這一機(jī)制巍糯,我們可以告訴對(duì)象如何處理未知的消息。默認(rèn)情況下客扎,對(duì)象接收到未知的消息祟峦,會(huì)導(dǎo)致程序崩潰,通過(guò)控制臺(tái)徙鱼,我們可以看到以下異常信息:這段異常信息實(shí)際上是由NSObject的doesNotRecognizeSelector方法拋出的宅楞。不過(guò),我們可以采取一些措施袱吆,讓我們的程序執(zhí)行特定的邏輯厌衙,而避免程序的崩潰。
  • 以 perform… 的形式來(lái)調(diào)用绞绒,則需要等到運(yùn)行時(shí)才能確定object是否能接收message消息婶希。如果不能,則程序崩潰蓬衡。通常喻杈,當(dāng)我們不能確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:來(lái)判斷一下狰晚。如下代碼所示:
    if([self respondsToSelector:@selector(method)]){
        [self performSelector:@selector(method)];
    }

但是最終運(yùn)行時(shí)都是調(diào)用了objc_msgSend(receiver, selector, arg1, arg2,...)函數(shù)筒饰,這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情:

  1. 檢測(cè)這個(gè)selector是不是要忽略。比如Mac OS X開(kāi)發(fā)壁晒,有了垃圾回收就不理會(huì)retain, release這些函數(shù)了瓷们。
  2. 檢測(cè)這個(gè)target是不是nil對(duì)象。ObjC的特性是允許對(duì)一個(gè)nil對(duì)象執(zhí)行任何一個(gè)方法不會(huì)Crash讨衣,因?yàn)闀?huì)被忽略掉。
  • 上面檢測(cè)都通過(guò)則開(kāi)始查找這個(gè)類(lèi)的IMP,先從cache里面找,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行式镐。
  • 如果cache找不到反镇,通過(guò)對(duì)象的isa指針獲取到類(lèi)的結(jié)構(gòu)體,然后在方法分發(fā)表里面查找方法的selector (方法分發(fā)表既是:class中的方法列表method_list娘汞,它將方法選擇器和方法實(shí)現(xiàn)聯(lián)系起來(lái))歹茶。
  • 如果分發(fā)表找不到,objc_msgSend 結(jié)構(gòu)體中指向父類(lèi)的指針找到其父類(lèi)你弦,并在父類(lèi)的分發(fā)表去找方法的selector惊豺,會(huì)一直沿著類(lèi)的繼承體系到達(dá)NSObject類(lèi)。一旦定位到selector禽作,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn)尸昧,并傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn),并將該方法添加進(jìn)入緩存中。如果最后也沒(méi)有定位到selector旷偿,則會(huì)走消息轉(zhuǎn)發(fā)流程烹俗。

在異常拋出前爆侣,Objective-C 的運(yùn)行時(shí)會(huì)給你三次拯救程序的機(jī)會(huì):

  • 動(dòng)態(tài)方法解析
  • 重定向接收者
  • 完整的消息轉(zhuǎn)發(fā)

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

對(duì)象在接收到未知的消息時(shí),首先會(huì)調(diào)用所屬類(lèi)的類(lèi)方法 +resolveInstanceMethod:(實(shí)例方法)或者 +resolveClassMethod:(類(lèi)方法)幢妄。在這個(gè)方法中兔仰,我們有機(jī)會(huì)為該未知消息新增一個(gè)“處理方法”(或者說(shuō)函數(shù)實(shí)現(xiàn)),通過(guò)運(yùn)行時(shí)class_addMethod函數(shù)動(dòng)態(tài)添加到類(lèi)里面就可以了蕉鸳。

  • + (BOOL)resolveInstanceMethod:(SEL)sel;解析實(shí)例方法
  • + (BOOL)resolveClassMethod:(SEL)sel;解析類(lèi)方法
  • 通過(guò)class_addMethod(...)的方式將缺少的selector動(dòng)態(tài)創(chuàng)建出來(lái)乎赴,前提是有提前實(shí)現(xiàn)好的IMP(method_types一致)
  • 如果resolveInstanceMethod:方法返回NO,運(yùn)行時(shí)就會(huì)進(jìn)行下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)潮尝。
  • 用處:這種方案更多的是為@dynamic屬性準(zhǔn)備的榕吼,Core Data有效用到這個(gè)方法,NSManagedObjects中的屬性的getter和setter就是在運(yùn)行時(shí)動(dòng)態(tài)添加的衍锚。

具體代碼示例:

//  CCSomeClass.h
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import <Foundation/Foundation.h>

@interface CCSomeClass : NSObject

- (void)resolveMethod;

@end

//  CCSomeClass.m
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import "CCSomeClass.h"
#import <objc/runtime.h>

@implementation CCSomeClass

void addResolveMethod(id obj, SEL _cmd) {
    NSLog(@"resolveMethod was called ");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if(sel == @selector(resolveMethod)){
        class_addMethod([self class], sel, (IMP)addResolveMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

重定向接收者

如果上一步動(dòng)態(tài)方法解析沒(méi)有處理友题,runtime會(huì)調(diào)用以下方法

  • -(id)forwardingTargetForSelector:(SEL)aSelector;
  • 如果該方法返回非nil的對(duì)象,則使用該對(duì)象作為新的消息接收者
    不能返回self戴质,會(huì)出現(xiàn)無(wú)限循環(huán)
  • 如果不知道該返回什么度宦,應(yīng)該使用[super forwardingTargetForSelector:aSelector];
  • 這種方法屬于單純的轉(zhuǎn)發(fā),無(wú)法對(duì)消息的參數(shù)和返回值進(jìn)行處理
  • 這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上告匠。但這一步無(wú)法對(duì)消息進(jìn)行處理戈抄,如操作消息的參數(shù)和返回值。

具體代碼示例:

//  CCSomeClass.h
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import <Foundation/Foundation.h>

@interface CCSomeClass : NSObject

- (void)forwardMethod_arrayWithString:(NSString *)str;

@end

//  CCSomeClass.m
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import "CCSomeClass.h"
#import <objc/runtime.h>
#import "CCOtherClass.h"

@implementation CCSomeClass

#pragma mark - 重定向接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    //獲取方法名
    NSString *selectorString = NSStringFromSelector(aSelector);
    //根據(jù)方法名添加方法
    if ([selectorString isEqualToString:@"forwardMethod_arrayWithString:"]) {
        CCOtherClass *otherClass = [[CCOtherClass alloc] init];
        
        return otherClass;
    }
    
    return [super forwardingTargetForSelector:aSelector];
}
@end

此例中備用接受者為自定義的CCOtherclass后专,其具體代碼:

//  CCOtherClass.h
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import <Foundation/Foundation.h>

@interface CCOtherClass : NSObject

@end

//  CCOtherClass.m
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import "CCOtherClass.h"

@implementation CCOtherClass

/**
 *  把字符串轉(zhuǎn)換為數(shù)組
 *
 *  @param str 需轉(zhuǎn)換的字符串
 *
 *  @return 轉(zhuǎn)換好的數(shù)組
 */
- (NSArray *)forwardMethod_arrayWithString:(NSString *)str
{
    if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) {
        NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:1];
        for (NSInteger index = 0; index < str.length; index++) {
            [mArr addObject:[str substringWithRange:NSMakeRange(index, 1)]];
        }
        NSLog(@"array:::%@",mArr);

        return mArr;
    }
    
    return nil;
}

@end

完整的消息轉(zhuǎn)發(fā)

  • 消息轉(zhuǎn)發(fā)的第一步重寫(xiě)方法:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;為未有實(shí)現(xiàn)的方法的selector提供合適的方法簽名然后返回這個(gè)aSelector的方法簽名划鸽。
  • 當(dāng)對(duì)象發(fā)送一個(gè)unrecognized 的消息時(shí),會(huì)使用從上面方法獲取的方法簽名等方法信息創(chuàng)建一個(gè)表示消息的 NSInvocation 對(duì)象戚哎,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中裸诽,包括selector,目標(biāo)(target)和參數(shù)型凳。
  • 然后調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation;方法丈冬,在方法中又通過(guò)- (void)invokeWithTarget:(id)target;方法選擇將消息轉(zhuǎn)發(fā)給指定對(duì)象。其中 forwardInvocation: 方法的實(shí)現(xiàn)有兩個(gè)任務(wù):
    • 定位可以響應(yīng)封裝在anInvocation中的消息的對(duì)象甘畅。這個(gè)對(duì)象不需要能處理所有未知消息埂蕊。
    • 使用anInvocation作為參數(shù),將消息發(fā)送到選中的對(duì)象疏唾。anInvocation將會(huì)保留調(diào)用結(jié)果蓄氧,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送 到消息的原始發(fā)送者。

具體代碼樣例分析:

//  CCSomeClass.h
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import <Foundation/Foundation.h>

@interface CCSomeClass : NSObject

- (void)usualMethod;

- (void)resolveMethod;

- (void)forwardMethod_arrayWithString:(NSString *)str;

- (void)signatureMethod_inverseWithString:(NSString *)str;

@end

#import "CCSomeClass.h"
#import <objc/runtime.h>
#import "CCOtherClass.h"

@implementation CCSomeClass

#pragma mark - 完整消息轉(zhuǎn)發(fā)
//必須重寫(xiě)這個(gè)方法槐脏,為給定的selector提供一個(gè)合適的方法簽名喉童。
// 返回aSelector的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    
    if (!signature) {
        if ([CCOtherClass instancesRespondToSelector:aSelector]) {
            //獲取方法簽名
            signature = [CCOtherClass instanceMethodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}

// 當(dāng)對(duì)self發(fā)送一個(gè)unrecoginzed的消息時(shí),會(huì)創(chuàng)建一個(gè)NSInvocation顿天,并調(diào)用這個(gè)方法泄朴。允許在這個(gè)方法中重抖,通過(guò)[anInvocation invokeWithTarget:otherSelf];的方式進(jìn)行消息轉(zhuǎn)發(fā)。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    //anInvocation選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象
    if ([CCOtherClass instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[[CCOtherClass alloc] init]];
    }

}

@end
//  CCOtherClass.h
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface CCOtherClass : NSObject

@end

//  CCOtherClass.m
//  CCRuntime
//
//  Created by zerocc on 2017/4/25.
//  Copyright ? 2017年 zerocc. All rights reserved.

#import "CCOtherClass.h"

@implementation CCOtherClass

/**
 *  逆置字符串
 *
 *  @param str 需逆置的字符串
 *
 *  @return 置換后的字符串
 */
- (NSString *)signatureMethod_inverseWithString:(NSString *)str
{
    if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) {
        NSMutableString *mStr = [NSMutableString stringWithCapacity:1];
        for (NSInteger index = str.length; index > 0; index--) {
            [mStr appendString:[str substringWithRange:NSMakeRange(index - 1, 1)]];
        }
        NSLog(@"retureStr:::%@",mStr);
        
        return mStr;
    }
    
    return nil;
}

@end

運(yùn)行時(shí)系統(tǒng)涉及的類(lèi)與對(duì)象操作函數(shù)

  • 屬性相關(guān)操作函數(shù)
CCMyClass *myClass = [[CCMyClass alloc] init];
unsigned int outCount = 0;
Class cls = [myClass class];
// 屬性操作
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    const char *propertyName =  property_getName(property);
    NSLog(@"property's name: %s", propertyName);
}
free(properties);
//成員變量操作函數(shù)
// 獲取類(lèi)中指定名稱(chēng)實(shí)例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 獲取類(lèi)成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); //這個(gè)只能夠向在runtime時(shí)創(chuàng)建的類(lèi)添加成員變量

// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); //必須使用free()來(lái)釋放這個(gè)數(shù)組

//屬性操作函數(shù)
// 獲取類(lèi)中指定名稱(chēng)實(shí)例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 獲取類(lèi)成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
  • 方法相關(guān)操作函數(shù)
//  獲取一個(gè)類(lèi)方法列表
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
    Method method = methods[i];
    SEL method_name = method_getName(method);
    NSLog(@"method's signature - method_name: %s", method_name);
}
free(methods);

// 添加方法
//和成員變量不同的是可以為類(lèi)動(dòng)態(tài)添加方法祖灰。如果有同名會(huì)返回NO钟沛,修改的話(huà)需要使用method_setImplementation
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types) ;
// 替換原方法實(shí)現(xiàn)(偷梁換柱)
IMP class_replaceMethod(Class cls, SEL name, IMP imp, 
                                    const char *types) ;
// 交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2);

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

// 類(lèi)實(shí)例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
  • 協(xié)議相關(guān)操作函數(shù)
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回類(lèi)是否實(shí)現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回類(lèi)實(shí)現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

Runtime黑魔法的具體運(yùn)用

  • 能獲得某個(gè)類(lèi)的所有成員變量
  • 能獲得某個(gè)類(lèi)的所有屬性
  • 能獲得某個(gè)類(lèi)的所有方法
  • 交換方法實(shí)現(xiàn)
  • 能動(dòng)態(tài)添加一個(gè)成員變量
  • 能動(dòng)態(tài)添加一個(gè)屬性
  • 字典轉(zhuǎn)模型
  • runtime歸檔/反歸檔

交換方法

  • 開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能局扶,并且保持原有的功能恨统。
  • 方式一:繼承系統(tǒng)的類(lèi),重寫(xiě)方法
  • 方式二:使用runtime,交換方法.
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // 需求:給imageNamed方法提供功能三妈,每次加載圖片就判斷下圖片是否加載成功畜埋。
    // 步驟一:先搞個(gè)分類(lèi),定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
    // 步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn)畴蒲,就能調(diào)用imageWithName悠鞍,間接調(diào)用imageWithName的實(shí)現(xiàn)。
    UIImage *image = [UIImage imageNamed:@"123"];
}
@end

@implementation UIImage (Image)
// 加載分類(lèi)到內(nèi)存的時(shí)候調(diào)用
+ (void)load
{
    // 交換方法
    // 獲取imageWithName方法地址
    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));

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

    // 交換方法地址模燥,相當(dāng)于交換實(shí)現(xiàn)方式
    method_exchangeImplementations(imageWithName, imageName);
}

// 不能在分類(lèi)中重寫(xiě)系統(tǒng)方法imageNamed咖祭,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類(lèi)中不能調(diào)用super.

// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
    // 這里調(diào)用imageWithName蔫骂,相當(dāng)于調(diào)用imageName
    UIImage *image = [self imageWithName:name];

    if (image == nil) {
        NSLog(@"加載空的圖片");
    }
    return image;
}
@end

動(dòng)態(tài)添加方法

  • 開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類(lèi)方法非常多么翰,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表辽旋,可以使用動(dòng)態(tài)給某個(gè)類(lèi)浩嫌,添加方法解決。
  • 經(jīng)典面試題:有沒(méi)有使用performSelector补胚,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法码耐。

代碼示例:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    Person *p = [[Person alloc] init];

    // 默認(rèn)person,沒(méi)有實(shí)現(xiàn)eat方法溶其,可以通過(guò)performSelector調(diào)用骚腥,但是會(huì)報(bào)錯(cuò)。
    // 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
    [p performSelector:@selector(eat)];

}
@end

@implementation Person
// void(*)()
// 默認(rèn)方法都有兩個(gè)隱式參數(shù)握联,
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法桦沉,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過(guò)來(lái).
// 剛好可以用來(lái)判斷每瞒,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 動(dòng)態(tài)添加eat方法

        // 第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
        // 第二個(gè)參數(shù):添加方法的方法編號(hào)
        // 第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
        // 第四個(gè)參數(shù):函數(shù)的類(lèi)型金闽,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
@end

給分類(lèi)添加屬性

  • 原理:給一個(gè)類(lèi)聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類(lèi)添加關(guān)聯(lián)剿骨,并不是直接把這個(gè)值的內(nèi)存空間添加到類(lèi)存空間代芜。
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 給系統(tǒng)NSObject類(lèi)動(dòng)態(tài)添加屬性name
    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"zerocc";
    NSLog(@"%@",objc.name);
}
@end

// 定義關(guān)聯(lián)的key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name
{
    // 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值浓利。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
    // 第二個(gè)參數(shù):關(guān)聯(lián)的key挤庇,通過(guò)這個(gè)key獲取
    // 第三個(gè)參數(shù):關(guān)聯(lián)的value
    // 第四個(gè)參數(shù):關(guān)聯(lián)的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

歸檔和解檔 一鍵序列化

  • 原理描述:用runtime提供的函數(shù)遍歷Model自身所有屬性钞速,并對(duì)屬性進(jìn)行encode和decode操作。
  • 核心方法:在Model的基類(lèi)中重寫(xiě)方法:

如果需要實(shí)現(xiàn)一些基本數(shù)據(jù)的數(shù)據(jù)持久化(data persistance)或者數(shù)據(jù)共享(data share)嫡秕。我們可以選擇歸檔和解檔渴语。如果用一般的方法:

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"nameKey"];
    [aCoder encodeObject:self.gender forKey:@"genderKey"];
    [aCoder encodeObject:[NSNumber numberWithInteger:self.age] forKey:@"ageKey"];
}

也可以實(shí)現(xiàn)。但是如果實(shí)體類(lèi)有很多的成員變量昆咽,這種方法很顯然就無(wú)力了驾凶。這個(gè)時(shí)候,我們就可以利用runtime來(lái)實(shí)現(xiàn)快速歸檔掷酗、解檔:

  • 讓實(shí)體類(lèi)遵循<NSCoding>協(xié)議调违。并在.m文件導(dǎo)入頭文件<objc/runtime.h>。
  • 實(shí)現(xiàn)- (instancetype)initWithCoder:(NSCoder *)aDecoder;- (void)encodeWithCoder:(NSCoder *)aCoder;方法泻轰。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        //
        unsigned int count = 0;
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        for (int i = 0; i < count; i ++) {
            objc_property_t property = properties[i];
            const char *propertyChar = property_getName(property);
            NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
            id value = [aDecoder decodeObjectForKey:propertyString];
            [self setValue:value forKey:propertyString];
        }
        free(properties);
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i ++) {
        objc_property_t property = properties[i];
        const char *propertyChar = property_getName(property);
        NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
        id object = [self valueForKey:propertyString];
        [aCoder encodeObject:object forKey:propertyString];
    }
    free(properties);
}

或者這種寫(xiě)法:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

控制器的萬(wàn)能跳轉(zhuǎn)

應(yīng)用場(chǎng)景:

  • 推送:根據(jù)服務(wù)端推送過(guò)來(lái)的數(shù)據(jù)規(guī)則技肩,跳轉(zhuǎn)到對(duì)應(yīng)的控制器
  • 列表:不同類(lèi)似的名字,可能跳轉(zhuǎn)不同的控制器浮声,任意跳轉(zhuǎn)
- (void)testRuntime
{
    NSDictionary *userInfo = @{@"class":@"CCRuntimePushVC",
                               @"property": @{
                                       @"ID":@"81198",
                                       @"type":@"2"
                                       }
                               };
    [self push:userInfo];
}

// 跳轉(zhuǎn)
- (void)push:(NSDictionary *)params
{
    // 得到類(lèi)名
    NSString *className = [NSString stringWithFormat:@"%@",params[@"class"]];

    // 通過(guò)名稱(chēng)轉(zhuǎn)換成Class
    Class getClass = NSClassFromString([NSString stringWithFormat:@"%@",className]);

    // 判斷得到的這個(gè)class 是否存在
    if (getClass) {
        // 創(chuàng)建 class 對(duì)象
        id creatClass = [[getClass alloc] init];

        NSDictionary *propertys = params[@"property"];
        [propertys enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

            if ([self checkIsExistPropertyWithInstance:creatClass verifyPropertyName:key]) {
                // 利用 kvc 賦值
                [creatClass setValue:obj forKey:key];
            }
        }];

        [self.navigationController pushViewController:creatClass animated:YES];
    }else{
        NSLog(@"not this class,can not push");
    }
}

// 檢查對(duì)象是否存在該屬性
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
    unsigned int outCount, i;
    // 獲取對(duì)象的屬性列表
    objc_property_t *properties = class_copyPropertyList([instance class], &outCount);

    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        // 屬性名轉(zhuǎn)換成字符串
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property)  encoding:NSUTF8StringEncoding];
        // 判斷該屬性是否存在
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);

            return YES;
        }
    }
    free(properties);

    return NO;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self testRuntime];
}

參考鏈接

Objc 對(duì)象的今生今世
Objective-C 維基百科
精神病院Objective-C runtime系列
蘋(píng)果官方API文檔解釋
蘋(píng)果官方Objective-C運(yùn)行時(shí)編程指南文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虚婿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阿蝶,更是在濱河造成了極大的恐慌雳锋,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡洁,死亡現(xiàn)場(chǎng)離奇詭異玷过,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)筑煮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)辛蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人真仲,你說(shuō)我怎么就攤上這事袋马。” “怎么了秸应?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵虑凛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我软啼,道長(zhǎng)桑谍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任祸挪,我火速辦了婚禮锣披,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己雹仿,他們只是感情好增热,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著胧辽,像睡著了一般峻仇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邑商,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天础浮,我揣著相機(jī)與錄音,去河邊找鬼奠骄。 笑死豆同,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的含鳞。 我是一名探鬼主播影锈,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蝉绷!你這毒婦竟也來(lái)了鸭廷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熔吗,失蹤者是張志新(化名)和其女友劉穎辆床,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桅狠,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讼载,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了中跌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咨堤。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漩符,靈堂內(nèi)的尸體忽然破棺而出一喘,到底是詐尸還是另有隱情,我是刑警寧澤嗜暴,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布凸克,位于F島的核電站,受9級(jí)特大地震影響闷沥,放射性物質(zhì)發(fā)生泄漏萎战。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一狐赡、第九天 我趴在偏房一處隱蔽的房頂上張望撞鹉。 院中可真熱鬧,春花似錦颖侄、人聲如沸鸟雏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)孝鹊。三九已至,卻和暖如春展蒂,著一層夾襖步出監(jiān)牢的瞬間又活,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工锰悼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柳骄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓箕般,卻偏偏與公主長(zhǎng)得像耐薯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丝里,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353