Runloop&autorelease&事件傳遞&響應(yīng)鏈小節(jié)&UIResponder 分類與線程敝F活

預(yù)計(jì)內(nèi)容:
1、Runloop
2腰池、autorelease
3尾组、事件傳遞 & 響應(yīng)鏈(不一樣的 super 寓意)
4忙芒、UIResponder 分類與線程保活

關(guān)于以上內(nèi)容:不總結(jié)不知道讳侨、一總結(jié)內(nèi)容還真不少呵萨。

0x01 Runloop 小節(jié)

一、概念

(略過(guò))

二跨跨、監(jiān)聽(tīng)

代碼先行:

// Runloop 監(jiān)聽(tīng)回調(diào)函數(shù)
void hgRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}

// 創(chuàng)建一個(gè) Runloop 狀態(tài)監(jiān)聽(tīng)
- (void)runLoopObserverCreate {
    // 手動(dòng)創(chuàng)建一個(gè) observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, hgRunLoopObserverCallBack, NULL);
    // 添加到 MainRunLoop 中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}

以上的代碼還是比較簡(jiǎn)單的潮峦,但是五臟俱全,各個(gè)數(shù)據(jù)類型通過(guò)名字都能看出其意思歹叮。具體在項(xiàng)目中的使用可以參考 HGMonitorStuck 文件跑杭。 在開(kāi)發(fā)中可能比較關(guān)注的是 hgRunLoopObserverCallBack 中對(duì)所監(jiān)聽(tīng)到的不同狀態(tài)做不同的邏輯處理,關(guān)于 CFRunLoopActivity 的完整定義如下:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

這里需要注意的是這個(gè)枚舉類型是 *_OPTIONS 類型的咆耿。還有其它的數(shù)據(jù)類型,具體如下:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

這些具體的用法爹橱,直接搜索都有很多的萨螺,相關(guān)代碼,在這里:CF-1153.18.tar.gz

三愧驱、機(jī)制是什么

機(jī)制主要就是這張圖片慰技,就是運(yùn)行循環(huán),更多的信息可以看CF-1153.18.tar.gz

image.png

那么問(wèn)題來(lái)了:在 iOS 中哪里用到了 NSRunloop组砚,這個(gè)問(wèn)題的答案是:所有的地方都用到了吻商。比如將第二小節(jié)的代碼放到一個(gè)視圖控制器中調(diào)用,然后直接點(diǎn)擊屏幕糟红,都能監(jiān)聽(tīng)到其狀態(tài)的改變艾帐,這些東西都是很好理解的。

四盆偿、Runloop 總結(jié)

簡(jiǎn)單的介紹就到此結(jié)束了柒爸,上面除了代碼與圖片,貌似也沒(méi)有什么事扭。這種東西主要就是理解捎稚,要想理解那就看 CF-1153.18.tar.gz 中的代碼。更多的內(nèi)容求橄,比如與線程今野、定時(shí)器,還有其它的 Source0與 Source1都是什么關(guān)系罐农,這些網(wǎng)上隨便一搜都有条霜,但是盡量還是以看源代碼的邏輯為準(zhǔn)。

其實(shí)剛開(kāi)始想要介紹這個(gè) NSRunloop 的初衷是想直接介紹 autorelease 的啃匿,后來(lái)發(fā)現(xiàn)如果不先介紹 NSRunloop 的話蛔外,對(duì) autorelease 根本就解釋不通∏悖現(xiàn)在可以開(kāi)始對(duì) autorelease 小節(jié)的欣賞了。

0x02 autorelease 小節(jié)

一夹厌、概念

(略過(guò))

二豹爹、問(wèn)題

技術(shù)交流中可能會(huì)出現(xiàn)這樣的問(wèn)題:接收 autorelease 消息后的對(duì)象,是在什么時(shí)候被銷毀的矛纹?

關(guān)于這個(gè)問(wèn)題臂聋,是否會(huì)有小伙伴這樣考慮過(guò):這個(gè)是 MRC 環(huán)境的語(yǔ)法了,可以不用考慮或南。但是別忘了在 ARC 還有一個(gè)與之對(duì)應(yīng)的關(guān)鍵字是 __autoreleasing孩等,只能是理解了autorelease 之后就能能更好的理解 __autoreleasing

知道這個(gè)問(wèn)題的答案很簡(jiǎn)單采够,但完全弄清楚這個(gè)問(wèn)題需要對(duì) ****autorelease 與 NSRunloop 要有深層次的理解肄方。

別人的答案:

1、autoreleasepool不是一個(gè)大棧蹬癌,是分一個(gè)一個(gè)固定大小的page权她,雙向鏈表連起來(lái)的。

2逝薪、在runLoop睡眠之前釋放(kCFRunLoopBeforeWaiting)隅要,也就是說(shuō):如果在主線程開(kāi)啟一個(gè)自動(dòng)釋放池,那么就會(huì)在主線程即將進(jìn)入休眠的時(shí)候清理一遍自動(dòng)釋放池董济。如果是在子線程添加的自動(dòng)釋放池步清,那么就在子線程即將進(jìn)入休眠的時(shí)候清理.線程的runloop下次再開(kāi)始運(yùn)行循環(huán)的時(shí)候再去創(chuàng)建這些池子中的對(duì)象及釋放池就可以了。

這些答案都是有道理的虏肾,但是如何去理解這些答案才是關(guān)鍵廓啊。比如:雙向鏈表結(jié)構(gòu)的自動(dòng)緩存池對(duì)一個(gè) autorelease 的對(duì)象有什么影響,與 NSRunloop 又有什么關(guān)系询微,上面提到 在主線程即將進(jìn)入休眠的時(shí)候清理一遍自動(dòng)釋放池 里面提到的清理一遍緩存池崖瞭,是如何清除的?等等一系列的問(wèn)題將在下一小節(jié)詳細(xì)介紹撑毛。

三书聚、詳細(xì)介紹

3.1 緩存池

其實(shí),我意思是介紹 autorelease 藻雌,但是如果不介紹 AutoreleasePool 那肯定是沒(méi)有意義的雌续。在現(xiàn)在的網(wǎng)上的技術(shù)文章,幾乎都是一上來(lái)就介紹重點(diǎn)胯杭,反正我 剛開(kāi)始的時(shí)候是沒(méi)有看懂驯杜。

先介紹在沒(méi)有 NSRunloop 的參與下的 autorelease。那么就直接創(chuàng)建一個(gè)沒(méi)有 Runloop 的項(xiàng)目(終端項(xiàng)目)做个,首先就切換成 MRC 環(huán)境鸽心。

3.1.1 自動(dòng)釋放池的簡(jiǎn)單認(rèn)識(shí)

剛創(chuàng)建的項(xiàng)目代碼是這樣的:

image.png

直接轉(zhuǎn)成 cpp 代碼看一下:


image.png

因?yàn)榇a很簡(jiǎn)單滚局,所以轉(zhuǎn)成的 cpp 代碼也很簡(jiǎn)單,但是在上面的代碼中有我們想要的關(guān)鍵代碼:

__AtAutoreleasePool __autoreleasepool;

通過(guò)仔細(xì)的觀察發(fā)現(xiàn)所謂的自動(dòng)釋放池顽频,變成了一個(gè)大括號(hào)與這個(gè)局部變量的初現(xiàn)了藤肢。再通過(guò)全文搜索 __AtAutoreleasePool ,找到了其定義:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

原來(lái) __AtAutoreleasePool 是一個(gè) C++ 類型的結(jié)構(gòu)體糯景,那么問(wèn)題來(lái)了:對(duì)應(yīng)這個(gè)結(jié)構(gòu)體嘁圈,僅僅申明一個(gè)局部變量就可以了?是的蟀淮,很容易看到在這個(gè)結(jié)構(gòu)體的定義中有一個(gè)成員變量最住、一個(gè)無(wú)參構(gòu)造函數(shù)以及虛構(gòu)虛構(gòu),并在無(wú)參構(gòu)造函數(shù)中通過(guò)另一個(gè)函數(shù)給成員變量賦值怠惶,在虛構(gòu)函數(shù)中將這個(gè)成員變量放到另一個(gè)函數(shù)中涨缚。
根據(jù) C++的語(yǔ)法,可以模擬一下這個(gè)自動(dòng)釋放池最終在內(nèi)存中的偽代碼甚疟,應(yīng)該是這樣的:


image.png

可以自行的分析一下仗岖,自動(dòng)緩存池嵌套的情況,分析都是一樣的览妖。

到這里,對(duì)一個(gè)自動(dòng)釋放池應(yīng)該有一個(gè)基本的了解了揽祥,在上面的介紹中認(rèn)識(shí)了三個(gè)東西:
1讽膏、__AtAutoreleasePool 數(shù)據(jù)結(jié)構(gòu)
2、objc_autoreleasePoolPush() 函數(shù)
3拄丰、objc_autoreleasePoolPop(atautoreleasepoolobj) 函數(shù)

關(guān)于上面的兩個(gè)陌生函數(shù)府树,大家可以到源代碼中查看。

關(guān)于看源代碼料按,最近很多的小伙伴都在使用源代碼來(lái)分析各種的技術(shù)點(diǎn)奄侠,我有一個(gè)建議:盡量使用最新的版本,前不久我一直使用的 723 的载矿,昨天發(fā)現(xiàn)已經(jīng)更新到 750了垄潮,這是因?yàn)閯偪吹叫』锇閭兊奈恼拢l(fā)現(xiàn)代碼不一樣闷盔,仔細(xì)看才知道版本不對(duì)弯洗,當(dāng)然不管版本怎么更新,流程肯定都是相似的逢勾。僅僅是建議牡整。

3.1.2 AutoreleasePoolPage

如果到源代碼中看了哪兩個(gè)陌生的函數(shù),會(huì)看到一個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu) AutoreleasePoolPage溺拱,這是一個(gè) Class逃贝∫ゴ牵總之上面的兩個(gè)陌生函數(shù)實(shí)際調(diào)用的是AutoreleasePoolPage中的 Push() 與 pop 函數(shù)。
到這里可以理解成沐扳,自動(dòng)釋放池實(shí)際上就是在操作 AutoreleasePoolPage泥从。其實(shí)看到這里,如果到瀏覽器搜索一下 AutoreleasePoolPage 會(huì)發(fā)現(xiàn)迫皱,技術(shù)文章那是相當(dāng)?shù)呢S滿歉闰,但是很多的文章是直接介紹的,沒(méi)有像這篇文檔一樣還介紹了怎么找到這個(gè)數(shù)據(jù)結(jié)構(gòu)的卓起。
看到這里和敬,建議先到瀏覽器瀏覽一遍那些豐滿的文章,因?yàn)榻酉聛?lái)我就會(huì)直接介紹核心的東西了戏阅。也可以選擇在源代碼中按照這樣的順序全文搜索看一下:
1昼弟、objc_autoreleasePoolPush 與 objc_autoreleasePoolPop,主要看 NSObject.mm 文件中的奕筐。
2舱痘、AutoreleasePoolPage 中只要能看到下圖中的內(nèi)容就夠了。

image.png

3.1.3 自動(dòng)釋放池中的雙向鏈表

大概是這樣的:


image.png

