讀 Threading Programming Guide 筆記(三)

本文首發(fā)CSDN惑灵,如需轉(zhuǎn)載請(qǐng)與CSDN聯(lián)系山上。

記得第一次讀這個(gè)文檔還是3年前,那時(shí)也只是泛讀英支。如今關(guān)于iOS多線程的文章層出不窮佩憾,但我覺得若想更好的領(lǐng)會(huì)各個(gè)實(shí)踐者的文章,應(yīng)該先仔細(xì)讀讀官方的相關(guān)文檔干花,打好基礎(chǔ)妄帘,定會(huì)有更好的效果。文章中有對(duì)官方文檔的翻譯池凄,也有自己的理解寄摆,官方文檔中代碼片段的示例在這篇文章中都進(jìn)行了完整的重寫,還有一些文檔中沒有的代碼示例修赞,并且都使用Swift完成婶恼,給大家一些Objc與Swift轉(zhuǎn)換的參考。
官方文檔地址:Threading Programming Guide

何時(shí)使用Run Loop

前文中多次提到過柏副,在主線程中Run Loop是隨著應(yīng)用程序一起啟動(dòng)的勾邦,也就是說當(dāng)我們打開一個(gè)應(yīng)用時(shí),主線程中的Run Loop就已經(jīng)啟動(dòng)了割择,尤其現(xiàn)在我們都使用Xcode中的項(xiàng)目模版創(chuàng)建項(xiàng)目眷篇,更是不用考慮主線程中Run Loop的狀體。所以只有在二級(jí)線程中荔泳,也就是我們自己創(chuàng)建的線程中才有機(jī)會(huì)手動(dòng)的創(chuàng)建的Run Loop蕉饼,并對(duì)其進(jìn)行配置的操作。

在前文中還提到過玛歌,Run Loop在線程中的主要作用就是幫助線程常駐在進(jìn)程中昧港,并且不會(huì)過多消耗資源。所以說Run Loop在二級(jí)線程中也不是必須需要的支子,要根據(jù)該線程執(zhí)行的任務(wù)類型以及在整個(gè)應(yīng)用中擔(dān)任何作用而決定是否需要使用Run Loop创肥。比如說,如果你創(chuàng)建一個(gè)二級(jí)線程只是為了執(zhí)行一個(gè)不會(huì)頻繁執(zhí)行的一次性任務(wù),或者需要執(zhí)行很長時(shí)間的任務(wù)叹侄,那么可能就不需要使用Run Loop了巩搏。如果你需要一個(gè)線程執(zhí)行周期性的定時(shí)任務(wù),或者需要較為頻繁的與主線程之間進(jìn)行交互趾代,那么就需要使用Run Loop贯底。歸納一下需要使用Run Loop的情況大概有以下四點(diǎn):

  • 通過基于端口或自定義的數(shù)據(jù)源與其他線程進(jìn)行交互。
  • 在線程中執(zhí)行定時(shí)事件源的任務(wù)撒强。
  • 使用Cocoa框架提供的performSelector…系列方法禽捆。
  • 在線程中執(zhí)行較為頻繁的,具有周期性的任務(wù)尿褪。

光說不練假把式睦擂,下面就讓我們來看看如何具體創(chuàng)建得湘、配置杖玲、操作Run Loop。

Run Loop對(duì)象

要想操作配置Run Loop淘正,那自然需要通過Run Loop對(duì)象來完成摆马,它提供了一系列接口,可幫助我們便捷的添加Input sources鸿吆、timers以及觀察者囤采。較高級(jí)別的Cocoa框架提供了NSRunLoop類,較底層級(jí)別的Core Foundation框架提供了指向CFRunloopRef的指針惩淳。

獲取Run Loop對(duì)象

前文中提到過蕉毯,在Cocoa和Core Foundation框架中都沒有提供創(chuàng)建Run Loop的方法,只有從當(dāng)前線程獲取Run Loop的方法:

  • 在Cocoa框架中思犁,NSRunLoop類提供了類方法currentRunLoop()獲取NSRunLoop對(duì)象代虾。

    該方法是獲取當(dāng)前線程中已存在的Run Loop,如果不存在激蹲,那其實(shí)還是會(huì)創(chuàng)建一個(gè)Run Loop對(duì)象返回棉磨,只是Cocoa框架沒有向我們暴露該接口。

  • 在Core Foundation框架中提供了CFRunLoopGetCurrent()函數(shù)獲取CFRunLoop對(duì)象学辱。

雖然這兩個(gè)Run Loop對(duì)象并不完全等價(jià)乘瓤,它們之間還是可以轉(zhuǎn)換的,我們可以通過NSRunLoop對(duì)象提供的getCFRunLoop()方法獲取CFRunLoop對(duì)象策泣。因?yàn)?code>NSRunLoop和CFRunLoop指向的都是當(dāng)前線程中同一個(gè)Run Loop衙傀,所以在使用時(shí)它們可以混用,比如說要給Run Loop添加觀察者時(shí)就必須得用CFRunLoop了萨咕。

配置Run Loop觀察者

前文中提到過差油,可以向Run Loop中添加各種事件源和觀察者,這里事件源是必填項(xiàng),也就是說Run Loop中至少要有一種事件源蓄喇,不論是Input source還是timer发侵,如果Run Loop中沒有事件源的話妆偏,那么在啟動(dòng)Run Loop后就會(huì)立即退出叔锐。而觀察者是可選項(xiàng),如果沒有監(jiān)控Run Loop各運(yùn)行狀態(tài)的需求步责,可以不配置觀察者,這一節(jié)先看看如何向Run Loop中添加觀察者蔗包。

在Cocoa框架中,并沒有提供創(chuàng)建配置Run Loop觀察者的相關(guān)接口耻矮,所以我們只能通過Core Foundation框架中提供的對(duì)象和方法創(chuàng)建并配置Run Loop觀察者淘钟,下面我們看看示例代碼:

