Objective-C消息的傳遞和轉(zhuǎn)發(fā)

titleImg.png

很久沒有寫簡書了,今天得空更新一篇逞频。其實(shí)有關(guān)于Objective-C消息的傳遞和轉(zhuǎn)發(fā),簡書上已經(jīng)有很多類似的文章了栋齿。但是苗胀,為了讓自己記憶深刻,以便自己后續(xù)查找瓦堵,所以還是記錄下來基协。

一. 消息的傳遞

使用某個(gè)對象去調(diào)用成員方法,這個(gè)操作在我們實(shí)際開發(fā)中在常見不慣了菇用,當(dāng)時(shí)往往我們并沒去實(shí)際研究澜驮,其底層的實(shí)際操作是怎樣的⊥锱福可能有的小伙伴會說杂穷,我知道怎么調(diào)用就可以了,干嘛還要去研究底層揩慕。這個(gè)的話亭畜,純屬個(gè)人興趣吧。喜歡知其所以然迎卤,對我們的自身的提高也是一件非常好的事情拴鸵。

某個(gè)對象調(diào)用其所屬類的成員方法,這個(gè)在Objective-C(以下簡稱OC)中的術(shù)語叫“消息傳遞(pass a message)”蜗搔。我們的成員方法有生命格式是劲藐,返回值、方法名樟凄、參數(shù)聘芜、方法實(shí)體。這些知識我們的語法名稱缝龄,編譯器內(nèi)部汰现,他們又有不同的叫法。每個(gè)成員方法叔壤,又叫做“消息”瞎饲。“消息”主要有以下幾個(gè)東西構(gòu)成:“消息名稱”或“選擇子(selector)”炼绘、“參數(shù)”嗅战、“返回值”。
由于OC是C的超集俺亮,所以我們在弄清楚OC的方法傳遞之前驮捍,我們先來回顧下C語言的函數(shù)調(diào)用疟呐。C語言使用的是“靜態(tài)綁定”(static binding),也就是說东且,程序在編譯階段就已經(jīng)決定了各個(gè)步驟所需調(diào)用的函數(shù)名以及其具體實(shí)現(xiàn)代碼启具。舉例說明:

# import <stdio.h>

void printHello() {
    printf("Hello, world!\n");
}

void printGoodbye() {
    printf("Goodbye, world!\n");
}

void gratting(int type) {
      if (type == 0) {
          printHello();
      } else {
          printGoodbye();
      }
}

此時(shí)如果我們在不考慮內(nèi)聯(lián)函數(shù)的情況下,那么編譯器在編譯代碼的時(shí)候就已經(jīng)知道了程序中有printHello和printGoodbye兩個(gè)函數(shù)了苇倡,于是編譯器會直接把此兩個(gè)函數(shù)編譯成相關(guān)指令富纸,且兩個(gè)函數(shù)的存放地址也已經(jīng)硬編碼在指令之中了。那么旨椒,如果我們把上面的代碼稍加修改,會怎么樣呢?修改如下:

# import <stdio.h>

void printHello() {
    printf("Hello, world!\n");
}

void printGoodbye() {
    printf("Goodbye, world!\n");
}

void gratting(int type) {
      void *(fnc)();
      if (type == 0) {
          fnc = printHello;
      } else {
          fnc = printGoodbye;
      }
      fnc();
}

此時(shí)堵漱,我們使用了“動態(tài)綁定”(dynamic bingding)综慎。以為在所要調(diào)用的函數(shù)直到執(zhí)行了if else判讀之后,才能知道具體要調(diào)用那個(gè)方法勤庐。編譯器在這種情況下生成的指令與剛才的例子則有所不同示惊。在事先的例子中,在if else判斷語句中都有函數(shù)調(diào)用指令愉镰。但在剛才的例子中米罚,只有一個(gè)函數(shù)調(diào)用指令,不過待調(diào)用的函數(shù)的地址無法硬編碼到指令之中丈探,而是在執(zhí)行到了if else 語句后才知道具體的調(diào)用函數(shù)录择。那么這就是我們要講的第一個(gè)概念“動態(tài)綁定”(dynamic bingding)。