是的离赫,具體的雙向鏈表大概就是這個(gè)樣子的了芭逝,每個(gè)節(jié)點(diǎn)就是一個(gè) Page,各個(gè) Page 就是通過(guò) parent 與 child 指針串起來(lái)的渊胸。
那么問(wèn)題來(lái)了:每個(gè) Page 的下半部分(大紅括號(hào))到底什么東西旬盯?可以在 AutoreleasePoolPage 中找到這樣的代碼:

image.png

由上圖,可以得出以下結(jié)論:
1翎猛、begin 返回的是 Page 成員變量所占的所有內(nèi)容地址的下一個(gè)指針位置胖翰。
2、end 就是整個(gè) Page 的末尾地址切厘。
那么就知道用大紅括號(hào)括起來(lái)的地方應(yīng)該是 SIZE - sizeof(*this) 了萨咳。關(guān)于 SIZE 的值,通過(guò)代碼的跟蹤疫稿,你會(huì)跟蹤到這個(gè)地方:


image.png

那么問(wèn)題又來(lái)了培他,這部分空間有什么作用?這部分就是為了在以后有對(duì)象發(fā)送 autorelease 的話而克,會(huì)將那個(gè)對(duì)象的地址直接依次的放入下面的地址中靶壮。可以先幻想一下將地址放到這里到底有什么作用员萍。

到這里腾降,對(duì)自動(dòng)釋放池的理解又進(jìn)了一公里了。

3.2 autorelease 后的變化

到這里碎绎,依舊直接到源代碼NSObject.mm 中看 autorelease 的實(shí)現(xiàn)螃壤。入口在這里:

image.png

看到了一個(gè)熟悉的東西:AutoreleasePoolPage抗果,在看的過(guò)程中也會(huì)發(fā)現(xiàn)只要是 isTaggedPointer() 為真的都直接 return 了,進(jìn)一步的證明 Tagged Pointer 的性能高是有一定的道理的奸晴,省去了很多的內(nèi)存操作冤馏。
看到上面的這張圖,要注意寄啼,最終調(diào)用的是 autorelease 函數(shù)逮光,還將當(dāng)前的 this 傳進(jìn)去了。依然一路的狂飆到這里:


image.png

這個(gè)方法墩划,大家猜都能猜到了涕刚,我大概的翻譯一下:
獲取當(dāng)前的 hotPage,如果這個(gè) page 有值并且不滿(full)的情況乙帮,直接添加(add)到這一頁(yè)杜漠,其次如果這個(gè)頁(yè)有值,那么就到 autoreleaseFullPage 中創(chuàng)建一個(gè)節(jié)點(diǎn)察净,然后串起來(lái)驾茴。否則通過(guò) autoreleaseNoPage 函數(shù)創(chuàng)建一個(gè)新的節(jié)點(diǎn)。
到這里氢卡,應(yīng)該完全的明白了上面的雙向鏈表的意思了锈至。總之在 autorelease 之后會(huì)將當(dāng)前的對(duì)象添加到一個(gè) Page中译秦,如果發(fā)現(xiàn)沒(méi)有任何的 Page裹赴,那么就創(chuàng)建第一個(gè) Page 節(jié)點(diǎn),如果有诀浪、但是已經(jīng)滿了,就創(chuàng)建下一個(gè) Page 節(jié)點(diǎn)延都,如果沒(méi)有滿雷猪、那么就直接添加到當(dāng)前的 Page 節(jié)點(diǎn)中。

3.2.1 _objc_autoreleasePoolPrint 打印看本質(zhì)

到這里可以借助一個(gè)私有函數(shù)來(lái)幫我們理解晰房,就是 _objc_autoreleasePoolPrint求摇,這個(gè)是私有函數(shù),可以直接使用殊者,但是需要在使用的地方這樣聲明一下:

extern void _objc_autoreleasePoolPrint(void);

然后這樣使用:


image.png

主要是看圖与境,上圖中可以看到當(dāng)前代碼之后內(nèi)存中自動(dòng)緩存池的數(shù)據(jù)布局,結(jié)合以上的介紹應(yīng)該會(huì)有一個(gè)比較綜合的認(rèn)識(shí)猖吴。從上圖中可以看出這個(gè)內(nèi)存布局摔刁,很清晰的就把每個(gè)自動(dòng)緩存池中的對(duì)象劃分了:


image.png

這個(gè)就代表開(kāi)始了,這個(gè)地方還是挺重要的海蔽,在上面瀏覽源代碼的過(guò)程中跳過(guò)了一個(gè)地方:

image.png

AutoreleasePoolPage中的 push 與 autorelease 中所做的事情貌似很像是共屈,僅僅是參數(shù)不同而已绑谣。到這里很輕松的就能想到,在 push 的時(shí)候是將 POOL_BOUNDARY 的東西弄進(jìn)去了拗引,autorelease 的時(shí)候是將當(dāng)前的對(duì)象弄進(jìn)去了借宵。POOL_BOUNDARY就是代表著上面每頁(yè)的開(kāi)始,關(guān)于POOL_BOUNDARY可以自行瀏覽矾削。再結(jié)合最最最開(kāi)始介紹的壤玫,在AutoreleasePoolPage中有一個(gè)成員變量 atautoreleasepoolobj ,這個(gè)變量是在自動(dòng)緩存池的開(kāi)始返回,然后是在自動(dòng)緩存池結(jié)束的地方放入到一個(gè)叫 Pop 的函數(shù)中萝勤,理解到這里似乎明白了 atautoreleasepoolobj 的用意了浅碾,估計(jì)在這個(gè) Pop 函數(shù)中拿到這個(gè)成員變量之后就是給當(dāng)前緩存池中的對(duì)象發(fā)送 release 的吧。為了證明這一點(diǎn)括改,再次看看這個(gè) pop 函數(shù),于是找到這個(gè)地方:


image.png

到了這里似乎明白了家坎,在 pop 中是通過(guò)逆向的遍歷一個(gè) Page 中的對(duì)象嘱能,然后發(fā)送 release 方法,一直遍歷到 POOL_BOUNDARY 的時(shí)候停止虱疏,因?yàn)檫@個(gè)地方是當(dāng)前 Page 的開(kāi)始惹骂。

image.png

這張圖片應(yīng)該還算形象,大紅箭頭表示 pop 以后 next 的移動(dòng)方向做瞪,向左的箭頭(除第三個(gè))代表一個(gè)緩存池的開(kāi)始位置对粪。當(dāng)?shù)谧罾锩娴木彺娉亟Y(jié)束的時(shí)候,會(huì)調(diào)用一下這個(gè)緩存池中的 pop装蓬,然后開(kāi)始通過(guò) next 逆向遍歷著拭,找到一個(gè) POOL_BOUNDARY 的時(shí)候就知道這個(gè)緩存池中的對(duì)象已經(jīng) release 結(jié)束了,當(dāng)最外層的緩存池結(jié)束的時(shí)候牍帚,同理儡遮。
看到這里,直接弄一個(gè)思考:


image.png

以上代碼后 log 打印順序是什么樣的暗赶?
name_11
name_10
中間的 @autoreleasepool 就要結(jié)束了
name_00
這樣對(duì)么鄙币?為什么不對(duì)?理解了這個(gè)問(wèn)題蹂随,那么上面的所有介紹應(yīng)該就已經(jīng)精通了十嘿。

3.2.2 autorelease 小節(jié)

到這里,在一個(gè)沒(méi)有 NSRunloop 的項(xiàng)目中對(duì) autorelease 就介紹結(jié)束了岳锁。如果沒(méi)有看明白的绩衷,那肯定是我的描述有問(wèn)題,只能是再看一遍或者多看源代碼。

3.3 有 Runloop 的 autorelease

在上面的介紹中唇聘,雖然只是一個(gè)簡(jiǎn)單代碼的介紹版姑,但是也差不多清楚了自動(dòng)釋放池(@autoreleasepool)的簡(jiǎn)單結(jié)構(gòu)以及 autorelease 消息發(fā)送之后的一些簡(jiǎn)單步驟。
在上面是在終端項(xiàng)目中使用了 兩個(gè) @autoreleasepool 來(lái)做介紹迟郎,在 @autoreleasepool 的開(kāi)始有一個(gè)Page 的 Push操作剥险,結(jié)束有一個(gè) Page 的 pop 操作。
那么換到 iOS 項(xiàng)目中怎么樣呢宪肖?首先需要清楚的一點(diǎn)是:iOS 的 App 是一直在 Runloop 中的表制,如果在不手動(dòng) @autoreleasepool {} 一個(gè)池子的話,那么整個(gè) APP 的運(yùn)行都是在 main 函數(shù)中的那個(gè)池子中控乾。在這里一定要注意 一個(gè)自動(dòng)釋放池 不等于一個(gè) AutoreleasePoolPage么介,這個(gè)僅僅是一個(gè) 池子中的其中一頁(yè)。從上面的介紹中蜕衡,不難看出也可以在不同的池子中共用一個(gè) AutoreleasePoolPage壤短。其實(shí)從源代碼中也可以看出,所有的池子都是在一個(gè)雙向鏈表中慨仿,只是不同的池子中的所有對(duì)象在鏈表中的不同地方久脯。
那么問(wèn)題來(lái)了:在 iOS 項(xiàng)目中,autorelease 對(duì)象在什么時(shí)候被釋放的镰吆?
關(guān)于這個(gè)問(wèn)題的答案帘撰,其實(shí)在網(wǎng)上都是使用上面的小節(jié)來(lái)回答的,說(shuō)到了雙鏈表万皿,也說(shuō)到了 AutoreleasePoolPage 的 Push 與 Pod摧找,但是到底執(zhí)行這些函數(shù)的時(shí)機(jī)是什么時(shí)候呢?有的說(shuō)是跳出離自己最近的大括號(hào)牢硅,這分明就是錯(cuò)誤的蹬耘,在上面的小節(jié)中已經(jīng)提到。也有說(shuō)是在運(yùn)行循環(huán)中减余,在睡眠之前清理了一遍婆赠,這個(gè)答案比較靠譜,單并不完整佳励。
先來(lái)看一個(gè)很經(jīng)典的現(xiàn)象吧:


image.png

看了這張圖,就知道了 NSRunloop 與 autorelease 的結(jié)合真的不簡(jiǎn)單蛆挫。
看到這個(gè)顯示赃承,已經(jīng)清楚的一個(gè)事實(shí)是:autorelease 對(duì)象的銷毀肯定是 AutoreleasePoolPage 中的 next 指針逆向移動(dòng)了。

接下來(lái)看一下在 主線程中的 Runloop 中都有什么信息悴侵,發(fā)現(xiàn)信息很多瞧剖,但是有一個(gè) callout 值得關(guān)注:_wrapRunLoopWithAutoreleasePoolHandler,可以看出這是在主線程中的一個(gè) observer 。關(guān)于這個(gè) observer 可以發(fā)現(xiàn) activities 的值由兩種: 0x1 與 0xa0抓于。通過(guò)對(duì) NSRunloop 的了解做粤,0x1 就是 kCFRunLoopEntry0xa0 就是 kCFRunLoopBeforeWaiting | kCFRunLoopExit捉撮。

直接上一張 ibireme 大佬 的文章節(jié)選吧:

image.png