import Foundation

class TestThread: NSObject {
    
    func launch() {
        
        print("First event in Main Thread.")
        
        NSThread.detachNewThreadSelector("createAndConfigObserverInSecondaryThread", toTarget: self, withObject: nil)
        
        print(NSThread.isMultiThreaded())
        
        sleep(3)
        
        print("Second event in Main Thread.")
  
    }
    
    func createAndConfigObserverInSecondaryThread() {
        
        autoreleasepool{
            
            // 1
            let runloop = NSRunLoop.currentRunLoop()
            
            // 2
            var _self = self
            
            // 3
            var observerContext = CFRunLoopObserverContext(version: 0, info: &_self, retain: nil, release: nil, copyDescription: nil)
            
            // 4
            let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.AllActivities.rawValue, true, 0, self.observerCallbackFunc(), &observerContext)
            
            if(observer != nil) {
                
                // 5
                let cfRunloop = runloop.getCFRunLoop()
                
                // 6
                CFRunLoopAddObserver(cfRunloop, observer, kCFRunLoopDefaultMode)
                
            }
            
            // 7
            NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "fireTimer", userInfo: nil, repeats: true)
            
            var loopCount = 10
            
            repeat {
            
                // 8
                runloop.runUntilDate(NSDate(timeIntervalSinceNow: 1))
                
                loopCount--
            
            } while(loopCount > 0)
            
        }
        
    }
    
    func observerCallbackFunc() -> CFRunLoopObserverCallBack {
        
        return {(observer, activity, context) -> Void in
        
            switch(activity) {
                
            case CFRunLoopActivity.Entry:
                print("Run Loop已經(jīng)啟動(dòng)")
                break
            case CFRunLoopActivity.BeforeTimers:
                print("Run Loop分配定時(shí)任務(wù)前")
                break
            case CFRunLoopActivity.BeforeSources:
                print("Run Loop分配輸入事件源前")
                break
            case CFRunLoopActivity.BeforeWaiting:
                print("Run Loop休眠前")
                break
            case CFRunLoopActivity.AfterWaiting:
                print("Run Loop休眠后")
                break
            case CFRunLoopActivity.Exit:
                print("Run Loop退出后")
                break
            default:
                break
                
            }
        
        }
        
    }
    
    func fireTimer() {
        
    }
    
}

let testThread = TestThread()
testThread.launch()

下面解讀一下上述代碼示例毡琉,launch()方法在主線程中慧耍,通過NSThread類的類方法detachNewThreadSelector:toTarget:withObject:創(chuàng)建并啟動(dòng)一個(gè)二級(jí)線程煌珊,將createAndConfigObserverInSecondaryThread()方法作為事件消息傳入該二級(jí)線程,這個(gè)方法的主要作用就是在二級(jí)線程中創(chuàng)建配置Run Loop觀察者并啟動(dòng)Run Loop,然后讓主線程持續(xù)3秒畴博,以便二級(jí)線程有足夠的時(shí)間執(zhí)行任務(wù)俱病。

createAndConfigObserverInSecondaryThread()中共有8個(gè)關(guān)鍵步驟庶艾,下面一一進(jìn)行說明:

  • 第一步:通過NSRunLoop類的類方法currentRunLoop()獲取當(dāng)前線程的Run Loop袁余,這里獲取到的Run Loop對(duì)象是NSRunLoop對(duì)象。
  • 第二步:申明當(dāng)前對(duì)象的變量颖榜,至于為什么要這么做,在下一步中會(huì)有說明掩完。
  • 第三步:通過Core Foundation框架的CFRunLoopObserverContext結(jié)構(gòu)體構(gòu)造Run Loop觀察者上下文,大家需要注意前兩個(gè)參數(shù)且蓬,我們先看看這個(gè)結(jié)構(gòu)體:
public struct CFRunLoopObserverContext {
    public var version: CFIndex
    public var info: UnsafeMutablePointer<Void>
    public var retain: (@convention(c) (UnsafePointer<Void>) -> UnsafePointer<Void>)!
    public var release: (@convention(c) (UnsafePointer<Void>) -> Void)!
    public var copyDescription: (@convention(c) (UnsafePointer<Void>) -> Unmanaged<CFString>!)!
    public init()
    public init(version: CFIndex, info: UnsafeMutablePointer<Void>, retain: (@convention(c) (UnsafePointer<Void>) -> UnsafePointer<Void>)!, release: (@convention(c) (UnsafePointer<Void>) -> Void)!, copyDescription: (@convention(c) (UnsafePointer<Void>) -> Unmanaged<CFString>!)!)
}
  1. version:結(jié)構(gòu)體版本號(hào)欣硼,必須設(shè)置為0恶阴。
  2. info:上下文中retain焦匈、releasecopyDescription三個(gè)回調(diào)函數(shù)以及Run Loop觀察者的回調(diào)函數(shù)所有者對(duì)象的指針垦写。在Swift中彰触,UnsafePointer結(jié)構(gòu)體代表C系語言中申明為常量的指針梯澜,UnsafeMutablePoinger結(jié)構(gòu)體代表C系語言中申明為非常量的指針,比如說:
C:
void functionWithConstArg(const int *constIntPointer);

Swift:
func functionWithConstArg(constIntPointer: UnsafePointer<Int32>)

C:
void functionWithNotConstArg(unsigned int *unsignedIntPointer);

Swift:
func functionWithNotConstArg(unsignedIntPointer: UnsafeMutablePointer<UInt32>)

C:
void functionWithNoReturnArg(void *voidPointer);

Swift:
func functionWithNoReturnArg(voidPointer: UnsafeMutablePointer<Void>)
  • 第四步:通過Core Foundation框架的CFRunLoopObserverCreate函數(shù)創(chuàng)建CFRunLoopObserver對(duì)象:
public func CFRunLoopObserverCreate(allocator: CFAllocator!, _ activities: CFOptionFlags, _ repeats: Bool, _ order: CFIndex, _ callout: CFRunLoopObserverCallBack!, _ context: UnsafeMutablePointer<CFRunLoopObserverContext>) -> CFRunLoopObserver!
  1. allocator:該參數(shù)為對(duì)象內(nèi)存分配器渴析,一般使用默認(rèn)的分配器kCFAllocatorDefault晚伙。
  2. activities:該參數(shù)配置觀察者監(jiān)聽Run Loop的哪種運(yùn)行狀態(tài)。在示例中俭茧,我們讓觀察者監(jiān)聽Run Loop的所有運(yùn)行狀態(tài)咆疗。
  3. repeats:該參數(shù)標(biāo)識(shí)觀察者只監(jiān)聽一次還是每次Run Loop運(yùn)行時(shí)都監(jiān)聽。
  4. order:觀察者優(yōu)先級(jí)母债,當(dāng)Run Loop中有多個(gè)觀察者監(jiān)聽同一個(gè)運(yùn)行狀態(tài)時(shí)午磁,那么就根據(jù)該優(yōu)先級(jí)判斷,0為最高優(yōu)先級(jí)別毡们。
  5. callout:觀察者的回調(diào)函數(shù)迅皇,在Core Foundation框架中用CFRunLoopObserverCallBack重定義了回調(diào)函數(shù)的閉包。
  6. context:觀察者的上下文衙熔。
  • 第五步:因?yàn)?code>NSRunLoop沒有提供操作觀察者的接口登颓,所以我們需要getCFRunLoop()方法獲取到CFRunLoop對(duì)象。
  • 第六步:通過CFRunLoopAddObserver函數(shù)向當(dāng)前線程的Run Loop中添加創(chuàng)建好的觀察者:
func CFRunLoopAddObserver(_ rl: CFRunLoop!, _ observer: CFRunLoopObserver!, _ mode: CFString!)
  1. rl:當(dāng)前線程的CFRunLoop對(duì)象红氯。
  2. observer:創(chuàng)建好的觀察者框咙。
  3. mode:設(shè)置將觀察者添加到哪個(gè)Run Loop模式中。

這里需要注意的是痢甘,一個(gè)觀察者只能被添加到一個(gè)Run Loop中喇嘱,但是可以被添加到Run Loop中的多個(gè)模式中。

  • 第七步:通過Timer事件源向當(dāng)前線程發(fā)送重復(fù)執(zhí)行的定時(shí)任務(wù)塞栅,時(shí)間間隔為0.5秒者铜,因?yàn)橹皇菫榱藴y(cè)試觀察者,所以fireTimer()是一個(gè)空任務(wù)放椰。另外前文中提到過作烟,如果Run Loop中沒有任何數(shù)據(jù)源,那么Run Loop啟動(dòng)后會(huì)立即退出庄敛,所以大家可以把這行注釋了運(yùn)行看看會(huì)有什么效果俗壹。
  • 第八步:通過NSRunLoop對(duì)象的runUntilDate(limitDate: NSDate)方法啟動(dòng)Run Loop,設(shè)置Run Loop的運(yùn)行時(shí)長為1秒藻烤。這里將其放在一個(gè)循環(huán)里绷雏,最大循環(huán)次數(shù)為10次头滔,也就是說,如果不考慮主線程的運(yùn)行時(shí)間涎显,該二級(jí)線程的Run Loop可運(yùn)行10次坤检。

再來看看觀察者的回調(diào)方法observerCallbackFunc(),上面在介紹CFRunLoopObserverCreate函數(shù)時(shí)提到觀察者的回調(diào)函數(shù)是CFRunLoopObserverCallBack重定義的一個(gè)閉包期吓,我們來看看這個(gè)閉包:

typealias CFRunLoopObserverCallBack = (CFRunLoopObserver!, CFRunLoopActivity, UnsafeMutablePointer<Void>) -> Void

這個(gè)閉包沒有返回值早歇,第一個(gè)參數(shù)是觸發(fā)監(jiān)聽的觀察者,第二個(gè)參數(shù)是觀察者監(jiān)聽的Run Loop運(yùn)行狀態(tài)讨勤,第三個(gè)參數(shù)是觀察者的運(yùn)行上下文環(huán)境箭跳。所以在回調(diào)方法中,我們只需要根據(jù)第二個(gè)參數(shù)的值即可判斷觀察者監(jiān)聽到的Run Loop狀態(tài)潭千。大家可以拷貝上面的代碼谱姓,建一個(gè)Command Application運(yùn)行看看結(jié)果。

啟動(dòng)Run Loop

在啟動(dòng)Run Loop前務(wù)必要保證已添加一種類型的事件源刨晴,原因在前文中已提到多次屉来。在Cocoa框架和Core Foundation框架中啟動(dòng)Run Loop大體有三種形式,分別是無條件啟動(dòng)狈癞、設(shè)置時(shí)間限制啟動(dòng)茄靠、指定特定模式啟動(dòng)。

無條件啟動(dòng)

NSRunLoop對(duì)象的run()方法和Core Foundation框架中的CFRunLoopRun()函數(shù)都是無條件啟動(dòng)Run Loop的方式蝶桶。這種方式雖然是最簡單的啟動(dòng)方式慨绳,但也是最不推薦使用的一個(gè)方式,因?yàn)檫@種方式將Run Loop置于一個(gè)永久運(yùn)行并且不可控的狀態(tài)莫瞬,它使Run Loop只能在默認(rèn)模式下運(yùn)行儡蔓,無法給Run Loop設(shè)置特定的或自定義的模式郭蕉,而且以這種模式啟動(dòng)的Run Loop只能通過CFRunLoopStop(_ rl: CFRunLoop!)函數(shù)強(qiáng)制停止疼邀。