在OC中碗降,某實(shí)例對象調(diào)用某個(gè)成員方法隘竭,我們稱其為向該實(shí)例對象傳遞某條消息。此時(shí)將會使用到我們上述的動態(tài)綁定機(jī)制來決定需要調(diào)用的方法是哪一個(gè)讼渊。在OC的底層动看,所有的成員方法都是C語言函數(shù),當(dāng)實(shí)例對象調(diào)用某個(gè)方法的時(shí)候爪幻,他到底該去調(diào)用哪個(gè)底層的C函數(shù)菱皆?這就得在程序的運(yùn)行期才能知道,甚至挨稿,程序在運(yùn)行期仇轻,知道了該調(diào)用那一個(gè)底層函數(shù)后,我們還可以動態(tài)的去改變它叶组。由于OC具備這樣的相關(guān)功能拯田,也使其成了一種真正的動態(tài)開發(fā)語言。

在某對象調(diào)用某成員方法時(shí)候甩十,我們通常會這樣寫:

id return value = [someObject methodName: parameter]; 

在上述代碼中船庇,someObject 叫做消息的“接受者”吭产,messageName叫做“選擇子”。選擇子和參數(shù)合起來就稱之為一條“消息”鸭轮。當(dāng)編譯器看到了此條消息后臣淤,編譯器就會將其轉(zhuǎn)換一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用,所調(diào)用的函數(shù)就是消息傳遞機(jī)制中的和興函數(shù)窃爷,objc_msgSend邑蒋。該方法原型如下:

id returnValue = objc_msgSend(someObject, @selector(methodName:), parameter);

objc_msgSend函數(shù)會要依據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒ā榱耸菇邮照吣軠?zhǔn)確的調(diào)用到成員方法按厘,objc_msgSend會在接受者所屬類中去遍歷查找“方法列表”医吊,如果能找到與選擇子名稱相符的方法,就直接跳轉(zhuǎn)到其實(shí)現(xiàn)代碼逮京。如果找不到卿堂,就會沿著接受者所屬類的繼承體系繼續(xù)向上查找,等找到了合適的方法之后再進(jìn)行跳轉(zhuǎn)懒棉。如果最終還是未能找到與選擇子名稱相符的方法草描,那就執(zhí)行“消息轉(zhuǎn)發(fā)(message forwarding)”操作。

綜上所述策严,某類的實(shí)例對象在調(diào)用其成員方法的時(shí)候似乎需要執(zhí)行很多部的操作穗慕。可能我們會提出疑問妻导,如果該過程會執(zhí)行這么多步驟逛绵,那么在資源消耗上的開銷會不會很大呢?其實(shí)我們不用擔(dān)心這個(gè)問題栗竖,編譯器在執(zhí)行選擇子匹配的過程中暑脆,會將匹配的結(jié)果緩存到“快速映射表(fast map)”里面,切每個(gè)類都有這樣一塊緩存區(qū)狐肢。有了緩存區(qū)過戶添吗,方法調(diào)用的匹配過程就會變得非常的快速。當(dāng)然份名,話說回來碟联,即使有這種所謂的快速緩存區(qū),與之“靜態(tài)綁定的函數(shù)調(diào)用操作”相比較僵腺,它還是慢很多鲤孵,但OC是一門動態(tài)語言,這里且暫且不表辰如。

上述的介紹普监,知識描述了部分消息調(diào)用時(shí)的底層原理,在實(shí)際開發(fā)中,我們還可能會遇到其他一些“邊界情況”凯正,若如此情況毙玻,則需要交由OC運(yùn)行環(huán)境中的另一些函數(shù)來處理。我們這里只做簡單介紹廊散,不具體闡述桑滩。“邊界情況”如下:

  • objc_msgSend_stret: 如果待發(fā)送的消息的返回值是一個(gè)“結(jié)構(gòu)體(struct)”允睹,那么可交由此函數(shù)來處理运准。只有當(dāng)CPU的寄存器能夠容納的下消息返回?cái)?shù)據(jù)類型時(shí),這個(gè)函數(shù)才會處理此消息缭受。相反胁澳,則需由另一個(gè)函數(shù)執(zhí)行派發(fā)。此時(shí)米者,那個(gè)函數(shù)會通過分配在棧上的某個(gè)變量來處理消息返回的結(jié)構(gòu)體听哭。
  • objc_msgSend_fpret: 如果待發(fā)送消息的返回值是浮點(diǎn)數(shù),那么可交由此函數(shù)來處理塘雳。在某些架構(gòu)的CPU中調(diào)用函數(shù)時(shí),需要對“浮點(diǎn)數(shù)寄存器”做特殊處理普筹,也就是說败明,通常所用的objc_msgSend在這種情況下并不合適。這個(gè)函數(shù)是為了處理x86等架構(gòu)CPU中某些令人稍覺奇怪的狀況太防。
  • objc_msgSendSuper:如果需要給某實(shí)例對象所屬類的父類發(fā)送消息妻顶, 例如我們常用到的