在那些年看到這么金典的內(nèi)容怕品,只能一臉的懵逼,死記硬背的記住了答案巾遭。
至此肉康、關(guān)于 autorelease 的全部?jī)?nèi)容已經(jīng)介紹完了。文檔有些凌亂灼舍,細(xì)心的看的話吼和,應(yīng)該也會(huì)有所收獲的,感興趣的話就回頭再看一遍骑素。

0x03 事件 & 事件傳遞 & 響應(yīng)鏈

一炫乓、認(rèn)識(shí) UIResponder

在 iOS 開(kāi)發(fā)中很少看到 UIResponder 這個(gè)東西,但是都清楚 AppDelegate 献丑、 UIView 與UIViewController 都是直接繼承自她末捣,凡是繼承自她的,都叫響應(yīng)者[對(duì)象]阳距,都能接收與處理事件塔粒。在接下來(lái)的介紹中,不會(huì)特意的提 UIResponder筐摘,但是應(yīng)該清楚的是 事件傳遞與響應(yīng)鏈?zhǔn)请x不開(kāi) UIResponder 的卒茬。

二、觸摸方法

提到觸摸方法咖熟,肯定能想到如下四個(gè)方法:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

以上的四個(gè)方法特別的簡(jiǎn)單圃酵,大概的調(diào)用流程如下:


image.png

其中 B 節(jié)點(diǎn),如果不移動(dòng)的話就不會(huì)觸發(fā)馍管。所以就有如下兩種可能:
1郭赐、A-[B]-C1
2、A-[B]-C2
在這里需要注意的是确沸,在同一次操作中捌锭,各個(gè)方法中的兩個(gè)參數(shù)值是一樣的。
2.1 參數(shù)介紹
touches
是一個(gè) UITouch 集合罗捎,這個(gè)集合的數(shù)量就是當(dāng)前的觸摸由多少個(gè)觸摸點(diǎn)(手指的個(gè)數(shù))观谦。
UIEvent
這個(gè)就是具體的事件,雖然 UIEvent 中的屬性不是太多桨菜,但是包括了第一個(gè)參數(shù)的信息(allTouches)豁状。還有兩個(gè)比較關(guān)鍵的屬性:

@property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);

這就是事件類型捉偏,當(dāng)然當(dāng)前文檔主要介紹的是 UIEventTypeTouches。

三泻红、事件傳遞

為了接下來(lái)的介紹夭禽,我準(zhǔn)備了一個(gè)這樣的項(xiàng)目:responder

image.png

從上圖中可以清楚的了解到這些自定義視圖的父子關(guān)系,這些自定義視圖都是直接繼承于 UIView 的谊路。都重寫(xiě)了 touchesBegan 方法讹躯,都是這樣的:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
}

請(qǐng)不要問(wèn)為什么不弄一個(gè)基類,看到后面會(huì)明白的凶异。
現(xiàn)在可以順便亂點(diǎn)蜀撑,看看控制臺(tái)信息。主要關(guān)注圓圈圈區(qū)域:


image.png

經(jīng)過(guò)試驗(yàn)剩彬,應(yīng)該理解了大概的規(guī)律酷麦,可能比較難理解的就是 標(biāo)號(hào)為 4 的點(diǎn)擊。想必小伙伴們都會(huì)有自己的思考喉恋,這個(gè)簡(jiǎn)單的試驗(yàn)隱藏了 iOS 中的事件傳遞沃饶。接下來(lái)直接給出最終的解釋:
當(dāng)點(diǎn)擊屏幕的時(shí)候,首先是 UIApplication 收到這個(gè)事件轻黑,然后傳遞給 KeyWindow糊肤,然后通過(guò)控制器以及視圖相互的傳遞下去。對(duì)于父子控制器的順序是:父控制器傳給父控制器的視圖然后再傳給子控制器氓鄙。對(duì)于父子視圖的順序是:父視圖傳給子視圖馆揉。
比如點(diǎn)擊上面 標(biāo)號(hào) 2 的區(qū)域,其順序?yàn)椋篈ppDelegate --> UIApplication --> KeyWindow --> 控制器 --> HGVcView --> HGRedView抖拦。
同理 標(biāo)號(hào) 9 的順序?yàn)椋篈ppDelegate --> UIApplication --> KeyWindow --> 控制器 --> HGVcView --> HGGrayView --> HGYellowView.
那么問(wèn)題來(lái)了升酣,是所有的視圖都能傳遞事件么?不是的态罪,在以下的情況是不能傳遞事件的:
1噩茄、userInteractionEnabled = NO;
2、hidden = YES;
3复颈、alpha < 0.01
比如我將 HGGrayView 的 userInteractionEnabled = NO;绩聘,那么點(diǎn)擊標(biāo)號(hào)9的區(qū)域,事件是這樣的:AppDelegate --> UIApplication --> KeyWindow --> 控制器 --> HGVcView耗啦。 看完效果 記得將 HGGrayView 的 userInteractionEnabled = YES;
關(guān)于事件傳遞凿菩,還需要清楚的是,當(dāng)我點(diǎn)擊 標(biāo)號(hào)8 區(qū)域的時(shí)候帜讲,為什么是 HGGrayView 接收了事件呢蓄髓?首先要清楚一點(diǎn):在事件的傳遞過(guò)程中、這個(gè)傳遞對(duì)象是不處理事件的舒帮,除非找到了一個(gè)合適的響應(yīng)對(duì)象,也稱第一響應(yīng)者(First Responder)。那么什么樣的響應(yīng)者對(duì)象才算合適呢玩郊?需要滿足以下幾點(diǎn):
1肢执、這個(gè)響應(yīng)者對(duì)象已經(jīng)沒(méi)有可用的子響應(yīng)者對(duì)象
2、觸摸點(diǎn)在這個(gè)響應(yīng)者對(duì)象的空間范圍之類
到這里译红、關(guān)于事件的傳遞就簡(jiǎn)單的介紹結(jié)束了预茄。
看到這里,細(xì)心的小伙伴可能就會(huì)拋出一個(gè)疑問(wèn):如果將 HGGrayView 中的 touchesBegan 注釋之后侦厚,當(dāng)點(diǎn)擊 標(biāo)號(hào)8 區(qū)域的時(shí)候耻陕,盡然是 HGVcView 處理了這個(gè)事件,這是為什么呢刨沦?這個(gè)問(wèn)題的答案在下一小節(jié)诗宣,這個(gè)問(wèn)題已經(jīng)牽扯到了響應(yīng)鏈的概念。