設(shè)置時(shí)間限制啟動(dòng)

該方式對(duì)應(yīng)的方法是NSRunLoop對(duì)象的runUntilDate(_ limitDate: NSDate)方法,在啟動(dòng)Run Loop時(shí)設(shè)置超時(shí)時(shí)間召锈,一旦超時(shí)那么Run Loop則自動(dòng)退出旁振。該方法的好處是可以在循環(huán)中反復(fù)啟動(dòng)Run Loop處理相關(guān)任務(wù),而且可控制運(yùn)行時(shí)長涨岁。

指定特定模式啟動(dòng)

該方式對(duì)應(yīng)的方法是NSRunLoop對(duì)象的runMode(_ mode: String, beforeDate limitDate: NSDate)方法和Core Foundation框架的CFRunLoopRunInMode(_ mode: CFString!, _ seconds: CFTimeInterval, _ returnAfterSourceHandled: Bool)函數(shù)拐袜。前者有兩個(gè)參數(shù),第一個(gè)參數(shù)是Run Loop模式梢薪,第二個(gè)參數(shù)仍然是超時(shí)時(shí)間蹬铺,該方法使Run Loop只處理指定模式中的事件源事件,當(dāng)處理完事件或超時(shí)Run Loop會(huì)退出秉撇,該方法的返回值類型是Bool甜攀,如果返回true則表示Run Loop啟動(dòng)成功秋泄,并分派執(zhí)行了任務(wù)或者達(dá)到超時(shí)時(shí)間,若返回false則表示Run Loop啟動(dòng)失敗规阀。后者有三個(gè)參數(shù)恒序,前兩個(gè)參數(shù)的作用一樣,第三個(gè)參數(shù)的意思是Run Loop是否在執(zhí)行完任務(wù)后就退出谁撼,如果設(shè)置為false歧胁,那么代表Run Loop在執(zhí)行完任務(wù)后不退出,而是一直等到超時(shí)后才退出厉碟。該方法返回Run Loop的退出狀態(tài):

  • CFRunLoopRunResult.Finished:表示Run Loop已分派執(zhí)行完任務(wù)喊巍,并且再無任務(wù)執(zhí)行的情況下退出。
  • CFRunLoopRunResult.Stopped:表示Run Loop通過CFRunLoopStop(_ rl: CFRunLoop!)函數(shù)強(qiáng)制退出箍鼓。
  • CFRunLoopRunResult.TimedOut:表示Run Loop因?yàn)槌瑫r(shí)時(shí)間到而退出玄糟。
  • CFRunLoopRunResult.HandledSource:表示Run Loop已執(zhí)行完任務(wù)而退出,改狀態(tài)只有在returnAfterSourceHandled設(shè)置為true時(shí)才會(huì)出現(xiàn)袄秩。

退出Run Loop

退出Run Loop的方式總體來說有三種:

  • 啟動(dòng)Run Loop時(shí)設(shè)置超時(shí)時(shí)間阵翎。
  • 強(qiáng)制退出Run Loop。
  • 移除Run Loop中的事件源之剧,從而使Run Loop退出郭卫。

第一種方式是推薦使用的方式,因?yàn)榭梢越oRun Loop設(shè)置可控的運(yùn)行時(shí)間背稼,讓它執(zhí)行完所有的任務(wù)以及給觀察者發(fā)送通知贰军。第二種強(qiáng)制退出Run Loop主要是應(yīng)對(duì)無條件啟動(dòng)Run Loop的情況。第三種方式是最不推薦的方式蟹肘,雖然在理論上說當(dāng)Run Loop中沒有任何數(shù)據(jù)源時(shí)會(huì)立即退出词疼,但是在實(shí)際情況中我們創(chuàng)建的二級(jí)線程除了執(zhí)行我們指定的任務(wù)外,有可能系統(tǒng)還會(huì)讓其執(zhí)行一些系統(tǒng)層面的任務(wù)帘腹,而且這些任務(wù)我們一般無法知曉贰盗,所以用這種方式退出Run Loop往往會(huì)存在延遲退出。

Run Loop對(duì)象的線程安全性

Run Loop對(duì)象的線程安全性取決于我們使用哪種API去操作阳欲。Core Foundation框架中的CFRunLoop對(duì)象是線程安全的舵盈,我們可以在任何線程中使用。Cocoa框架的NSRunLoop對(duì)象是線程不安全的球化,我們必須在擁有Run Loop的當(dāng)前線程中操作Run Loop秽晚,如果操作了不屬于當(dāng)前線程的Run loop,會(huì)導(dǎo)致異常和各種潛在的問題發(fā)生筒愚。

自定義Run Loop事件源

Cocoa框架因?yàn)槭禽^為高層的框架赴蝇,所以沒有提供操作較為底層的Run Loop事件源相關(guān)的接口和對(duì)象,所以我們只能使用Core Foundation框架中的對(duì)象和函數(shù)創(chuàng)建事件源并給Run Loop設(shè)置事件源巢掺。

創(chuàng)建Run Loop事件源對(duì)象

我們定義自己的Run Loop事件源首先就是需要?jiǎng)?chuàng)建事件源句伶,我們來看看創(chuàng)建事件源的方法:

func CFRunLoopSourceCreate(_ allocator: CFAllocator!, _ order: CFIndex, _ context: UnsafeMutablePointer<CFRunLoopSourceContext>) -> CFRunLoopSource!
  1. allocator:該參數(shù)為對(duì)象內(nèi)存分配器芍耘,一般使用默認(rèn)的分配器kCFAllocatorDefault
  2. order:事件源優(yōu)先級(jí)熄阻,當(dāng)Run Loop中有多個(gè)接收相同事件的事件源被標(biāo)記為待執(zhí)行時(shí)斋竞,那么就根據(jù)該優(yōu)先級(jí)判斷,0為最高優(yōu)先級(jí)別秃殉。
  3. context:事件源上下文坝初。

