一、進程
進程是指在系統中正在運行的一個應用程序每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內比如同時打開QQ、Xcode,系統就會分別啓動2個進程
二、線程
是用來執行任務的,線程只要徹底執行完A才能去執行B,這就是線程的安全,爲了防止任務能同時進行,引入了多線程。
1、多線程:其實是CPU對任務的調度,任務的優先級越高,CPU被調度的就越快,對於單核CPU只能在同一時間處理一條線程,多線程併發執行實質是任務線程之間的不斷切換,因爲切換的太快,所以造成了同一時間執行的假象;比如兩個線程A、B;A執行到某一時間,切換到B,但是A還沒有執行完,系統會把A當前的位置和數據保存在棧中,依次循環,線程對CPU的消耗是比較大的,ios中不建議開多條線程
2、線程分爲兩種:主線程和子線程
在當前線程中開啓多個新線程,不按照順序執行叫異步,在當前線程中的任務按順序執行,不開啓新的線程,叫同步。隊列是狀態線程任務的結構:包括併發隊列和穿行隊列;併發隊列的線程可以同時一起執行,串行隊列只能依次逐一先後有序的執行
三、多線程的優缺點:
優點:1、提高CPU的利用率,不讓它閒着
2、提高程序的執行效率,避免線程阻塞造成的卡頓
缺點:1、大量的線程降低代碼的可讀性
2、需要大量的內存空間,降低程序的性能
3、多個線程同時爭奪同一資源,容易線程不安全,比如mutalArray的例子
死鎖:A在等待B的結果,B在等待A的結果
多線程的實現方法有四種:Pthreads、CGD、NSThread、NSOpreation
多線程就是開闢線程、添加隊列、隊列中同步或者異步執行
四、主線程
一個ios程序運行後,系統會默認開啓一條線程,稱爲UI線程或者主線程,主線程是用來顯示、刷新界面、處理UI事件的,假如沒有RunLoop程序一運行就結束了,看不到持續運行的app
五、RunLoop的作用;學習原鏈接RunLoop深入
1、保持程序的持續運行;程序一啓動就會開一個主線程,主線程一開起來就會跑一個主線程對應的RunLoop,是Runloop保證主線程不被銷燬
2、處理項目中各種事件,比如:selector事件、定時器、觸摸事件等
3、節省CPU的資源,提高程序性能。當有操作的時候runloop會通知CPU該做什麼,沒有事情的時候讓它休息
Runloop實際上就是一個對象,這個對象管理了其需要處理的事件和消息,並提供了一個入口的函數,線程執行了這個函數就會一直處於這個函數內部接受消息----等待消息----處理消息,直到這個循環結束
RunLoop在哪裏開啓:在UIApplicationMain函數中啓動了RunLoop,程序不會馬上退出而是保持運行狀態,程序運行,主線程開啓,和主線程對應的Runloop開啓,那麼runloop肯定是在程序的入口main函數中開啓的
在iOS中有兩套API訪問和使用RunLoop
Foundation:NSRunloop
CoreFoundation :CFRunLoopRef 他是基於pthread來管理的
NSRunloop是基於CFRunLoopRef的一層OC的包裝
在源碼中創建的線程核心代碼
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
//如果沒有線程,創建一個
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//創建一個可變的字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//將主線程作爲參數創建一個Runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//將主線程的RunLoop和主線程以key/value的形式保存,可以看到線程和RunLoop是一一對應的
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//當輸入一個 currentRunLoop 時,會通過當前線程這個Key,在字典中尋找對應的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
//如果沒有找到
if (!loop) {
//重新創建一個
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
//然後將runloop和線程以 key/value的形式保存
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
很好的runloop帖子:Runloop全面學習
六、線程與runloop的關係
程序啓動時,系統會自動創建主線程的RunLoop,每條線程都有唯一的一個與之對應的RunLoop對象,主線程的Runloop已經自動創建好了,子線程的runloop需要自己手動創建,runloop在第一次獲取時創建,在線程結束時銷燬
獲取當前線程的Runloop,runloop是懶加載的,在發送消息時自動創建對象
[NSRunLoop currentRunLoop];
在iOS開發中,會遇到兩個線程對象:pthread_t 和 NSThread,NSThread只是對pthread的封裝,兩者肯定是一一對應的,我們可以通過pthread_main_thread_np()或者 [NSThread mainThread]來獲取主線程,也可以通過pthread_self() [NSThread currentThread]來獲取當前的線程。
蘋果是不允許直接來創建Runloop的。他只提供了兩個自動獲取的函數:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()
七、RunLoop相關類
我嘗試着打印了 [NSRunLoop mainRunLoop];
mainRunLoop == <CFRunLoop 0x6040001f9a00 [0x103291c80]>
{wakeup port = 0xc03, stopped = false, ignoreWakeUps = true,
current mode = (none),
common modes = <CFBasicHash 0x6040002557b0 [0x103291c80]>
{type = mutable set, count = 1,
entries =>
2 : <CFString 0x103267818 [0x103291c80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x604000255780 [0x103291c80]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x604000192f10 [0x103291c80]>{name = kCFRunLoopDefaultMode, port set = 0xd03, queue = 0x60400014f4c0, source = 0x600000192e40 (not fired), timer port = 0x1503,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 556728920 (19355243452143) / soft deadline in: 1.84467247e+10 sec (@ -1) / hard deadline in: 1.84467247e+10 sec (@ -1)
},
}
}
可以看到Corefoundation中關於runloop的五個類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
從打印中可以看出,一個RunLoop想要跑起來,必須要有Model支持,而Mode裏邊必須有(NSSet *)soure (NSArray *)Timer
這時候的(NSArray *)Observer是用來監聽runloop的狀態,這時候是不會激活的
1、CFRunloopModeRef
代表runloop運行模式,每個runloop都包含幾個mode。每個mode又包含若干個source、timer、observer,每次runloop啓動時,只能指定其中一個mode,這個mode被稱作currentMode,如果需要切換mode,只能退出runloop,再重新指定一個mode進入,這樣做主要是爲了分開不同的source、timer、observer,讓他們互不影響
runloop默認註冊了5個mode
NSDefaultRunLoopMode:app默認的mode,通常主線程是在這個mode下運行的
UITrackingRunLoopMode:界面跟蹤mode。用於scorllview追蹤觸摸滑動,保證界面滑動時不受影響
UIInitializationRunLoop:剛啓動時app進入第一個mode,啓動完成就不再使用
GSRunLoopReceiveRunLoopMode:接受系統時間的內部mode,繪圖服務,通常用不到
NSRunLoopCommonModes:這是一個佔位用的mode,不是一種真的mode
幾個mode的區別
NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
後面這一句相當於前兩者寫在一起的模式下運行(####其實這裏我也不是很明白####)
2、CFRunLoopTimeRef
CFRunLoopTimeRed是基於時間的觸發器,應該說就是NSTimer,它手RunLoop的mode影響
創建Timer的兩種方式
1> NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];
這種方式必須手動添加到 RunLoop 中去纔會被調用
2> 通過方法scheduledTimerWithTimeInterval: target : selector
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
/*
注意:調用了 scheduledTimer 返回的定時器,已經自動被添加到當前runLoop 中,而且是NSDefaultRunLoopMode ,想讓上述方法起作用,必須先讓添加了上述 timer的RunLoop 對象 run 起來,通過scheduledTimerWithTimeInterval 創建的 timer 可以通過以下方法修改 mode
*/
[[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
****提示**** GCD的定時器不受RunLoop的Mode影響
CFRunLoopSourceRef 事件的輸入源
source的分類:
1 》port-Based Source 基於端口,跟其他線程進行交互的,屬於mac內核發過來的一些消息
2》 custom input Source 自定義輸入源
3》cocoa perform selector sourcees
按照函數條用棧可分爲
1》source0:基於port的觸摸事件、點擊事件
2》source1:基於port的,通過內核和其他線程通信,接受分發系統事件,是和硬件的交互,觸摸首先在屏幕上包裝成一個event事件,在通過source1進行分發到source0,最後通過source0來處理
邏輯圖如下
CFRunLoopObserver : 是觀察者,能夠監聽runloop的狀態改變,主要監聽以下幾個時間節點,在文檔的.h中這麼聲明
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), //即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //即將退出 Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU //監聽所有事件
};
/**
創建觀察者,監聽runloop
該方法可以在添加timer之前做點事情。可以在添加source之前搞點事情
參數一:CFAllocatorRef allocator CFAllocatorGetDefault 默認值
參數二:CFOptionFlags activities 監聽runloop的活動,也就是之前的枚舉
參數三:Boolean repeats 是否重複監聽
參數四:CFIndex order 寫0
參數五:block
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopRunHandledSource, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
//添加觀察者,監聽當前的runloop
CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer , kCFRunLoopCommonModes);
//cf的是不ARC管理內存的,需要釋放
CFRelease(observer);
整個runloop的運行邏輯如下圖:
八、RunLoop的應用
1 > NSTimer 需求讓定時器在其他線程開啓
/*
> 需求
> 有時候,用戶拖拽scrollView的時候,mode:UITrackingRunLoopMode,顯示圖片,如果圖片很大,會渲染比較耗時,造成不好的體驗,因此,設置當用戶停止拖拽的時候再顯示圖片,進行延遲操作
- 方法1:設置scrollView的delegate 當停止拖拽的時候做一些事情
- 方法2:使用performSelector 設置模式爲default模式 ,則顯示圖片這段代碼只能在RunLoop切換模式之後執行
*/
// 加載比較大的圖片時,
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// inModes 傳入一個 mode 數組,這句話的意思是
// 只有在 NSDefaultRunLoopMode 模式下才會執行 seletor 的方法顯示圖片
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"avater"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
}
效果爲:當用戶點擊之後,下載圖片,但是圖片太大,不能及時下載。這時用戶可能會做些其他 UI 操作,比如拖拽,但是如果用戶正在拖拽瀏覽其他的東西時,圖片下載完畢了,此時如果要渲染顯示,會造成不好的用戶體驗,所以當用戶拖拽完畢後,顯示圖片。這是因爲,用戶拖拽,處於 UITrackingRunLoopMode 模式下,所以圖片不會顯示
2、常駐線程
> 需求
搞一個線程一直存在,一直在後臺做一些操作 比如監聽某個狀態, 比如監聽是否聯網。
//一個線程對應一個runloop,創建一個線程會自動開闢一個runloop
NSThread * runloopTherd = [[NSThread alloc]initWithTarget:self selector:@selector(runLoop) object:nil];
[runloopTherd start];
-(void)runLoop
{
NSLog(@"-----");
/*
* 創建RunLoop,如果RunLoop內部沒有添加任何Source Timer
* 會直接退出循環,因此需要自己添加一些source才能保持RunLoop運轉
*/
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
3、自動釋放池
Runloop循環時,在進入睡眠之前會清掉自動釋放池,並且創建一個新的釋放池,用於內部變量的銷燬。在子線程開RunLoop的時候一定要自己寫一個autoreleasepool。一個runloop對應一條線程,自動釋放池是針對當前線程裏邊的對象
NSThread * releaseTherd = [[NSThread alloc]initWithTarget:self selector:@selector(releaseRun) object:nil];
[releaseTherd start];
-(void)releaseRun
{
//這樣保證了內存的安全
@autoreleasepool{
NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runLoop) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
}
4、runloop與網絡請求、AFNetWorking ---摘自博客
iOS中,關於網絡請求的接口從上到下有:
CFSocket 最底層的接口,只負責socket通信
CFNetwork -----> ASIHttpRequest 基於CFSocket的上層封裝
NSURLConnection -----> AFNetworking 基於CFNetwork的分裝,提供面向對象的接口
NSURLSession ------->AFNetworking iOS7中新增的接口,內部用到了NSURLConnection
五、PerformSelecter,這個單獨放出來,這裏只做簡單介紹
當調用NSObject的performSelecter:afterDelay:方法後,其實內部會創建一個timer,並添加到當前的線程的runloop中,所以如果當前的線程沒有runloop,則這個方法會失效
========稍後我會將RunLoop源代碼的註釋貼上=========
=======可能理解有錯或者不到位歡迎指出========
=========相關帖子=======