一、SDWebImage原理
二、什麼是Block?
三、RunLoop剖析
一個爲UIImageView提供一個分類來支持遠程服務器圖片加載的庫。前端
一、一個添加了web圖片加載和緩存管理的UIImageView分類
二、一個異步圖片下載器
三、一個異步的內存加磁盤綜合存儲圖片而且自動處理過時圖片
四、支持動態gif圖
五、支持webP格式的圖片
六、後臺圖片解壓處理
七、確保一樣的圖片url不會下載屢次
八、確保僞造的圖片url不會重複嘗試下載
九、確保主線程不會阻塞
複製代碼
一、入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示,而後 SDWebImageManager 根據 URL 開始處理圖片。
二、進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經下載 queryDiskCacheForKey:delegate:userInfo:.
三、先從內存圖片緩存查找是否有圖片,若是內存中已經有圖片緩存,SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
四、SDWebImageManagerDelegate 回調 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展現圖片。
五、若是內存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經緩存。
六、根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操做,因此回主線程進行結果回調 notifyDelegate:。
七、若是上一操做從硬盤讀取到了圖片,將圖片添加到內存緩存中(若是空閒內存太小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:。進而回調展現圖片。
八、若是從硬盤緩存目錄讀取不到圖片,說明全部緩存都不存在該圖片,須要下載圖片,回調 imageCache:didNotFindImageForKey:userInfo:。
九、共享或從新生成一個下載器 SDWebImageDownloader 開始下載圖片。
十、圖片下載由 NSURLConnection 來作,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
十一、connection:didReceiveData: 中利用 ImageIO 作了按圖片下載進度加載效果。connectionDidFinishLoading: 數據下載完成後交給 SDWebImageDecoder 作圖片解碼處理。
十二、圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。若是有須要對下載的圖片進行二次處理,最好也在這裏完成,效率會好不少。
1三、在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。
1四、通知全部的 downloadDelegates 下載完成,回調給須要的地方展現圖片。將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
1五、SDImageCache 在初始化的時候會註冊一些消息通知,在內存警告或退到後臺的時候清理內存圖片緩存,應用結束的時候清理過時圖片。
1六、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
1七、SDWebImagePrefetcher 能夠預先下載圖片,方便後續使用。
複製代碼
一、 SDWebImageDownloaderweb
1.單例,圖片下載器,負責圖片異步下載,並對圖片加載作了優化處理數組
2.圖片的下載操做放在一個NSOperationQueue併發操做隊列中,隊列默認最大併發數是6緩存
3.每一個圖片對應一些回調(下載進度,完成回調等),回調信息會存在downloader的URLCallbacks(一個字典,key是url地址,value是圖片下載回調數組)中,URLCallbacks可能被多個線程訪問,因此downloader把下載任務放在一個barrierQueue中,並設置屏障保證同一時間只有一個線程訪問URLCallbacks。,在建立回調URLCallbacks的block中建立了一個NSOperation並添加到NSOperationQueue中。安全
4.每一個圖片下載都是一個operation類,建立後添加到一個隊列中,SDWebimage定義了一個協議 SDWebImageOperation做爲圖片下載操做的基礎協議,聲明瞭一個cancel方法,用於取消操做。bash
@protocol SDWebImageOperation <NSObject>
-(void)cancel;
@end
複製代碼
connection:didReceiveResponse:
connection:didReceiveData:
connectionDidFinishLoading:
connection:didFailWithError:
connection:willCacheResponse:
connectionShouldUseCredentialStorage:
-connection:willSendRequestForAuthenticationChalleng
-connection:didReceiveData:方法,接受數據,建立一個CGImageSourceRef對象,在首次獲取數據時(圖片width,height),圖片下載完成以前,使用CGImageSourceRef對象建立一個圖片對象,通過縮放、解壓操做生成一個UIImage對象供回調使用,同時還有下載進度處理。
注:縮放:SDWebImageCompat中SDScaledImageForKey函數
解壓:SDWebImageDecoder文件中decodedImageWithImage
複製代碼
二、SDWebImageDownloaderOption服務器
1.繼承自NSOperation類,沒有簡單實現main方法,而是採用更加靈活的start方法,以便本身管理下載的狀態網絡
2.start方法中建立了下載使用的NSURLConnections對象,開啓了圖片的下載,並拋出一個下載開始的通知,數據結構
3.小結:下載的核心是利用NSURLSession加載數據,每一個圖片的下載都有一個operation操做來完成,並將這些操做放到一個操做隊列中,這樣能夠實現圖片的併發下載。併發
三、SDWebImageDecoder(異步對圖片進行解碼)
減小網絡流量,下載完圖片後存儲到本地,下載再獲取同一張圖片時,直接從本地獲取,提高用戶體驗,能快速從本地獲取呈現給用戶。 SDWebImage提供了對圖片進行了緩存,主要由SDImageCache完成。該類負責處理內存緩存以及一個可選的磁盤緩存,其中磁盤緩存的寫操做是異步的,不會對UI形成影響。
一、內存緩存及磁盤緩存
1.內存緩存的處理由NSCache對象實現,NSCache相似一個集合的容器,它存儲key-value對,相似於nsdictionary類,咱們一般使用緩存來臨時存儲短期使用但建立昂貴的對象,重用這些對象能夠優化新能,同時這些對象對於程序來講不是緊要的,若是內存緊張就會自動釋放。
2.磁盤緩存的處理使用NSFileManager對象實現,圖片存儲的位置位於cache文件夾,另外SDImageCache還定義了一個串行隊列來異步存儲圖片。
3.SDImageCache提供了大量方法來緩存、獲取、移除及清空圖片。對於圖片的索引,咱們經過一個key來索引,在內存中,咱們將其做爲NSCache的key值,而在磁盤中,咱們用這個key值做爲圖片的文件名,對於一個遠程下載的圖片其url實做爲這個key的最佳選擇。
二、存儲圖片 先在內存中放置一份緩存,若是須要緩存到磁盤,將磁盤緩存操做做爲一個task放到串行隊列中處理,會先檢查圖片格式是jpeg仍是png,將其轉換爲響應的圖片數據,最後吧數據寫入磁盤中(文件名是對key值作MD5後的串)
三、查詢圖片 內存和磁盤查詢圖片API:
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
複製代碼
查看本地是否存在key指定的圖片,使用一下API:
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
複製代碼
四、移除圖片 移除圖片API:
- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
複製代碼
五、清理圖片(磁盤)
清空磁盤圖片能夠選擇徹底清空和部分清空,徹底清空就是吧緩存文件夾刪除。
- (void)clearDisk;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
複製代碼
部分清理 會根據設置的一些參數移除部分文件,主要有兩個指標:文件的緩存有效期(maxCacheAge:默認是1周)和最大緩存空間大小(maxCacheSize:若是全部文件大小大於最大值,會按照文件最後修改時間的逆序,以每次一半的遞歸來移除哪些過早的文件,知道緩存文件總大小小於最大值),具體代碼參考- (void)cleanDiskWithCompletionBlock;
六、小結 SDImageCache處理提供以上API,還提供了獲取緩存大小,緩存中圖片數量等API, 經常使用的接口和屬性:
(1)-getSize :得到硬盤緩存的大小
(2)-getDiskCount : 得到硬盤緩存的圖片數量
(3)-clearMemory : 清理全部內存圖片
(4)- removeImageForKey:(NSString *)key 系列的方法 : 從內存、硬盤按要求指定清除圖片
(5)maxMemoryCost : 保存在存儲器中像素的總和
(6)maxCacheSize : 最大緩存大小 以字節爲單位。默認沒有設置,也就是爲0,而清理磁盤緩存的先決條件爲self.maxCacheSize > 0,因此0表示無限制。
(7)maxCacheAge : 在內存緩存保留的最長時間以秒爲單位計算,默認是一週
複製代碼
實際使用中並不直接使用SDWebImageDownloader和SDImageCache類對圖片進行下載和存儲,而是使用SDWebImageManager來管理。包括日常使用UIImageView+WebCache等控件的分類,都是使用SDWebImageManager來處理,該對象內部定義了一個圖片下載器(SDWebImageDownloader)和圖片緩存(SDImageCache)
@interface SDWebImageManager : NSObject
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
...
@end
複製代碼
SDWebImageManager聲明瞭一個delegate屬性,實際上是一個id對象,代理聲明瞭兩個方法
// 控制當圖片在緩存中沒有找到時,應該下載哪一個圖片
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
// 容許在圖片已經被下載完成且被緩存到磁盤或內存前當即轉換
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
複製代碼
這兩個方法會在SDWebImageManager的-downloadImageWithURL:options:progress:completed:方法中調用,而這個方法是SDWebImageManager類的核心所在(具體看源碼)
SDWebImageManager的幾個API:
(1)- (void)cancelAll : 取消runningOperations中全部的操做,並所有刪除
(2)- (BOOL)isRunning :檢查是否有操做在運行,這裏的操做指的是下載和緩存組成的組合操做
(3) - downloadImageWithURL:options:progress:completed: 核心方法
(4)- (BOOL)diskImageExistsForURL:(NSURL *)url :指定url的圖片是否進行了磁盤緩存
複製代碼
在使用SDWebImage的時候,使用最多的是UIImageView+WebCache中的針對UIImageView的擴展,核心方法是sd_setImageWithURL:placeholderImage:options:progress:completed:, 其使用SDWebImageManager單例對象下載並緩存圖片。
除了擴展UIImageView外,SDWebImage還擴展了UIView,UIButton,MKAnnotationView等視圖類,具體能夠參考源碼,除了可使用擴展的方法下載圖片,同時也可使用SDWebImageManager下載圖片。
UIView+WebCacheOperation分類: 把當前view對應的圖片操做對象存儲起來(經過運行時設置屬性),在基類中完成 存儲的結構:一個loadOperationKey屬性,value是一個字典(字典結構: key:UIImageViewAnimationImages或者UIImageViewImageLoad,value是 operation數組(動態圖片)或者對象)
UIButton+WebCache分類 會根據不一樣的按鈕狀態,下載的圖片根據不一樣的狀態進行設置 imageURLStorageKey:{state:url}
好比:
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
block(2);
複製代碼
經過clang -rewrite-objc WYTest.m命令編譯該.m文件,發現該block被編譯成這個形式:
NSInteger num = 3;
NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__WYTest__blockTest_block_impl_0((void *)__WYTest__blockTest_block_func_0, &__WYTest__blockTest_block_desc_0_DATA, num));
((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);
複製代碼
其中WYTest是文件名,blockTest是方法名,這些能夠忽略。 其中__WYTest__blockTest_block_impl_0結構體爲
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__block_impl結構體爲
struct __block_impl {
void *isa;//isa指針,因此說Block是對象
int Flags;
int Reserved;
void *FuncPtr;//函數指針
};
複製代碼
block內部有isa指針,因此說其本質也是OC對象 block內部則爲:
static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
NSInteger num = __cself->num; // bound by copy
return n*num;
}
複製代碼
因此說 Block是將函數及其執行上下文封裝起來的對象 既然block內部封裝了函數,那麼它一樣也有參數和返回值。
一、局部變量截獲 是值截獲。 好比:
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
複製代碼
這裏的輸出是6而不是2,緣由就是對局部變量num的截獲是值截獲。 一樣,在block裏若是修改變量num,也是無效的,甚至編譯器會報錯。
二、局部靜態變量截獲 是指針截獲。
static NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
複製代碼
輸出爲2,意味着num = 1這裏的修改num值是有效的,便是指針截獲。 一樣,在block裏去修改變量m,也是有效的。
三、全局變量,靜態全局變量截獲:不截獲,直接取值。
咱們一樣用clang編譯看下結果。
static NSInteger num3 = 300;
NSInteger num4 = 3000;
- (void)blockTest
{
NSInteger num = 30;
static NSInteger num2 = 3;
__block NSInteger num5 = 30000;
void(^block)(void) = ^{
NSLog(@"%zd",num);//局部變量
NSLog(@"%zd",num2);//靜態變量
NSLog(@"%zd",num3);//全局變量
NSLog(@"%zd",num4);//全局靜態變量
NSLog(@"%zd",num5);//__block修飾變量
};
block();
}
複製代碼
編譯後
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;//局部變量
NSInteger *num2;//靜態變量
__Block_byref_num5_0 *num5; // by ref//__block修飾變量
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
( impl.isa = &_NSConcreteStackBlock;這裏注意到這一句,即說明該block是棧block) 能夠看到局部變量被編譯成值形式,而靜態變量被編成指針形式,全局變量並未截獲。而__block修飾的變量也是以指針形式截獲的,而且生成了一個新的結構體對象:
struct __Block_byref_num5_0 {
void *__isa;
__Block_byref_num5_0 *__forwarding;
int __flags;
int __size;
NSInteger num5;
};
複製代碼
該對象有個屬性:num5,即咱們用__block修飾的變量。 這裏__forwarding是指向自身的(棧block)。 通常狀況下,若是咱們要對block截獲的局部變量進行賦值操做需添加__block 修飾符,而對全局變量,靜態變量是不須要添加__block修飾符的。 另外,block裏訪問self或成員變量都會去截獲self。
分爲全局Block(_NSConcreteGlobalBlock)、棧Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三種形式
其中棧Block存儲在棧(stack)區,堆Block存儲在堆(heap)區,全局Block存儲在已初始化數據(.data)區
一、不使用外部變量的block是全局block
好比:
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);
複製代碼
輸出:
__NSGlobalBlock__
複製代碼
二、使用外部變量而且未進行copy操做的block是棧block
好比:
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"stackBlock:%zd",num);
} class]);
複製代碼
輸出:
__NSStackBlock__
複製代碼
平常開發經常使用於這種狀況:
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
block();
NSLog(@"%@",[block class]);
}
複製代碼
三、對棧block進行copy操做,就是堆block,而對全局block進行copy,還是全局block
void (^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[globalBlock class]);
複製代碼
輸出:
__NSGlobalBlock__
複製代碼
還是全局block
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"stackBlock:%zd",num);
};
NSLog(@"%@",[mallocBlock class]);
複製代碼
輸出:
__NSMallocBlock__
複製代碼
對棧blockcopy以後,並不表明着棧block就消失了,左邊的mallock是堆block,右邊被copy的還是棧block 好比:
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block
{
block();
dispatch_block_t tempBlock = block;
NSLog(@"%@,%@",[block class],[tempBlock class]);
}
複製代碼
輸出:
__NSStackBlock__,__NSMallocBlock__
複製代碼
另外,__block變量在copy時,因爲__forwarding的存在,棧上的__forwarding指針會指向堆上的__forwarding變量,而堆上的__forwarding指針指向其自身,因此,若是對__block的修改,其實是在修改堆上的__block變量。
即__forwarding指針存在的意義就是,不管在任何內存位置, 均可以順利地訪問同一個__block變量。
__block typeof(self) weakSelf = self;
_testBlock = ^{
NSLog(@"%@",weakSelf);
};
_testBlock();
複製代碼
若是要解決這種循環引用,能夠主動斷開__block變量對self的持有,即在block內部使用完weakself後,將其置爲nil,但這種方式有個問題,若是block一直不被調用,那麼循環引用將一直存在。 因此,咱們最好仍是用__weak來修飾self
RunLoop是經過內部維護的事件循環(Event Loop)
來對事件/消息進行管理
的一個對象。
一、沒有消息處理時,休眠已避免資源佔用,由用戶態切換到內核態(CPU-內核態和用戶態) 二、有消息須要處理時,馬上被喚醒,由內核態切換到用戶態
爲何main函數不會退出?
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
UIApplicationMain內部默認開啓了主線程的RunLoop,並執行了一段無限循環的代碼(不是簡單的for循環或while循環)
//無限循環代碼模式(僞代碼)
int main(int argc, char * argv[]) {
BOOL running = YES;
do {
// 執行各類任務,處理各類事件
// ......
} while (running);
return 0;
}
複製代碼
UIApplicationMain函數一直沒有返回,而是不斷地接收處理消息以及等待休眠,因此運行程序以後會保持持續運行狀態。
NSRunLoop(Foundation)
是CFRunLoop(CoreFoundation)
的封裝,提供了面向對象的API RunLoop 相關的主要涉及五個類:
CFRunLoop
:RunLoop對象 CFRunLoopMode
:運行模式 CFRunLoopSource
:輸入源/事件源 CFRunLoopTimer
:定時源 CFRunLoopObserver
:觀察者
一、CFRunLoop
由pthread
(線程對象,說明RunLoop和線程是一一對應的)、currentMode
(當前所處的運行模式)、modes
(多個運行模式的集合)、commonModes
(模式名稱字符串集合)、commonModelItems
(Observer,Timer,Source集合)構成
二、CFRunLoopMode
由name、source0、source一、observers、timers構成
三、CFRunLoopSource
分爲source0和source1兩種
source0
: 即非基於port的,也就是用戶觸發的事件。須要手動喚醒線程,將當前線程從內核態切換到用戶態source1
: 基於port的,包含一個 mach_port 和一個回調,可監聽系統端口和經過內核和其餘線程發送的消息,能主動喚醒RunLoop,接收分發系統事件。 具有喚醒線程的能力四、CFRunLoopTimer
基於時間的觸發器,基本上說的就是NSTimer。在預設的時間點喚醒RunLoop執行回調。由於它是基於RunLoop的,所以它不是實時的(就是NSTimer 是不許確的。 由於RunLoop只負責分發源的消息。若是線程當前正在處理繁重的任務,就有可能致使Timer本次延時,或者少執行一次)。
五、CFRunLoopObserver
監聽如下時間點:CFRunLoopActivity
kCFRunLoopEntry
RunLoop準備啓動kCFRunLoopBeforeTimers
RunLoop將要處理一些Timer相關事件kCFRunLoopBeforeSources
RunLoop將要處理一些Source事件kCFRunLoopBeforeWaiting
RunLoop將要進行休眠狀態,即將由用戶態切換到內核態kCFRunLoopAfterWaiting
RunLoop被喚醒,即從內核態切換到用戶態後kCFRunLoopExit
RunLoop退出kCFRunLoopAllActivities
監聽全部狀態六、各數據結構之間的聯繫
線程和RunLoop一一對應, RunLoop和Mode是一對多的,Mode和source、timer、observer也是一對多的
關於Mode首先要知道一個RunLoop 對象中可能包含多個Mode,且每次調用 RunLoop 的主函數時,只能指定其中一個 Mode(CurrentMode)。切換 Mode,須要從新指定一個 Mode 。主要是爲了分隔開不一樣的 Source、Timer、Observer,讓它們之間互不影響。
當RunLoop運行在Mode1上時,是沒法接受處理Mode2或Mode3上的Source、Timer、Observer事件的
總共是有五種CFRunLoopMode
:
kCFRunLoopDefaultMode
:默認模式,主線程是在這個運行模式下運行
UITrackingRunLoopMode
:跟蹤用戶交互事件(用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘Mode影響)
UIInitializationRunLoopMode
:在剛啓動App時第進入的第一個 Mode,啓動完成後就再也不使用
GSEventReceiveRunLoopMode
:接受系統內部事件,一般用不到
kCFRunLoopCommonModes
:僞模式,不是一種真正的運行模式,是同步Source/Timer/Observer到多個Mode中的一種解決方案
這張圖在網上流傳比較廣。 對於RunLoop而言最核心的事情就是保證線程在沒有消息的時候休眠,在有消息時喚醒,以提升程序性能。RunLoop這個機制是依靠系統內核來完成的(蘋果操做系統核心組件Darwin中的Mach)。
RunLoop經過mach_msg()
函數接收、發送消息。它的本質是調用函數mach_msg_trap()
,至關因而一個系統調用,會觸發內核狀態切換。在用戶態調用 mach_msg_trap()
時會切換到內核態;內核態中內核實現的mach_msg()
函數會完成實際的工做。 即基於port的source1,監聽端口,端口有消息就會觸發回調;而source0,要手動標記爲待處理和手動喚醒RunLoop
Mach消息發送機制 大體邏輯爲: 一、通知觀察者 RunLoop 即將啓動。 二、通知觀察者即將要處理Timer事件。 三、通知觀察者即將要處理source0事件。 四、處理source0事件。 五、若是基於端口的源(Source1)準備好並處於等待狀態,進入步驟9。 六、通知觀察者線程即將進入休眠狀態。 七、將線程置於休眠狀態,由用戶態切換到內核態,直到下面的任一事件發生才喚醒線程。
八、通知觀察者線程將被喚醒。 九、處理喚醒時收到的事件。
十、通知觀察者RunLoop結束。
一個比較常見的問題:滑動tableView時,定時器還會生效嗎? 默認狀況下RunLoop運行在kCFRunLoopDefaultMode
下,而當滑動tableView時,RunLoop切換到UITrackingRunLoopMode
,而Timer是在kCFRunLoopDefaultMode
下的,就沒法接受處理Timer的事件。 怎麼去解決這個問題呢?把Timer添加到UITrackingRunLoopMode
上並不能解決問題,由於這樣在默認狀況下就沒法接受定時器事件了。 因此咱們須要把Timer同時添加到UITrackingRunLoopMode
和kCFRunLoopDefaultMode
上。 那麼如何把timer同時添加到多個mode上呢?就要用到NSRunLoopCommonModes
了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
Timer就被添加到多個mode上,這樣即便RunLoop由kCFRunLoopDefaultMode
切換到UITrackingRunLoopMode
下,也不會影響接收Timer事件
一、怎麼建立一個常駐線程?
一、爲當前線程開啓一個RunLoop(第一次調用 [NSRunLoop currentRunLoop]方法時實際是會先去建立一個RunLoop) 一、向當前RunLoop中添加一個Port/Source等維持RunLoop的事件循環(若是RunLoop的mode中一個item都沒有,RunLoop會退出) 二、啓動該RunLoop
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
複製代碼
二、輸出下邊代碼的執行順序
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
NSLog(@"4");
- (void)test
{
NSLog(@"5");
}
複製代碼
答案是1423,test方法並不會執行。 緣由是若是是帶afterDelay的延時函數,會在內部建立一個 NSTimer,而後添加到當前線程的RunLoop中。也就是若是當前線程沒有開啓RunLoop,該方法會失效。 那麼咱們改爲:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
複製代碼
然而test方法依然不執行。 緣由是若是RunLoop的mode中一個item都沒有,RunLoop會退出。即在調用RunLoop的run方法後,因爲其mode中沒有添加任何item去維持RunLoop的時間循環,RunLoop隨即仍是會退出。 因此咱們本身啓動RunLoop,必定要在添加item後
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
[[NSRunLoop currentRunLoop] run];
NSLog(@"3");
});
複製代碼
三、怎樣保證子線程數據回來更新UI的時候不打斷用戶的滑動操做?
當咱們在子請求數據的同時滑動瀏覽當前頁面,若是數據請求成功要切回主線程更新UI,那麼就會影響當前正在滑動的體驗。 咱們就能夠將更新UI事件放在主線程的NSDefaultRunLoopMode
上執行便可,這樣就會等用戶再也不滑動頁面,主線程RunLoop由UITrackingRunLoopMode
切換到NSDefaultRunLoopMode
時再去更新UI
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
複製代碼