Run Loop事件源上下文很重要,我們來看看它的結(jié)構(gòu):

struct CFRunLoopSourceContext { 
    var version: CFIndex 
    var info: UnsafeMutablePointer<Void> 
    var retain: ((UnsafePointer<Void>) -> UnsafePointer<Void>)! 
    var release: ((UnsafePointer<Void>) -> Void)! 
    var copyDescription: ((UnsafePointer<Void>) -> Unmanaged<CFString>!)! 
    var equal: ((UnsafePointer<Void>, UnsafePointer<Void>) -> DarwinBoolean)! 
    var hash: ((UnsafePointer<Void>) -> CFHashCode)! 
    var schedule: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)! 
    var cancel: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)! 
    var perform: ((UnsafeMutablePointer<Void>) -> Void)! 
    init() 
    init(version version: CFIndex, info info: UnsafeMutablePointer<Void>, retain retain: ((UnsafePointer<Void>) -> UnsafePointer<Void>)!, release release: ((UnsafePointer<Void>) -> Void)!, copyDescription copyDescription: ((UnsafePointer<Void>) -> Unmanaged<CFString>!)!, equal equal: ((UnsafePointer<Void>, UnsafePointer<Void>) -> DarwinBoolean)!, hash hash: ((UnsafePointer<Void>) -> CFHashCode)!, schedule schedule: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)!, cancel cancel: ((UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void)!, perform perform: ((UnsafeMutablePointer<Void>) -> Void)!) 
}

該結(jié)構(gòu)體中我們需要關(guān)注的是前兩個(gè)和后三個(gè)屬性:

  1. version:事件源上下文的版本钾军,必須設(shè)置為0鳄袍。
  2. info:上下文中retainrelease吏恭、copyDescription拗小、equalhash樱哼、schedule哀九、cancelperform這八個(gè)回調(diào)函數(shù)所有者對(duì)象的指針搅幅。
  3. schedule:該回調(diào)函數(shù)的作用是將該事件源與給它發(fā)送事件消息的線程進(jìn)行關(guān)聯(lián)阅束,也就是說如果主線程想要給該事件源發(fā)送事件消息,那么首先主線程得能獲取到該事件源茄唐。
  4. cancel:該回調(diào)函數(shù)的作用是使該事件源失效息裸。
  5. perform:該回調(diào)函數(shù)的作用是執(zhí)行其他線程或當(dāng)前線程給該事件源發(fā)來的事件消息。

將事件源添加至Run Loop

事件源創(chuàng)建好之后沪编,接下來就是將其添加到指定某個(gè)模式的Run Loop中呼盆,我們來看看這個(gè)方法:

func CFRunLoopAddSource(_ rl: CFRunLoop!, _ source: CFRunLoopSource!, _ mode: CFString!)
  1. rl:希望添加事件源的Run Loop對(duì)象,類型是CFRunLoop蚁廓。
  2. source:我們創(chuàng)建好的事件源访圃。
  3. mode:Run Loop的模式。(可以回顧之前文章)

我們?cè)賮砜纯催@個(gè)方法都干了些什么:

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {
  
    .....
  
    __CFRunLoopSourceSchedule(rls, rl, rlm);
  
    .....

}

static void __CFRunLoopSourceSchedule(CFRunLoopSourceRef rls, CFRunLoopRef rl, CFRunLoopModeRef rlm) {  
    
    .....
    
    if (0 == rls->_context.version0.version) {
         if (NULL != rls->_context.version0.schedule) {
             rls->_context.version0.schedule(rls->_context.version0.info, rl, rlm->_name);
         }
    } 
    
    .....
    
}

從上述的代碼片段可以看出纳令,在CFRunLoopAddSource中調(diào)用了__CFRunLoopSourceSchedule內(nèi)部函數(shù)挽荠,而該函數(shù)中正是執(zhí)行了Run Loop事件源上下文中的schedule回調(diào)函數(shù)。也就是說當(dāng)把事件源添加到Run Loop中后就會(huì)將事件源與給它發(fā)送事件消息的線程進(jìn)行關(guān)聯(lián)平绩。

標(biāo)記事件源及喚醒Run Loop

前面的文章中說過,srouce0類型漠另,也就是非port類型的事件源都需要進(jìn)行手動(dòng)標(biāo)記捏雌,標(biāo)記完還需要手動(dòng)喚醒Run Loop,下面我們來看看這兩個(gè)方法:

func CFRunLoopSourceSignal(_ source: CFRunLoopSource!)

func CFRunLoopWakeUp(_ rl: CFRunLoop!)

這里需要注意的是喚醒Run Loop并不等價(jià)與啟動(dòng)Run Loop笆搓,因?yàn)閱?dòng)Run Loop時(shí)需要對(duì)Run Loop進(jìn)行模式性湿、時(shí)限的設(shè)置纬傲,而喚醒Run Loop只是當(dāng)已啟動(dòng)的Run Loop休眠時(shí)重新讓其運(yùn)行。

執(zhí)行Run Loop事件源的任務(wù)

喚醒Run Loop意味著讓休眠的Run Loop重新運(yùn)行肤频,那么我們就從啟動(dòng)Run Loop叹括,讓其開始運(yùn)行的方法看起:

extension NSRunLoop {

    .....

    public func runUntilDate(limitDate: NSDate) {
        while runMode(NSDefaultRunLoopMode, beforeDate: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
    }

    public func runMode(mode: String, beforeDate limitDate: NSDate) -> Bool {
        
        .....
        
        let limitTime = limitDate.timeIntervalSinceReferenceDate
        let ti = limitTime - CFAbsoluteTimeGetCurrent()
        CFRunLoopRunInMode(modeArg, ti, true)
        return true
    }

}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     
    