四想诅、響應(yīng)鏈

在看響應(yīng)者鏈之前召庞,先來(lái)復(fù)習(xí)一下再 OC 中的 super 關(guān)鍵字吧。
復(fù)習(xí) super 中 来破。篮灼。。徘禁。诅诱。
復(fù)習(xí) super 中 。送朱。娘荡。。骤菠。

4.1 響應(yīng)鏈中的 super

當(dāng)你復(fù)習(xí)結(jié)束之后它改,想要問(wèn)問(wèn)你,上面提到touchesBegan商乎。央拖。。鹉戚。touchesEnded這些方法鲜戒,都是系統(tǒng)方法,按照道理來(lái)說(shuō)這種系統(tǒng)方法抹凳,蘋果是推薦在使用的時(shí)候調(diào)用 super 的遏餐。但是在開(kāi)發(fā)中,你有調(diào)用過(guò) super 么赢底?相反失都,一旦調(diào)用可能會(huì)出現(xiàn)一些莫名其妙的問(wèn)題柏蘑。
好的,接下來(lái)將 HGGrayView 中的 touchesBegan 打開(kāi)粹庞,并給 HGGrayView 添加一個(gè)自定義的父類 HGBaseView咳焚, 讓 HGGrayView 繼承于這個(gè) HGBaseView。在 HGBaseView 中重寫(xiě) touchesBegan 方法庞溜,然后在 HGGrayView 中調(diào)用 super革半。
最后你發(fā)現(xiàn)了什么?是不是 HGBaseView 中的 touchesBegan 根本沒(méi)有被調(diào)用流码,而是調(diào)用了 HGVcView 中的 touchesBegan又官、這就厲害了(畫(huà)線部分是我忘記將 HGGrayView 繼承于HGBaseView了,通過(guò) HGResponder 項(xiàng)目可以看出漫试,也許已經(jīng)有小伙伴發(fā)現(xiàn)了六敬。所以才沒(méi)有調(diào)用 HGBaseView 中的方法,實(shí)際上是會(huì)調(diào)用父類方法的商虐,會(huì)在父類方法中通過(guò) super 將事件傳給下一個(gè)響應(yīng)者對(duì)象)觉阅。通過(guò)對(duì)事件傳遞的理解,HGGrayView 的事件是由 HGVcView 傳遞來(lái)的秘车、差不多知道是怎么回事了典勇。
響應(yīng)鏈就是事件傳遞的反方向。比如在 HGYellowView 中的 touchesBegan 方法中添加如下代碼:

NSMutableString* stringM = [NSMutableString string];
 
UIResponder * r = self;
[stringM appendFormat:@"%@", NSStringFromClass(r.class)];
while (r) {
    r = r.nextResponder;
    [stringM appendFormat:@" --> %@", NSStringFromClass(r.class)];
}
 
NSLog(@"%@", stringM);

點(diǎn)擊 標(biāo)簽 9 區(qū)域的打印如下:
HGYellowView --> HGGrayView --> HGVcView --> ViewController --> UIWindow --> UIApplication --> AppDelegate --> (null)
到這里可以介紹在上面小節(jié)中提出的一個(gè)疑問(wèn):
如果將 HGGrayView 中的 touchesBegan 注釋之后叮趴,當(dāng)點(diǎn)擊標(biāo)號(hào)8 區(qū)域的時(shí)候割笙,盡然是 HGVcView 處理了這個(gè)事件,這是為什么呢眯亦?
這是因?yàn)樵谀J(rèn)的情況下 HGGrayView 中的事件通過(guò)響應(yīng)鏈傳給了 HGVcView伤溉,因?yàn)椴恢貙?xiě) touchesBegan 方法的話,默認(rèn)是這樣的:

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

4.2 響應(yīng)鏈小節(jié)

其實(shí)對(duì)應(yīng)響應(yīng)鏈的介紹到這里已經(jīng)結(jié)束了妻率。
總之乱顾,要理解好響應(yīng)鏈,那么必須要理解好事件傳遞宫静。這兩者的區(qū)別是走净,一個(gè)是屬于尋找的過(guò)程,一個(gè)事件執(zhí)行的過(guò)程孤里。他們的方向正好是相反的伏伯。還有一點(diǎn)是要正確的理解在響應(yīng)鏈的過(guò)程中對(duì) super 關(guān)鍵字的理解,之所以在以后的技術(shù)交流中又能在 super 這個(gè)技術(shù)點(diǎn)上添點(diǎn)油加點(diǎn)醋了捌袜。
到這里也應(yīng)該也能解釋當(dāng)點(diǎn)擊標(biāo)簽4 的現(xiàn)象了说搅,其實(shí)這個(gè)問(wèn)題是我在 15年的時(shí)候親自遇到的一個(gè) BUG,具體的現(xiàn)象是有一個(gè)按鈕虏等,在有的設(shè)備上只能下半部分點(diǎn)擊有反應(yīng)弄唧,在按鈕的下半部分點(diǎn)擊是沒(méi)有問(wèn)題的适肠,就是因?yàn)樯习氩糠殖隽烁缚丶恕?br> 這里有一些的技術(shù)交流中會(huì)出現(xiàn)的問(wèn)題:
如何讓UIView處理事件的同時(shí)繼續(xù)傳遞事件.
如何判斷一個(gè)UIView是View Controller的Root View
這些答案就顯得 手一日 了。
關(guān)于事件傳遞與響應(yīng)鏈候引,在開(kāi)發(fā)中很有可能會(huì)遇到這兩個(gè)系統(tǒng)方法迂猴,在現(xiàn)有項(xiàng)目中都有使用,可以看一下:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