[super message: parameter];

此時(shí)就可交由此函數(shù)來處理。
前面我們說到了蜒车,objc_msgSend等消息傳遞函數(shù)一旦找到了與之匹配的方法實(shí)現(xiàn)就會實(shí)現(xiàn)跳轉(zhuǎn)讳嘱,編譯器之所以會這么做,就是因?yàn)镺C中所有類的成員方法在底層都是標(biāo)準(zhǔn)的C語言函數(shù)酿愧,其原型如下:

<return_type> Class_selector(id self,  SEL,  _cmd,  ...);

真正的函數(shù)名可能和上述有所差別沥潭,這里我用“類(class)”和“選擇子(selector)”來命名是想其工作原理。OC中每一個(gè)類都有一張表嬉挡,表中的方法指針“methodPointer”都會只指向這樣的函數(shù)钝鸽,而選擇子的名稱則是方法列表中的“key”。objc_msgSend等函數(shù)正是通過這張表來尋找應(yīng)該執(zhí)行的方法庞钢,并跳轉(zhuǎn)至實(shí)現(xiàn)代碼中拔恰。

“消息傳遞”總結(jié):

  • 某實(shí)例對象調(diào)用其成員方法,稱之為給該對象傳遞一條消息基括。
  • 消息有接受者颜懊,選擇子以及參數(shù)列表構(gòu)成。給某實(shí)例對象“發(fā)送消息(invoke a message)”就是在改對象上調(diào)用方法“call a method”。
  • 發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)(dynamic message dispatch system)”來處理河爹,該系統(tǒng)會查出對應(yīng)的方法匠璧,并執(zhí)行其代碼。

二. 消息的轉(zhuǎn)發(fā)

上文我們了解了OC消息的傳遞機(jī)制昌抠,接下來我們來說說消息的轉(zhuǎn)發(fā)機(jī)制患朱。
很多時(shí)候,某實(shí)例對象在收到了無法解讀的消息之后炊苫,該怎么辦呢裁厅?
在開發(fā)過程中,我在某個(gè)類中申明了某個(gè)方法侨艾,但是我們不具體實(shí)現(xiàn)执虹,此時(shí)我們編譯我們的程序,我們會發(fā)現(xiàn)唠梨,編譯器并不會報(bào)錯(cuò)袋励。因?yàn)镺C門動態(tài)語言,我們可以在運(yùn)行期当叭,動態(tài)的向該類中添加我們剛剛申明的方法的具體實(shí)現(xiàn)代碼茬故。但是此時(shí),如果我們并沒有動態(tài)添加該方法的具體實(shí)現(xiàn)蚁鳖,也就是說磺芭,我們用某實(shí)例去調(diào)用該方法,但是該方法不能被正常調(diào)用醉箕,因?yàn)槲覀冇腥?shí)現(xiàn)它的具體邏輯钾腺,此時(shí)就會啟動“消息轉(zhuǎn)發(fā)機(jī)制”。

在我們的開發(fā)過程中讥裤,你可能遇見過經(jīng)由消息轉(zhuǎn)發(fā)流程處理的消息放棒,只是我們未加留意它。如果在控制臺看到以下報(bào)錯(cuò)提示己英,那就說明我們向某一實(shí)例發(fā)送了一條其無法解讀的消息间螟,從而啟動了消息轉(zhuǎn)發(fā)機(jī)制,并將此消息轉(zhuǎn)發(fā)給了NSObject的默認(rèn)實(shí)現(xiàn)剧辐『ィ控制打印如下:

- [__NSCFNumber lowercaseString]: unrecognized selector send to instance 0x87
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '- [__NSCFNumber lowercaseString]: unrecognized selector sent to instance 0x87'

上面控制臺答應(yīng)的消息是由NSObject的“doesNotRecognizeSelector:”方法所拋出的異常,此異常表明:消息的接受者的類型是 __NSCFNumber荧关, 而該接收者無法解讀名為“l(fā)owercacseString”的選擇子溉奕。在本例中,消息轉(zhuǎn)發(fā)過程以程序crash而告終忍啤,不過加勤,開發(fā)者在編寫自己的類的時(shí)候仙辟,可在轉(zhuǎn)發(fā)過程中設(shè)置掛鉤,用以執(zhí)行預(yù)定的邏輯鳄梅,而讓程序不會crash掉叠国。

好了,看完實(shí)例戴尸,我們在具體說說消息轉(zhuǎn)發(fā)的具體步驟粟焊。消息轉(zhuǎn)發(fā)可以分兩個(gè)階段。第一個(gè)階段先征詢接收者所屬的類孙蒙,看其能否動態(tài)添加方法项棠,以處理當(dāng)前這個(gè)未知選擇子。這個(gè)步驟叫做“動態(tài)方法解析(dynamic method resolution)”挎峦。第二階段則涉及到完整的消息轉(zhuǎn)發(fā)機(jī)制香追。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接收者自己就無法再以動態(tài)新增方法的手段來響應(yīng)包含該選擇子的消息了坦胶。此時(shí)透典,運(yùn)行期系統(tǒng)會請求接收者以其他手段來處理與消息相關(guān)的方法調(diào)用。這里又要分為兩個(gè)小步驟顿苇,首先峭咒,接收者會查看,看有沒有其他對象也就是“備用接收者(replacement receiver)”能處理該未知消息纪岁,如果有讹语,則運(yùn)行期系統(tǒng)會將此未知消息轉(zhuǎn)發(fā)給那個(gè)可執(zhí)行此消息的對象,于是消息轉(zhuǎn)發(fā)結(jié)束蜂科。如果接收者未能查看到備用接收者,則啟動完整消息轉(zhuǎn)發(fā)機(jī)制短条,運(yùn)行期系統(tǒng)會把與此未知消息有關(guān)的所有信息和細(xì)節(jié)都封裝到NSInvocation的實(shí)例對象中导匣,然后再給接收者一次機(jī)會,令其設(shè)法解決當(dāng)前未處理的這條消息茸时。

2.1 動態(tài)方法解析

上述過程就是整個(gè)消息轉(zhuǎn)發(fā)過程贡定,但是里面還有些知識點(diǎn),這給大家具體解釋下可都。首先我們看看上文中提及到的動態(tài)解析方法缓待。在消息轉(zhuǎn)發(fā)的第一個(gè)階段,當(dāng)實(shí)例對象收到無法解讀的消息時(shí)渠牲,首先會去征詢接收者所屬類旋炒,看其能否動態(tài)添加方法來處理該未知消息。此過程叫做“動態(tài)方法解析”签杈。
在對象收到無法解讀消息后瘫镇,首先會調(diào)用所屬類的下列方法:

+ (BOOL) resolveInstancemethod: (SEL) selector;

該方法的參數(shù),就是那個(gè)未知的選擇子。其返回值為布爾類型铣除,表示這個(gè)類是否能新增一個(gè)實(shí)例方法來處理該未知消息谚咬。還與這么一種情況,當(dāng)我們通過類直接調(diào)用其類方法的時(shí)候尚粘,切該類方法也是類當(dāng)前不能響應(yīng)的方法择卦,此時(shí)同樣,會去征詢類本身郎嫁,看其能否動態(tài)新增一個(gè)類方法來處理該未知消息秉继。此時(shí)調(diào)用的方法和上面類似"resolveClassMethod:"。