    .....
    
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, false);
    
    .....
    
    return result;
}

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, Boolean waitIfEmpty) {

    .....
    
    __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    
    .....
    
}

static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) {

    CFTypeRef sources = NULL;
    
    .....
    
    if (__CFRunLoopSourceIsSignaled(rls)) {
    
        .....
    
        rls->_context.version0.perform(rls->_context.version0.info);
        
        .....
    
    }
    
    .....

}

從上述代碼片段中可以看出,當(dāng)Run Loop運(yùn)行后會(huì)調(diào)用內(nèi)部函數(shù)__CFRunLoopDoSources0執(zhí)行自定義事件源的任務(wù)宵荒,在執(zhí)行之前會(huì)通過內(nèi)部函數(shù)__CFRunLoopSourceIsSignaled(rls)判斷事件源是否已被標(biāo)記為待執(zhí)行汁雷,然后執(zhí)行Run Loop事件上下文中的perform回調(diào)函數(shù)。

移除Run Loop事件源

當(dāng)我們自定義的事件源完成使命后就可以將其從Run Loop中移除报咳,我們來看看對(duì)應(yīng)的方法:

func CFRunLoopRemoveSource(_ rl: CFRunLoop!, _ source: CFRunLoopSource!, _ mode: CFString!)

void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {
    
    .....
    
    __CFRunLoopSourceCancel(rls, rl, rlm);
    
    .....

}

static void __CFRunLoopSourceCancel(CFRunLoopSourceRef rls, CFRunLoopRef rl, CFRunLoopModeRef rlm) {
    if (0 == rls->_context.version0.version) {
          if (NULL != rls->_context.version0.cancel) {
              rls->_context.version0.cancel(rls->_context.version0.info, rl, rlm->_name);
          }
    }
    
    .....
    
}

從上述代碼片段可以看出侠讯,當(dāng)我們調(diào)用了CFRunLoopRemoveSource方法后,其實(shí)是執(zhí)行了Run Loop事件源上下文中的cancel回調(diào)函數(shù)暑刃。

自定義Run Loop事件源的實(shí)際運(yùn)用

在講解示例之前厢漩,我們先來看看示例Demo的效果:

LearnThread-5

在這個(gè)示例中,創(chuàng)建了兩個(gè)自定義事件源岩臣,一個(gè)添加到主線程中溜嗜,另一個(gè)添加到二級(jí)線程中。主線程給二級(jí)線程中的自定義事件源發(fā)送事件消息架谎,目的是讓其改變所有UICollectionViewCell的透明度粱胜,當(dāng)二級(jí)線程收到事件消息后執(zhí)行計(jì)算每個(gè)UICollectionViewCell透明度的任務(wù),然后再給主線程的自定義事件源發(fā)送事件消息狐树,讓其更新UICollectionViewCell的透明度并顯示焙压。下面來看看類圖:

LearnThread-6

整個(gè)工程一共就這六個(gè)類:

  • MainCollectionViewController:程序主控制器,啟動(dòng)程序抑钟、展示UI及計(jì)算UICollectionViewCell透明度的相關(guān)方法涯曲。
  • MainThreadRunLoopSource:主線程自定義事件源管理對(duì)象,負(fù)責(zé)初始化事件源在塔,將事件源添加至指定線程幻件,標(biāo)記事件源并喚醒指定Run Loop以及包含上文中說過的事件源最主要的三個(gè)回調(diào)方法。
  • MainThreadRunLoopSourceContext:主線程自定義事件源上下文蛔溃,可獲取到對(duì)應(yīng)的事件源及添加了該事件源的Run Loop绰沥。
  • SecondaryThreadRunLoopSource:二級(jí)線程自定義事件源管理對(duì)象,負(fù)責(zé)初始化事件源贺待,將事件源添加至指定線程徽曲,標(biāo)記事件源并喚醒指定Run Loop以及包含上文中說過的事件源最主要的三個(gè)回調(diào)方法。
  • SecondaryThreadRunLoopSourceContext:二級(jí)線程自定義事件源上下文麸塞,可獲取到對(duì)應(yīng)的事件源及添加了該事件源的Run Loop秃臣。
  • AppDelegate:應(yīng)用程序代理類,這里零時(shí)充當(dāng)為各自定義事件源回調(diào)方法執(zhí)行內(nèi)容的管理類。

下面我按照程序的運(yùn)行順序一一對(duì)這些類及屬性和方法進(jìn)行簡單說明奥此。

程序開始運(yùn)行

MainCollectionViewController類中與UI展示相關(guān)的方法在這里就不再累贅了弧哎。點(diǎn)擊Start按鈕,調(diào)用start()方法稚虎,初始化MainThreadRunLoopSource對(duì)象撤嫩,在這個(gè)過程中初始化了CFRunLoopSourceContext對(duì)象并且創(chuàng)建CFRunLoopSource對(duì)象以及初始化該事件源的指令池:

let mainThreadRunLoopSource = MainThreadRunLoopSource()
        
mainThreadRunLoopSource.addToCurrentRunLoop()
var runloopSourceContext = CFRunLoopSourceContext(version: 0, info: unsafeBitCast(self, UnsafeMutablePointer<Void>.self), retain: nil, release: nil, copyDescription: nil, equal: nil, hash: nil, schedule: runloopSourceScheduleRoutine(), cancel: runloopSourceCancelRoutine(), perform: runloopSourcePerformRoutine())
        
runloopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &runloopSourceContext)
        
commandBuffer = Array<SecondaryThreadRunLoopSourceContext>()

