iOS之武功秘籍⑩: OC底層題目分析

iOS之武功秘籍 文章匯總

寫在前面

前面篇章說了那么多的原理,那本篇就拿說說OC相關(guān)的題目吧...

本節(jié)可能用到的秘籍Demo

一、Runtime Asssociate方法關(guān)聯(lián)的對象,需要我們手動釋放嗎?

當我們對象釋放時,會調(diào)用dealloc

  • 1、C++函數(shù)釋放 :objc_cxxDestruct
  • 2、移除關(guān)聯(lián)屬性:_object_remove_assocations
  • 3、將弱引用自動設(shè)置nilweak_clear_no_lock(&table.weak_table, (id)this);
  • 4、引用計數(shù)處理:table.refcnts.erase(this)
  • 5、銷毀對象:free(obj)

所以,關(guān)聯(lián)對象不需要我們手動移除,會在對象析構(gòu)即dealloc時釋放.

dealloc的原理詳解我們將在內(nèi)存管理章節(jié)詳細講解,這里先附上一張dealloc流程圖

二、方法的調(diào)用順序

類的方法 和 分類方法 重名,如和調(diào)用,是什么情況?

  • 如果同名方法是普通方法,包括initialize -- 先調(diào)用分類方法
    • 因為分類的方法是在類realize實現(xiàn)之后再attach進去的,插在類的方法的前面,所以優(yōu)先調(diào)用分類的方法(注意:不是分類覆蓋主類!?。?/li>
    • initialize方法什么時候調(diào)用? initialize方法也是主動調(diào)用,即第一次消息發(fā)送時調(diào)用,為了不影響整個load,可以將需要提前加載的數(shù)據(jù)寫到initialize
  • 如果同名方法是load方法 -- 先 主類load,后分類load(分類之間,看編譯的順序)可以參考iOS之武功秘籍⑧: 類和分類加載過程文章中的load_images原理分析

三、Runtime是什么?

  • runtime是由CC++匯編實現(xiàn)的一套API,為OC語言加入了面向?qū)ο?/code>、以及運行時的功能
  • 運行時是指將數(shù)據(jù)類型的確定由編譯時 推遲到了 運行時
    • 舉例:extensioncategory 的區(qū)別
  • 平時編寫的OC代碼,在程序運行的過程中,其實最終會轉(zhuǎn)換成runtimeC語言代碼, runtimeOC的幕后工作者

1、category 類別、分類

  • 專門用來給類添加新的方法
  • 不能給類添加成員屬性,添加了成員屬性,也無法取到
  • 注意:其實可以通過runtime 給分類添加屬性,即屬性關(guān)聯(lián),重寫setter、getter方法
  • 分類中用@property 定義變量,只會生成變量的setter、getter方法的聲明,不能生成方法實現(xiàn) 和 帶下劃線的成員變量

2、extension 類擴展

  • 可以說成是特殊的分類 ,也可稱作 匿名分類
  • 可以給類添加成員屬性,但是是私有變量
  • 可以給類添加方法,也是私有方法

四、方法的本質(zhì),sel是什么?IMP是什么?兩者之間的關(guān)系又是什么?

方法的本質(zhì):發(fā)送消息,發(fā)送消息會有以下幾個流程

  • 1.快速查找流程——通過匯編objc_msgSend查找緩存cache_t是否有imp實現(xiàn)
  • 2.慢速查找流程——通過C++lookUpImpOrForward遞歸查找當前類和父類的rwmethodlist的方法
  • 3.查找不到消息:動態(tài)方法解析——通過調(diào)用resolveInstanceMethodresolveClassMethod來動態(tài)方法決議——實現(xiàn)消息動態(tài)處理
  • 4.快速轉(zhuǎn)發(fā)流程——通過CoreFoundation來觸發(fā)消息轉(zhuǎn)發(fā)流程,forwardingTargetForSelector實現(xiàn)快速轉(zhuǎn)發(fā),由其他對象來實現(xiàn)處理方法
  • 5.慢速轉(zhuǎn)發(fā)流程——先調(diào)用methodSignatureForSelector獲取到方法的簽名,生成對應(yīng)的invocation;再通過forwardInvocation來進行處理