關(guān)于時(shí)間傳遞與響應(yīng)鏈的內(nèi)容背伴,還能想到的是手勢(shì),還有 UIButton 的 Action 峰髓,暫時(shí)感覺(jué)這些除了都是由觸摸發(fā)起的之外傻寂,貌似沒(méi)有多大的關(guān)系,畢竟屬于不同的機(jī)制(可能是我的理解還不夠深入)携兵。比如在 UIButton 中會(huì)遇到這些現(xiàn)象:
1疾掰、按鈕添加手勢(shì),那么 action 就得不到執(zhí)行徐紧。
2静檬、按鈕沒(méi)有添加手勢(shì),想要屏蔽 action 的話就可以重寫(xiě) touchesBegan 不調(diào)用 super并级。
反正就是各種的沖突拂檩,但是話又說(shuō)回來(lái),一般情況誰(shuí)會(huì)在同一個(gè)控件上搞這些事件呢嘲碧,除非像圖片瀏覽器稻励,可以有點(diǎn)擊、可以有雙擊愈涩、也可以有旋轉(zhuǎn)望抽,但是這些都是屬于手勢(shì)范疇。在 LongLong Ago履婉,還會(huì)出現(xiàn) UIButton 的 superView 添加手勢(shì)的情況會(huì)影響到 UIButton煤篙,但是現(xiàn)在蘋果在很久之前就已經(jīng)優(yōu)化。
當(dāng)然在一些特殊的場(chǎng)景毁腿,可能手勢(shì)與觸摸沖突了辑奈,蘋果也已經(jīng)考慮到?jīng)_突的情況,所以也提供了這樣的 delegate 方法:

// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;

五狸棍、官方參考

在網(wǎng)上有很多大佬都在使用這張圖片來(lái)對(duì)響應(yīng)鏈做介紹:


image.png

這個(gè)對(duì)響應(yīng)鏈的解釋算是最權(quán)威的了身害,在GitHub 找到了一 大佬 的翻譯 Event-Handling-Guide-for-iOS, 可以參考一下草戈。

這里有一個(gè)最新的官方介紹塌鸯,請(qǐng)看這張圖片與上面的圖片是一樣的:

image.png

仔細(xì)觀察,這張圖與上面的那張圖還是有區(qū)別的唐片,多了一個(gè) UIApplicationDelegate丙猬。難道在很久之前的響應(yīng)鏈直接到 UIApplication 就結(jié)束了涨颜??這個(gè)就不是太清楚了茧球,確實(shí)在之前大佬們的介紹中沒(méi)有提到 UIApplicationDelegate 的庭瑰。但是同時(shí)也要清楚一點(diǎn) UIApplicationDelegate 是 Delegate 過(guò)來(lái)的。
更多的詳細(xì)信息在這里 using_responders_and_the_responder_chain_to_handle_events

0x04 金典案例

在這里有兩個(gè)例子:線程鼻缆瘢活與UIResponder分類分別介紹了 NSRunloop 與 UIRespomder 的應(yīng)用弹灭。具體的代碼在這里:UIResponder

一、UIResponder分類

核心代碼:

/**
 響應(yīng)鏈
  
 @param eventName 事件名稱
 @param eventInfo 事件信息
 */
- (void)routerEventWithName:(NSString *)eventName eventInfo:(NSDictionary *_Nullable)eventInfo {
    [[self nextResponder] routerEventWithName:eventName eventInfo:eventInfo];
}

突然一看死循環(huán)揪垄、仔細(xì)一看很經(jīng)典穷吮。這也是有使用場(chǎng)景的,這個(gè)使用場(chǎng)景也可用通知來(lái)實(shí)現(xiàn)饥努。具體的使用捡鱼,可以自行品味。

二酷愧、線程奔菡活

我們知道,一個(gè)線程只要是任務(wù)結(jié)束了溶浴,那么當(dāng)前的線程馬上就退出了乍迄。如果有一批的任務(wù)需要在一個(gè)單獨(dú)的線程中處理,但是又不想每次使用都創(chuàng)建一個(gè)新的線程戳葵,那么久需要考慮將同一個(gè)線程一直處于活躍狀態(tài)而不退出就乓。這個(gè)功能主要就是結(jié)合 NSRunloop 來(lái)處理。
具體的代碼拱烁,在這里:
代碼不多生蚁,但是技術(shù)點(diǎn)還真不少,主要集中于這三條:
1戏自、NSThread 的使用
2邦投、NSRunloop 的 run 方法
3、waitUntilDone參數(shù)選擇
接下來(lái)一一介紹擅笔。

2.1 NSThread 的使用

現(xiàn)在使用的是 initWithBlock 方法 很像 NSTimer志衣,那么第一個(gè)問(wèn)題來(lái)了,這個(gè) block 中可否使用如下的代碼:

__strong typeof(weakSelf) self = weakSelf;