這里需要注意的是CFRunLoopSourceContextinit方法中的第二個(gè)參數(shù)和CFRunLoopSourceCreate方法的第三個(gè)參數(shù)都是指針,那么在Swift中蠢终,將對(duì)象轉(zhuǎn)換為指針的方法有兩種:

  • 使用unsafeBitCast方法序攘,該方法會(huì)將第一個(gè)參數(shù)的內(nèi)容按照第二個(gè)參數(shù)的類型進(jìn)行轉(zhuǎn)換。一般當(dāng)需要對(duì)象與指針來回轉(zhuǎn)換時(shí)使用該方法蜕径。
  • 在對(duì)象前面加&符號(hào)两踏,表示傳入指針地址。

當(dāng)主線程的自定義事件源初始化完成之后兜喻,調(diào)用addToCurrentRunLoop()方法梦染,將事件源添加至當(dāng)前Run Loop中,即主線程的Run Loop:

let cfrunloop = CFRunLoopGetCurrent()
        
if let rls = runloopSource {
            
    CFRunLoopAddSource(cfrunloop, rls, kCFRunLoopDefaultMode)
            
}

接下來創(chuàng)建二級(jí)線程朴皆,并且讓其執(zhí)行二級(jí)線程的配置任務(wù):

let secondaryThread = NSThread(target: self, selector: "startThreadWithRunloop", object: nil)
        
secondaryThread.start()

在二級(jí)線程中同樣初始化自定義事件源帕识,并將將其添加至二級(jí)線程的Run Loop中,然后啟動(dòng)Run Loop:

func startThreadWithRunloop() {
        
    autoreleasepool{
            
        var done = false
            
        let secondaryThreadRunLoopSource = SecondaryThreadRunLoopSource()
            
        secondaryThreadRunLoopSource.addToCurrentRunLoop()
            
        repeat {
                
            let result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 5, true)
                
            if ((result == CFRunLoopRunResult.Stopped) || (result == CFRunLoopRunResult.Finished)) {
                    
                done = true;
                    
            }
                
        } while(!done)
            
    }
        
}

執(zhí)行事件源的schedule回調(diào)函數(shù)

前文中說過將事件源添加至Run Loop后會(huì)觸發(fā)事件源的schedule回調(diào)函數(shù)遂铡,所以當(dāng)執(zhí)行完mainThreadRunLoopSource.addToCurrentRunLoop()這句代碼后肮疗,便會(huì)觸發(fā)主線程自定義事件源的schedule回調(diào)函數(shù):