SEL是方法編號,也是方法名,在dyld加載鏡像到內(nèi)存時,通過_read_image方法加載到內(nèi)存的表中了
imp函數(shù)實現(xiàn)指針 ,找imp就是找函數(shù)的過程

SELIMP的關(guān)系就可以解釋為:

  • sel 就相當于本書的目錄標題
  • imp 就相當于書本的頁碼
  • 具體的函數(shù)就是具體頁碼對應(yīng)的內(nèi)容

比如我們想在《程序員的自我修養(yǎng)——鏈接、裝載與庫》一書中找到“動態(tài)鏈接”(SEL),肯定會翻到179頁(IMP),179頁會開始講述具體內(nèi)容(函數(shù)實現(xiàn))

五、能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量?

具體情況具體分析:

  • 編譯好的類不能添加實例變量
  • 運行時創(chuàng)建的類可以添加實例變量,但若已注冊到內(nèi)存中就不行了

原因:

  • 編譯好的實例變量存儲的位置在ro,而ro是在編譯時就已經(jīng)確定了的
  • ?旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定就?法修改
  • 只能修改rw中的方法或者可以通過關(guān)聯(lián)對象的方式來添加屬性

六、[self class]和[super class]的區(qū)別以及原理分析

  • [self class]就是發(fā)送消息objc_msgSend,消息接收者是self,方法編號class

  • [super class]本質(zhì)就是objc_msgSendSuper,消息的接收者還是self,方法編號 class,在運行時,底層調(diào)用的是_objc_msgSendSuper2

  • 只是objc_msgSendSuper2 會更快,直接跳過self的查找

代碼調(diào)試
TCJStudent中的init方法中打印這兩種class調(diào)用,TCJStudent繼續(xù)自TCJPerson.

打印結(jié)果如下

有點出乎意料,[self class]點進去來到NSObject.mm文件查看源碼

其底層是獲取對象的isa,當前的對象是TCJStudent,其isa是同名的TCJStudent,所以[self class]打印的是TCJStudent

[super class]中,其中super 是語法的 關(guān)鍵字,可以通過clangsuper的本質(zhì),這是編譯時的底層源碼,其中第一個參數(shù)是消息接收者,是一個__rw_objc_super結(jié)構(gòu)

底層源碼中搜索__rw_objc_super,是一個中間結(jié)構(gòu)體

objc4-818.2源碼中搜索objc_msgSendSuper,查看其隱藏參數(shù)

搜索struct objc_super

通過clang的底層編譯代碼可知,當前消息的接收者 等于 self,而self 等于 TCJStudent,所以 [super class]進入class方法源碼后,其中的selfinit后的實例對象,實例對象的isa指向的是本類,即消息接收者是TCJStudent本類.

我們再來看[super class]在運行時是否如上一步的底層編碼所示,是objc_msgSendSuper,打開匯編調(diào)試,調(diào)試結(jié)果如下

搜索objc_msgSendSuper2,從注釋得知,是從 類開始查找,而不是父類

查看objc_msgSendSuper2的匯編源碼,是從superclass中的cache中查找方法

所以,最完整的回答如下

[self class]方法調(diào)用的本質(zhì)是發(fā)送消息,調(diào)用class的消息流程,拿到元類的類型,在這里是因為類已經(jīng)加載到內(nèi)存,所以在讀取時是一個字符串類型,這個字符串類型是在map_imagesreadClass時已經(jīng)加入表中,所以打印為TCJStudent

[super class]打印的是TCJStudent,原因是當前的super是一個關(guān)鍵字,在這里只調(diào)用objc_msgSendSuper2,其實他的消息接收者和[self class]是一模一樣的,所以返回的是TCJStudent