答案是不可以的猛们,即使這樣使用了念脯,貌似還是內(nèi)存泄漏了,這個(gè)具體的原因有待進(jìn)一步的研究弯淘,應(yīng)該是這個(gè) Block 的內(nèi)部實(shí)現(xiàn) 很強(qiáng)大绿店,所以只能使用 weakSelf。
第二個(gè)問(wèn)題是:是否可以使用 initWithTarget?
其實(shí)這個(gè)也要注意假勿,我在之前的文檔中也介紹過(guò)借嗽,這種方式會(huì)像 NSTimer 一樣導(dǎo)致指針循環(huán), 但是還不好解決的樣子。我使用了 YYWeakProxy 都不行转培。恶导。。浸须。惨寿。
這樣看來(lái),這個(gè) NSThread 確實(shí)不簡(jiǎn)單删窒,有待進(jìn)一步的研究缤沦。所以當(dāng)前的實(shí)現(xiàn)來(lái)看、只可以使用 initWithBlock + weak 的方式易稠。

2.2 NSRunloop 的 run 方法

是的, NSRunloop 有三個(gè)去激活的方式:

- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

這里使用了 第三個(gè)方法包蓝,第二個(gè)方法不符合現(xiàn)在的場(chǎng)景驶社。那么第一個(gè)方法為什么不可以呢?這是因?yàn)榈谝粋€(gè)方法是永遠(yuǎn)停不下來(lái)的测萎,一旦 run 了亡电,那么這個(gè)線程就永遠(yuǎn)的保活了硅瞧,這個(gè)可以從蘋果對(duì)這個(gè)方法的注釋中可以看出份乒。所以只能放棄這種方式。
在看代碼的過(guò)程中腕唧,還有一個(gè)屬性 stopped或辖。關(guān)于這個(gè)屬性,是與 runMode 這個(gè)方法的機(jī)制息息相關(guān)的:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

這個(gè)方法的意思是將當(dāng)前的線程處于 run 的狀態(tài)在 beforeDate 之前枣接,distantFuture很大颂暇,所以能保持永遠(yuǎn)的處于 run 狀態(tài)的等待狀態(tài),但是有一個(gè)關(guān)鍵的點(diǎn)是:這個(gè)線程每執(zhí)行一次任務(wù)但惶,這個(gè)方法就返回了耳鸯。所以這里需要加一個(gè) stopped 屬性,當(dāng) runMode 返回之后再 runMode膀曾。
其次 while (weakSelf && !weakSelf.isStopped) 能換成 while (!weakSelf.isStopped) 答案是不可以的县爬,當(dāng) weakSelf 為 nil 的時(shí)候 !weakSelf.isStopped 的值為 YES. 所以。添谊。财喳。。碉钠。纲缓。

2.3 waitUntilDone參數(shù)選擇

主要在兩個(gè)地方使用了 performSelector:onThread:withObject:waitUntilDone: 方法卷拘,但是在 waitUntilDone 的參數(shù)卻各不相同,為啥祝高?
首先要明白這個(gè)參數(shù)的意義:代表從當(dāng)前的線程執(zhí)行另一個(gè)線程方法的時(shí)候栗弟,是否要等另一個(gè)線程的方法執(zhí)行結(jié)束之后,再返回工闺。為 YES 的時(shí)候代表必須要等另一個(gè)線程執(zhí)行結(jié)束了乍赫,才會(huì)返回。為 NO 的時(shí)候代表陆蟆,不等另一個(gè)線程的方法執(zhí)行結(jié)束雷厂,立馬就返回。
那這里為啥要使用 YES 呢叠殷?
這是因?yàn)檫@個(gè) __stop 的方法可能是在 HGPermenantThread 的 dealloc 方法中調(diào)用的改鲫。如果使用 NO 的話,那么HGPermenantThread 對(duì)象已經(jīng)沒(méi)有了林束,但是在 __stop 中又訪問(wèn)了 HGPermenantThread 對(duì)象像棘,所以壞內(nèi)存訪問(wèn)錯(cuò)誤了。

0x05 GCD 中的 Runloop 的 autorelease

直接看一段代碼:

NSLog(@"開(kāi)始上班了");
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"我應(yīng)該做點(diǎn)啥~");
});
NSLog(@"下班了");

可以嘗試回答一下這個(gè)打印順序是不是這樣的壶冒?

  • 開(kāi)始上班了
  • 下班了
  • 我應(yīng)該做點(diǎn)啥~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缕题,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胖腾,更是在濱河造成了極大的恐慌烟零,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咸作,死亡現(xiàn)場(chǎng)離奇詭異锨阿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)记罚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門群井,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人毫胜,你說(shuō)我怎么就攤上這事书斜。” “怎么了酵使?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵荐吉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我口渔,道長(zhǎng)样屠,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮痪欲,結(jié)果婚禮上悦穿,老公的妹妹穿的比我還像新娘。我一直安慰自己业踢,他們只是感情好栗柒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著知举,像睡著了一般瞬沦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雇锡,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天逛钻,我揣著相機(jī)與錄音,去河邊找鬼锰提。 笑死曙痘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的立肘。 我是一名探鬼主播屡江,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赛不!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起罢洲,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤踢故,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后惹苗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體殿较,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年桩蓉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淋纲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡院究,死狀恐怖洽瞬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情业汰,我是刑警寧澤伙窃,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站样漆,受9級(jí)特大地震影響为障,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一鳍怨、第九天 我趴在偏房一處隱蔽的房頂上張望呻右。 院中可真熱鬧,春花似錦鞋喇、人聲如沸声滥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)醒串。三九已至,卻和暖如春鄙皇,著一層夾襖步出監(jiān)牢的瞬間芜赌,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工伴逸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缠沈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓错蝴,卻偏偏與公主長(zhǎng)得像洲愤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顷锰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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