func runloopSourceScheduleRoutine() -> @convention(c) (UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void {
        
    return { (info, runloop, runloopMode) -> Void in
        
        let mainThreadRunloopSource = unsafeBitCast(info, MainThreadRunLoopSource.self)
            
        let mainThreadRunloopSourceContext = MainThreadRunLoopSourceContext(runloop: runloop, runloopSource: mainThreadRunloopSource)
            
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
            
        appDelegate.performSelector("registerMainThreadRunLoopSource:", withObject: mainThreadRunloopSourceContext)
        
    }
        
}

這里還需注意的是在Swift2.0中,如果一個(gè)作為回調(diào)函數(shù)方法的返回類型是指向函數(shù)的指針扒接,這類指針可以轉(zhuǎn)換為閉包伪货,并且要在閉包前面加上@convention(c)標(biāo)注。在runloopSourceScheduleRoutine()方法中钾怔,獲取到主線程事件源對(duì)象并初始化事件源上下文對(duì)象碱呼,然后將該事件源上下文對(duì)象傳給AppDelegate的對(duì)應(yīng)方法注冊(cè)該事件源上下文對(duì)象:

func registerMainThreadRunLoopSource(runloopSourceContext: MainThreadRunLoopSourceContext) {
        
    mainThreadRunloopSourceContext = runloopSourceContext
        
}

自然當(dāng)在二級(jí)線程中執(zhí)行完secondaryThreadRunLoopSource.addToCurrentRunLoop()這句代碼后,也會(huì)觸發(fā)二級(jí)線程自定義事件源的schedule回調(diào)函數(shù):

func runloopSourceScheduleRoutine() -> @convention(c) (UnsafeMutablePointer<Void>, CFRunLoop!, CFString!) -> Void {
        
    return { (info, runloop, runloopMode) -> Void in
            
        let secondaryThreadRunloopSource = unsafeBitCast(info, SecondaryThreadRunLoopSource.self)
            
        let secondaryThreadRunloopSourceContext = SecondaryThreadRunLoopSourceContext(runloop: runloop, runloopSource: secondaryThreadRunloopSource)
            
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
            
        appDelegate.performSelectorOnMainThread("registerSecondaryThreadRunLoopSource:", withObject: secondaryThreadRunloopSourceContext, waitUntilDone: true)
            
    }
        
}

這里要注意的是宗侦,在該方法中同樣是將二級(jí)線程事件源上下文對(duì)象傳給了AppDelegate的對(duì)應(yīng)方法愚臀,但是這里用了performSelectorOnMainThread方法,讓其在主線程中執(zhí)行矾利,目的在于注冊(cè)完上下文對(duì)象后就接著從主線程給二級(jí)線程發(fā)送事件消息了姑裂,其實(shí)我將這里作為了主線程觸發(fā)二級(jí)線程執(zhí)行任務(wù)的觸發(fā)點(diǎn):

func registerSecondaryThreadRunLoopSource(runloopSourceContext: SecondaryThreadRunLoopSourceContext) {
        
    secondaryThreadRunloopSourceContext = runloopSourceContext
        
    sendCommandToSecondaryThread()
        
}
    
func sendCommandToSecondaryThread() {
        
    secondaryThreadRunloopSourceContext?.runloopSource?.commandBuffer?.append(mainThreadRunloopSourceContext!)
        
    secondaryThreadRunloopSourceContext?.runloopSource?.signalSourceAndWakeUpRunloop(secondaryThreadRunloopSourceContext!.runloop!)
        
}

從上述代碼中可以看到在sendCommandToSecondaryThread()方法中,將主線程的事件源上下文放入了二級(jí)線程事件源的指令池中男旗,這里我設(shè)計(jì)的是只要指令池中有內(nèi)容就代表事件源需要執(zhí)行后續(xù)任務(wù)了舶斧。然后執(zhí)行了二級(jí)線程事件源的signalSourceAndWakeUpRunloop()方法,給其標(biāo)記為待執(zhí)行剑肯,并喚醒二級(jí)線程的Run Loop:

func signalSourceAndWakeUpRunloop(runloop: CFRunLoopRef) {
        
    CFRunLoopSourceSignal(runloopSource)
        
    CFRunLoopWakeUp(runloop)
        
}

執(zhí)行事件源的perform回調(diào)函數(shù)

當(dāng)二級(jí)線程事件源被標(biāo)記并且二級(jí)線程Run Loop被喚醒后捧毛,就會(huì)觸發(fā)事件源的perform回調(diào)函數(shù):

func runloopSourcePerformRoutine() -> @convention(c) (UnsafeMutablePointer<Void>) -> Void {
        
    return { info -> Void in
            
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
            
        appDelegate.performSelector("performSecondaryThreadRunLoopSourceTask")
            
    }
        
}

二級(jí)線程事件源的perform回調(diào)函數(shù)會(huì)在當(dāng)前線程,也就是二級(jí)線程中執(zhí)行AppDelegate中的對(duì)應(yīng)方法:

func performSecondaryThreadRunLoopSourceTask() {
        
    if secondaryThreadRunloopSourceContext!.runloopSource!.commandBuffer!.count > 0 {
            
        mainCollectionViewController!.generateRandomAlpha()
            
        let mainThreadRunloopSourceContext = secondaryThreadRunloopSourceContext!.runloopSource!.commandBuffer![0]
            
        secondaryThreadRunloopSourceContext!.runloopSource!.commandBuffer!.removeAll()
            
        mainThreadRunloopSourceContext.runloopSource?.commandBuffer?.append(secondaryThreadRunloopSourceContext!)
            
        mainThreadRunloopSourceContext.runloopSource?.signalSourceAndWakeUpRunloop(mainThreadRunloopSourceContext.runloop!)
            
    }
        
}

從上述代碼中可以看到让网,先會(huì)判斷二級(jí)線程事件源的指令池中有沒有內(nèi)容呀忧,如果有的話,那么執(zhí)行計(jì)算UICollectionViewCell透明度的任務(wù)溃睹,然后從指令池中獲取到主線程事件源上下文對(duì)象而账,將二級(jí)線程事件源上下文對(duì)象放入主線程事件源的指令池中,并將主線程事件源標(biāo)記為待執(zhí)行因篇,然后喚醒主線程Run Loop泞辐。之后便會(huì)觸發(fā)主線程事件源的perform回調(diào)函數(shù):

func runloopSourcePerformRoutine() -> @convention(c) (UnsafeMutablePointer<Void>) -> Void {
        
    return { info -> Void in
            
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
            
        appDelegate.performSelector("performMainThreadRunLoopSourceTask")
        
    }
        
}
func performMainThreadRunLoopSourceTask() {
        
    if mainThreadRunloopSourceContext!.runloopSource!.commandBuffer!.count > 0 {
        
        mainThreadRunloopSourceContext!.runloopSource!.commandBuffer!.removeAll()
            
        mainCollectionViewController!.collectionView.reloadData()
            
        let timer = NSTimer(timeInterval: 1, target: self, selector: "sendCommandToSecondaryThread", userInfo: nil, repeats: false)
            
        NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
        
    }
        
}

performMainThreadRunLoopSourceTask()方法中同樣會(huì)先判斷主線程事件源的指令池是否有內(nèi)容,然后執(zhí)行MainCollectionViewController中的刷新UI的方法竞滓,最后再次給二級(jí)線程發(fā)送事件消息咐吼,以此循環(huán)。大家可以去Github下載該示例的源碼商佑,編譯環(huán)境是Xcode7.2锯茄,然后可以自己試著在界面中添加一個(gè)Stop按鈕,讓事件源執(zhí)行cancel回調(diào)函數(shù)茶没。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肌幽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抓半,更是在濱河造成了極大的恐慌喂急,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛求,死亡現(xiàn)場離奇詭異廊移,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)探入,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門狡孔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人新症,你說我怎么就攤上這事步氏。” “怎么了徒爹?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵荚醒,是天一觀的道長。 經(jīng)常有香客問我隆嗅,道長界阁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任胖喳,我火速辦了婚禮泡躯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己较剃,他們只是感情好咕别,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著写穴,像睡著了一般惰拱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啊送,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天偿短,我揣著相機(jī)與錄音,去河邊找鬼馋没。 笑死昔逗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篷朵。 我是一名探鬼主播勾怒,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼款票!你這毒婦竟也來了控硼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤艾少,失蹤者是張志新(化名)和其女友劉穎卡乾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缚够,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幔妨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谍椅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片误堡。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雏吭,靈堂內(nèi)的尸體忽然破棺而出锁施,到底是詐尸還是另有隱情,我是刑警寧澤杖们,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布悉抵,位于F島的核電站,受9級(jí)特大地震影響摘完,放射性物質(zhì)發(fā)生泄漏姥饰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一孝治、第九天 我趴在偏房一處隱蔽的房頂上張望列粪。 院中可真熱鬧审磁,春花似錦、人聲如沸岂座。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掺逼。三九已至妒峦,卻和暖如春浴讯,著一層夾襖步出監(jiān)牢的瞬間默穴,已是汗流浹背身坐。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工坚俗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棕硫,地道東北人螟加。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓档址,卻偏偏與公主長得像祠斧,于是被迫代替她去往敵國和親闻察。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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