七、內(nèi)存平移問題

① 原始題

程序能否運行?是否正常輸出?


運行結(jié)果與普通初始化對象一模一樣,可面試的時候不可能只說能或不能,還要說出個所以然來


[person saySomething]的本質(zhì)是對象發(fā)送消息,那么當前的person是什么?

  • personisa指向類TCJPersonperson的首地址 指向 TCJPerson的首地址,我們可以通過TCJPerson的內(nèi)存平移找到cache在cache中查找方法
  • [(__bridge id)obj saySomething]中的obj是來自于TCJPerson這個類,然后有一個指針obj,將其指向TCJPerson的首地址.

所以,person是指向TCJPerson類的結(jié)構(gòu),obj也是指向TCJPerson類的結(jié)構(gòu),然后都是在TCJPerson類中的methodList中查找方法.


② 拓展一

修改打印方法saySomething——不但打印方法,同時打印屬性cj_name

重新運行代碼,得到結(jié)果如下

為什么會出現(xiàn)打印不一致的情況?

其中person方式的cj_name是由于self指向person的內(nèi)存結(jié)構(gòu),然后通過內(nèi)存平移8字節(jié),取出去cj_name,即self指針首地址平移8字節(jié)獲得.

其中的cls方式中的obj指針中沒有其他的,所以obj表示8字節(jié)指針,self.cj_name的獲取,相當于obj首地址的指針也需要平移8字節(jié)找cj_name,那么此時的obj的指針地址是多少?平移8字節(jié)獲取的是什么?

obj是一個指針,是存在中的,棧是一個先進后出的結(jié)構(gòu),參數(shù)傳入就是一個不斷壓棧的過程,其中隱藏參數(shù)會壓入棧,且每個函數(shù)都會有兩個隱藏參數(shù)(id self,sel _cmd),可以通過clang查看底層編譯,隱藏參數(shù)壓棧的過程,其地址是遞減的,而棧是從高地址->低地址分配的,即在棧中,參數(shù)會從前往后一直壓.

super通過clang查看底層的編譯,是objc_msgSendSuper,其第一個參數(shù)是一個結(jié)構(gòu)體__rw_objc_super(self,class_getSuperclass),那么結(jié)構(gòu)體中的屬性是如何壓棧的?可以通過自定義一個結(jié)構(gòu)體,判斷結(jié)構(gòu)體內(nèi)部成員的壓棧情況

從打印結(jié)果可以得出20先加入,再加入10,因此結(jié)構(gòu)體內(nèi)部的壓棧情況是 低地址->高地址遞增的,棧中結(jié)構(gòu)體內(nèi)部的成員反向壓入棧,即低地址->高地址是遞增的.

所以到目前為止,棧中從高地址到低地址的順序的:self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - obj - person

  • self_cmdviewDidLoad方法的兩個隱藏參數(shù),是高地址->低地址正向壓棧的
  • class_getSuperClassselfobjc_msgSendSuper2中的結(jié)構(gòu)體成員,是從最后一個成員變量,即低地址->高地址反向壓棧的

那我們來打印下棧的存儲情況:
obj的棧的存儲情況

person的棧的存儲情況

objperson一起的棧的存儲情況


其中為什么class_getSuperclassViewController,因為objc_msgSendSuper2返回的是當前類,兩個self,并不是同一個self而是棧的指針不同,但是指向同一片內(nèi)存空間

  • [(__bridge id)obj saySomething]調(diào)用時,此時的objTCJPerson: 0x7ffee0d57048,所以saySomething方法中傳入的self 還是TCJPerson,但并不是我們通常認為的TCJPerson,是我們當前傳入的消息接收者,即TCJPerson: 0x7ffee0d57048,是TCJPerson的實例對象,此時的操作與普通的TCJPerson是一致的,即TCJPerson的地址內(nèi)存平移8字節(jié).
  • 普通person流程:person -> cj_name - 內(nèi)存平移8字節(jié)
  • obj流程:0x7ffee0d57048 + 0x80 -> 0x7ffee0d57050,即為self,指向<ViewController: 0x7ffee0d57050>