在使用這個(gè)方法的時(shí)候行剂,有一個(gè)前提秕噪,動態(tài)添加的方法的具體實(shí)現(xiàn),我們已經(jīng)提前編碼好了厚宰。只等在運(yùn)行期動態(tài)插入即可腌巾。這里我們舉個(gè)例子來說明, 當(dāng)然铲觉,文章最后我們會用一個(gè)完整的簡單例子來說明消息的傳遞和轉(zhuǎn)發(fā)機(jī)制澈蝙。這里,我們暫且先說這個(gè)動態(tài)插入撵幽。在開發(fā)的過程中灯荧,我們很多時(shí)候用@dynamic來修飾類的屬性,例如我們在訪問CoreData框架中的“NSManagerObjects”對象的屬性時(shí)盐杂,我們就可以用到動態(tài)插入逗载,因?yàn)閷?shí)現(xiàn)這些屬性的所需的setter和getter是在編譯器就能確定的。好了链烈,我們來看看如果用“resolveInstancemethod”方法來實(shí)現(xiàn)@dynamic屬性的:

id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);

+ (BOOL)resolveInstanceMethod:(SEL)selector {
    NSString *selectorString = NSStringFromSelector(selector);
    if (/* selector is from a @dynamic property */) {
        if ([selectorString hasPrefix:@"set"]) {   
            class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
        } else {
            class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }
    return [super resolveInstanceMethod: selector];
}

此段代碼首先是將選擇子轉(zhuǎn)為字符串厉斟,然后檢測其是否表示設(shè)置方法。如果前綴位"set"强衡,則表示為設(shè)置方法擦秽,否則就是獲取方法。不管是哪種情況漩勤,都會把處理改選擇子的方法加到類里面感挥。所添加的方法是用C函數(shù)實(shí)現(xiàn)的。C函數(shù)可能會用代碼來操作相關(guān)的數(shù)據(jù)結(jié)構(gòu)越败,類之中的屬性數(shù)據(jù)就就存放在那些數(shù)據(jù)結(jié)構(gòu)里面触幼。以coreData為例,這些存取方法也許要和后端數(shù)據(jù)庫通信究飞,以便獲取或更新相應(yīng)的值域蜗。

2.2 備用接收者

在消息轉(zhuǎn)發(fā)的第二階段巨双,接收者擁有第二次機(jī)會來處理未知消息,在這一步驟中霉祸,運(yùn)行期會判斷接收者能不能將此未知消息轉(zhuǎn)發(fā)給其他接收者來處理筑累。這個(gè)其他接收者就是備用接收者。此時(shí)會調(diào)用下面這個(gè)方法:

- (id) forwardingTargetForSelector: (SEL) selector;

該方法參數(shù)就是將要轉(zhuǎn)發(fā)的選擇子丝蹭。如果說接收者能找到能處理該未知選擇子的其他對象慢宗,則將其返回,若找不到奔穿,就返回nil镜沽。

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

如果說消息轉(zhuǎn)發(fā)算法已經(jīng)來到這一步的話,那么唯一能做的就是啟動完整消息轉(zhuǎn)發(fā)機(jī)制贱田。首先缅茉,創(chuàng)建一個(gè)NSInvocation對象,此對象包含有選擇子男摧,目標(biāo)以及參數(shù)蔬墩。然后將尚未處理的選擇子,及其先關(guān)全部細(xì)節(jié)都封裝于此對象中耗拓。在觸發(fā)NSInvocation對象時(shí)拇颅,“消息派發(fā)系統(tǒng)(message-dispatch-system)”將親自出馬,把消息派發(fā)給目標(biāo)對象乔询。其方法如下:

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

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


屏幕快照 2019-04-19 上午10.35.11.png

為了說明消息轉(zhuǎn)發(fā)機(jī)制的意義樟插,下面示范如何以動態(tài)方法解析來實(shí)現(xiàn) @dynamic 屬性。假設(shè)要編寫一個(gè)類似于“字典”的對象竿刁,他里面可以容納其他對象黄锤,只不過開發(fā)者要直接通過屬性來存取其中的數(shù)據(jù)。這個(gè)類的設(shè)計(jì)思路是:由開發(fā)者來添加屬性定義食拜,并將其聲明為 @dynamic 猜扮,而類則會處理自動處相關(guān)屬性的存取。
具體代碼如下:

.h文件內(nèi)容
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SxsAutoDictionary : NSObject
@property(nonatomic, copy) NSString *string;
@property(nonatomic, strong) NSNumber *number;
@property(nonatomic, copy) NSDate *date;
@property(nonatomic, strong) id opaqueObject;
@end
NS_ASSUME_NONNULL_END
//其實(shí)這些屬性都沒有任何的實(shí)際意義监婶,所以屬性的類型和屬性名稱你可以隨意設(shè)置。

.m文件內(nèi)容
#import <objc/runtime.h>
#import "SxsAutoDictionary.h"
@interface SxsAutoDictionary ()
@property(nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation SxsAutoDictionary
@dynamic string, number, date, opaqueObject;
- (id)init {
    if (self = [super init]) {
        _backingStore = [NSMutableDictionary dictionary];
    }
    return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selStrin = NSStringFromSelector(sel);
    if ([selStrin hasPrefix:@"set"]) {
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
    } else {
        class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
    }
    return YES;
}
id autoDictionaryGetter(id self, SEL _cmd) {
    //Get the backing store from the objcet
    SxsAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    //The key is simply the selector name
    NSString *key = NSStringFromSelector(_cmd);
    //Return the value
    return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value) {
    //Get the backing store from the objcet
    SxsAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStroe = typeSelf.backingStore;
    /**
     *  The selector will be for example , "setOpaqueObject".
     *  We need to remove the "set", ":", and lowercase the first letter of the remainder
     */
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    // Remove the ":" at the end
    [key deleteCharactersInRange: NSMakeRange(key.length - 1, 1)];
    // Remove the 'set' prefix
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    // lowercase the first character
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    if (value) {
        [backingStroe setObject:value forKey:key];
    } else {
        [backingStroe removeObjectForKey:key];
    }
}
@end

該類的實(shí)際調(diào)用:
SxsAutoDictionary *dic = [[SxsAutoDictionary alloc] init];
dic.string = @"Set a String";
NSLog(@"Print: %@", dic.string);

控制臺輸出結(jié)果:
2019-04-19 11:21:48.503465+0800 EffectiveObjective-C_TestApp[9036:2325913] Print: Set a String

消息轉(zhuǎn)發(fā)總結(jié):

  • 如果對象無法響應(yīng)某個(gè)選擇子齿桃,則進(jìn)入消息轉(zhuǎn)發(fā)流程惑惶。
  • 通過運(yùn)行期的動態(tài)方法解析,我們可以在需要用到某個(gè)方法時(shí)再講其動態(tài)插入到類中短纵。
  • 對象可以把無法響應(yīng)的選擇子轉(zhuǎn)發(fā)給其他備用對象來處理带污。
  • 經(jīng)過以上兩個(gè)步驟知乎,如果還是沒法處理未知消息香到,則啟用完整消息轉(zhuǎn)發(fā)機(jī)制鱼冀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末报破,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子千绪,更是在濱河造成了極大的恐慌充易,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荸型,死亡現(xiàn)場離奇詭異盹靴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瑞妇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門稿静,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辕狰,你說我怎么就攤上這事改备。” “怎么了蔓倍?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵悬钳,是天一觀的道長。 經(jīng)常有香客問我柬脸,道長他去,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任倒堕,我火速辦了婚禮灾测,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垦巴。我一直安慰自己媳搪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布骤宣。 她就那樣靜靜地躺著秦爆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憔披。 梳的紋絲不亂的頭發(fā)上等限,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音芬膝,去河邊找鬼望门。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锰霜,可吹牛的內(nèi)容都是我干的筹误。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼癣缅,長吁一口氣:“原來是場噩夢啊……” “哼厨剪!你這毒婦竟也來了哄酝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤祷膳,失蹤者是張志新(化名)和其女友劉穎陶衅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钾唬,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡万哪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抡秆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕巍。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖儒士,靈堂內(nèi)的尸體忽然破棺而出的止,到底是詐尸還是另有隱情,我是刑警寧澤着撩,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布诅福,位于F島的核電站,受9級特大地震影響拖叙,放射性物質(zhì)發(fā)生泄漏氓润。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一薯鳍、第九天 我趴在偏房一處隱蔽的房頂上張望咖气。 院中可真熱鬧,春花似錦挖滤、人聲如沸崩溪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伶唯。三九已至,卻和暖如春惧盹,著一層夾襖步出監(jiān)牢的瞬間乳幸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工钧椰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粹断,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓演侯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親背亥。 傳聞我的和親對象是個(gè)殘疾皇子秒际,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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