其中 personTCJPerson的關(guān)系是 person是以TCJPerson為模板的實例化對象,即alloc有一個指針地址,指向isa,isa指向TCJPerson,它們之間關(guān)聯(lián)是有一個isa指向.
obj也是指向TCJPerson的關(guān)系,編譯器會認為obj也是TCJPerson的一個實例化對象,即obj相當于isa,即首地址,指向TCJPerson,具有和person一樣的效果,簡單來說,我們已經(jīng)完全將編譯器騙過了,即obj也有cj_name.由于person查找cj_name是通過內(nèi)存平移8字節(jié),所以obj也是通過內(nèi)存平移8字節(jié)去查找cj_name.

③ 拓展二

修改viewDidLoad——在obj前面加個臨時字符串變量

同樣道理,在obj入棧前已經(jīng)有了temp變量,此時訪問self.cj_name就會訪問到temp

④ 拓展三

去掉臨時變量,TCJPerson類新增字符串屬性cj_hobby,打印方法改為打印cj_hobby,運行

ViewController就是obj偏移16字節(jié)拿到的super_class.

⑤ 拓展四

TCJPerson類新增字符串屬性cj_hobby,改成int類型,打印

這種情況就是野指針——指針偏移的offset不正確,獲取不到對應(yīng)變量的首地址.

八、Runtime是如何實現(xiàn)weak的,為什么可以自動置nil

  • 1、通過SideTable 找到我們的 weak_table
  • 2、weak_table 根據(jù) referent找到或者創(chuàng)建 weak_entry_t
  • 3、然后append_referrer(entry,referrer)將我的新弱引用的對象加進去entry
  • 4、最后 weak_entry_insert,把entry加入到我們的weak_table

weak一行打下斷點運行項目

Xcode菜單欄Debug->Debug Workflow->Always show Disassembly打上勾查看匯編——匯編代碼會來到libobjc庫的objc_initWeak

① weak創(chuàng)建過程

①.1 objc_initWeak

  • location:表示__weak指針的地址(我們研究的就是__weak指針指向的內(nèi)容怎么置為nil
  • newObj:所引用的對象,即例子中的person

①.2 storeWeak

  • HaveOldweak指針之前是否已經(jīng)指向了一個弱引用
  • HaveNewweak指針是否需要指向一個新引用
  • CrashIfDeallocating:如果被弱引用的對象正在析構(gòu),此時再弱引用該對象,是否應(yīng)該crash

storeWeak最主要的兩個邏輯點

由于是第一次調(diào)用,所以走haveNew分支——獲取到的是新的散列表SideTable,主要執(zhí)行了weak_register_no_lock方法來進行插入

①.3 weak_register_no_lock

  • 主要進行了isTaggedPointerdeallocating條件判斷
  • 將被弱引用對象所在的weak_table中的weak_entry_t哈希數(shù)組中取出對應(yīng)的weak_entry_t
  • 如果weak_entry_t不存在,則會新建一個并插入
  • 如果存在就將指向被弱引用對象地址的指針referrer通過函數(shù)append_referrer插入到對應(yīng)的weak_entry_t引用數(shù)組

①.4 append_referrer
找到弱引用對象的對應(yīng)的weak_entry_t哈希數(shù)組中插入

② weak創(chuàng)建流程

③ weak銷毀過程

由于弱引用在析構(gòu)dealloc時自動置空,所以查看dealloc的底層實現(xiàn)

  • _objc_rootDealloc->rootDealloc
  • rootDealloc->object_dispose
  • object_dispose->objc_destructInstance
  • objc_destructInstance->clearDeallocating
  • clearDeallocating->sidetable_clearDeallocating
  • weak_clear_no_lock->table.refcnts.erase

④ weak銷毀流程

九、利用runtime-API創(chuàng)建對象

① API介紹

①.1 動態(tài)創(chuàng)建類

①.2 添加成員變量

①.3 注冊到內(nèi)存

①.4 添加屬性變量

①.5 添加方法

② 整體使用

③ 注意事項

  • 記得導(dǎo)入<objc/runtime.h>
  • 添加成員變量class_addIvar必須在objc_registerClassPair前,因為注冊到內(nèi)存時ro已經(jīng)確定了,不能再往ivars添加
  • 添加屬性變量class_addProperty可以在注冊內(nèi)存前后,因為是往rw中添加的
  • class_addProperty中“屬性的屬性”——nonatomic/copy是根據(jù)屬性的類型變化而變化的
  • class_addProperty不會自動生成settergetter方法,因此直接調(diào)用KVC會崩潰
    • 不只可以通過KVC打印來檢驗,也可以下斷點查看ro、rw的結(jié)構(gòu)來檢驗

十、Method Swizzing坑點

① method-swizzling 是什么?

method-swizzling的含義是方法交換,其主要作用是在運行時將一個方法的實現(xiàn)替換成另一個方法的實現(xiàn),這就是我們常說的iOS黑魔法.

OC中就是利用method-swizzling實現(xiàn)AOP,其中AOP(Aspect Oriented Programming,面向切面編程)是一種編程的思想,區(qū)別于OOP(面向?qū)ο缶幊蹋?

  • OOPAOP都是一種編程的思想
  • OOP編程思想更加傾向于對業(yè)務(wù)模塊的封裝,劃分出更加清晰的邏輯單元
  • AOP面向切面進行提取封裝,提取各個模塊中的公共部分,提高模塊的復(fù)用率,降低業(yè)務(wù)之間的耦合性.

每個類都維護著一個方法列表,即methodListmethodList中有不同的方法即Method,每個方法中包含了方法的selIMP,方法交換就是將selimp原本的對應(yīng)斷開,并將sel新的IMP生成對應(yīng)關(guān)系.
如下圖所示,交換前后的selIMP的對應(yīng)關(guān)系

② method-swizzling涉及的相關(guān)API

  • 通過sel獲取方法Method
    • class_getInstanceMethod:獲取實例方法
    • class_getClassMethod:獲取類方法
  • method_getImplementation:獲取一個方法的實現(xiàn)
  • method_setImplementation:設(shè)置一個方法的實現(xiàn)
  • method_getTypeEncoding:獲取方法實現(xiàn)的編碼類型
  • class_addMethod:添加方法實現(xiàn)
  • class_replaceMethod:用一個方法的實現(xiàn),替換另一個方法的實現(xiàn),即aIMP 指向 bIMP,但是bIMP不一定指向aIMP
  • method_exchangeImplementations:交換兩個方法的實現(xiàn),即 aIMP -> bIMP, bIMP -> aIMP

③ 坑點1:method-swizzling使用過程中的一次性問題

所謂的一次性就是:mehod-swizzling寫在load方法中,而load方法會主動調(diào)用多次,這樣會導(dǎo)致方法的重復(fù)交換,使方法sel的指向又恢復(fù)成原來的imp的問題

解決方案

可以通過單例設(shè)計原則,使方法交換只執(zhí)行一次,在OC中可以通過dispatch_once實現(xiàn)單例

④ 坑點2:子類沒有實現(xiàn),父類實現(xiàn)了

  • 父類TCJPerson類中有-personInstanceMethod方法,子類TCJStudent類沒有重寫
  • 子類TCJStudent類新建分類做了方法交換,新方法中調(diào)用舊方法
  • TCJPerson類、TCJStudent類調(diào)用-personInstanceMethod

運行

子類打印出結(jié)果,而父類調(diào)用卻崩潰了,為什么會這樣呢?

  • [student personInstanceMethod];中不報錯是因為 student中的imp交換成了cj_studentInstanceMethod,而TCJStudent中有這個方法(在TCJ分類中),所以不會報錯.
  • 崩潰的點在于[person personInstanceMethod];,其本質(zhì)原因:TCJStudent的分類TCJ中進行了方法交換,將personimp 交換成了 TCJStudent中的cj_studentInstanceMethod,然后需要去 TCJPerson中的找cj_studentInstanceMethod,但是TCJPerson中沒有cj_studentInstanceMethod方法,即相關(guān)的imp找不到,所以就崩潰了

優(yōu)化:避免imp找不到

通過class_addMethod嘗試添加你要交換的方法

  • 如果添加成功,即類中沒有這個方法,則通過class_replaceMethod進行替換,其內(nèi)部會調(diào)用class_addMethod進行添加
  • 如果添加不成功,即類中有這個方法,則通過method_exchangeImplementations進行交換

這樣就不會報錯了.

下面是class_replaceMethod、class_addMethodmethod_exchangeImplementations的源碼實現(xiàn)

其中class_replaceMethodclass_addMethod中都調(diào)用了addMethod方法,區(qū)別在于bool值的判斷,下面是addMethod的源碼實現(xiàn)

⑤ 坑點3:子類沒有實現(xiàn),父類也沒有實現(xiàn),下面的調(diào)用有什么問題?

在上面測試代碼的基礎(chǔ)上加入父類TCJPersonpersonInstanceMethod的方法只寫了方法聲明,沒有方法實現(xiàn),卻做了方法交換——會造成死循環(huán)

原因是 棧溢出,遞歸死循環(huán)了,那么為什么會發(fā)生遞歸呢?----主要是因為 personInstanceMethod沒有實現(xiàn),然后在方法交換時,始終都找不到oriMethod,然后交換了寂寞,即交換失敗,當我們調(diào)用personInstanceMethod(oriMethod)時,也就是oriMethod會進入TCJ分類cj_studentInstanceMethod方法,然后這個方法中又調(diào)用了cj_studentInstanceMethod,此時的cj_studentInstanceMethod并沒有指向oriMethod ,然后導(dǎo)致了自己調(diào)自己,即遞歸死循環(huán)

優(yōu)化:避免遞歸死循環(huán)

如果oriMethod為空,為了避免方法交換沒有意義,而被廢棄,需要做一些事情

  • 通過class_addMethodoriSEL添加swiMethod方法
  • 通過method_setImplementationswiMethodIMP指向不做任何事的空實現(xiàn)

⑥ method-swizzling - 類方法

類方法和實例方法的method-swizzling的原理是類似的,唯一的區(qū)別是類方法存在元類中,所以可以做如下操作

  • 需要通過class_getClassMethod方法獲取類方法
  • 在調(diào)用class_addMethodclass_replaceMethod方法添加和替換時,需要傳入的類是元類,元類可以通過object_getClass方法獲取類的元類

⑦ method-swizzling的應(yīng)用

method-swizzling最常用的應(yīng)用是防止數(shù)組、字典等越界崩潰問題
iOSNSNumber、NSArrayNSDictionary等這些類都是類簇,一個NSArray的實現(xiàn)可能由多個類組成.所以如果想對NSArray進行Swizzling,必須獲取到其“真身”進行Swizzling,直接對NSArray進行操作是無效的.

下面列舉了NSArrayNSDictionary本類的類名,可以通過Runtime函數(shù)取出本類.

⑧ 注意事項

使用Method Swizzling有以下注意事項:

  • 盡可能在+load方法中交換方法
  • 最好使用單例保證只交換一次
  • 自定義方法名不能產(chǎn)生沖突
  • 對于系統(tǒng)方法要調(diào)用原始實現(xiàn),避免對系統(tǒng)產(chǎn)生影響
  • 做好注釋(因為方法交換比較繞)
  • 迫不得已情況下才去使用方法交換

寫在后面

和諧學(xué)習(xí),不急不躁.我還是我,顏色不一樣的煙火.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

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