這兩個月App被拒審,應對蘋果那邊花了不少心思,也花了大量時間,也對蘋果審覈團隊有了全新高度的認識,後續計劃寫一下這段時間是如何與蘋果鬥智鬥勇~html
幾乎全部的操做系統都支持同時運行多個任務,一個任務一般就是一個程序,每一個運行程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每一個順序執行流就是一個線程。ios
當一個程序進入內存運行後,即變成一個進程。進程是處於運行過程當中的程序,而且具備必定的獨立功能,進程是系統進行資源分配和調度的一個獨立單位。 通常而言,進程有以下特徵:git
線程也被稱做輕量級進程,線程是進程的執行單元。就像進程在系統中同樣,線程在進程中也是獨立、併發的執行流。 一個進程能夠擁有多個線程,一個線程必須有一個父進程,但再也不擁有系統資源,而是和父進程下的其餘線程一塊兒共享父進程的所有資源。多線程因爲共享父進程的資源,因此編程更加方便;可是也須要當心線程不會影響到父進程中的其餘線程。 線程是獨立運行的,它並不知道其餘線程的存在。線程的執行是搶佔式的,也就是說,當前運行的線程在任什麼時候候均可能被掛起,以便另一個線程能夠運行。程序員
爲了提升資源利用率來提高系統總體效率,實際每每是將耗時操做放在後臺執行,避免阻塞主線程,在iOS中UI繪製和用戶響應都是在主線程。github
Java中是cpu核數*2-1,iOS沒有查到確切資料。編程
經常使用API安全
- (void)viewDidLoad {
[super viewDidLoad];
//打印當前線程
NSLog(@"開始:%@ 優先級:%d", [NSThread currentThread], [NSThread currentThread].qualityOfService);
//1.建立NSTread對象,必須調用start方法開始,而且只能傳一個參數object
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"test"];
// NSThread *thread = [[NSThread alloc] initWithBlock:^{}];
thread.name = @"testThread";
thread.qualityOfService = NSQualityOfServiceUserInteractive;
[thread start];
//2.直接建立並啓動線程
// [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"test"];
// [NSThread detachNewThreadWithBlock:^{}];
//3.隱式直接建立
// [NSThread performSelectorInBackground:@selector(run:) withObject:nil];
// NSLog(@"結束:%@", [NSThread currentThread]);
}
- (void)run:(NSObject *)object {
//阻塞休眠
// [NSThread sleepForTimeInterval:5];
//停止當前線程
// [NSThread exit];
NSLog(@"子線程運行:%@ %@ 優先級:%d", [NSThread currentThread], object, [NSThread currentThread].qualityOfService);
}
複製代碼
線程被啓動後,並非直接進入執行狀態,也不是一直處於執行狀態,因爲多線程併發,線程會反覆在運行、就緒間切換。 建立一個線程後,處於新建狀態,系統爲其分配內存,初始化成員變量;調用- (void)start;
方法後,該線程處於就緒狀態,系統爲其建立方法調用棧和程序計數器,此時並無運行,什麼時候運行取決於系統調度。 bash
NSTread並無提供方法來終止子線程,只有+ (void)exit;
來終止當前線程,咱們不能直接使用該方法,防止誤操做終止了主線程,正確應該是調用子線程的- (void)cancel;
方法用來標記子線程狀態,而後在子線程的執行方法體內對isCancelled
作判斷,而後在調用+ (void)exit;
來終止當前運行的子線程。多線程
每一個線程都有必定的優先級,優先級越高得到執行機會越多。目前經過qualityOfService
屬性來設置,原來的threadPriority
因爲語義不夠清晰,已經被廢棄了。併發
NSQualityOfServiceUserInteractive:最高優先級,主要用於提供交互UI的操做,好比處理點擊事件,繪製圖像到屏幕上
NSQualityOfServiceUserInitiated:次高優先級,主要用於執行須要當即返回的任務
NSQualityOfServiceDefault:默認優先級,當沒有設置優先級的時候,線程默認優先級
NSQualityOfServiceUtility:普通優先級,主要用於不須要當即返回的任務
NSQualityOfServiceBackground:後臺優先級,用於徹底不緊急的任務
複製代碼
使用NSThread進行多線程編程較複雜,須要本身控制多線程的同步、併發,還須要本身控制線程的終止銷燬,稍不留神就容易出現錯誤,對開發者要求較高,通常較少使用。
iOS還提供了NSOperation與NSOperationQueue來實現多線程,是基於 GCD 更高一層的封裝,徹底面向對象。可是比 GCD 更簡單易用、代碼可讀性也更高。
NSOperationQueue:負責管理系統提交的多個NSOperation ,底層維護了一個線程池。不一樣於 GCD 中的調度隊列 FIFO(先進先出)的原則。NSOperationQueue 對於添加到隊列中的操做,首先進入準備就緒的狀態(就緒狀態取決於操做之間的依賴關係),而後進入就緒狀態的操做的開始執行順序(非結束執行順序)由操做之間相對的優先級決定(優先級是操做對象自身的屬性)。
NSOperation:表明一個多線程任務。
NSOperationQueue *queue;
//獲取執行當前NSOperation的NSOperationQueue隊列
// queue = [NSOperationQueue currentQueue];
//獲取主線程的NSOperationQueue隊列
// queue = [NSOperationQueue mainQueue];
//自定義隊列
queue = [[NSOperationQueue alloc] init];
//隊列名
queue.name = @"testOperationQueue";
//最大併發操做數(系統有限制,即便設置很大,也會自動調整)
queue.maxConcurrentOperationCount = 10;
//設置優先級
queue.qualityOfService = NSQualityOfServiceDefault;
//自定義NSOperation,若是SEL和Block爲空,系統不會加入到指定隊列
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation");
}];
//添加依賴關係,invocationOperation執行完後才執行blockOperation
[blockOperation addDependency:invocationOperation];
//添加到隊列中
// [queue addOperation:invocationOperation];
[queue addOperations:@[invocationOperation, blockOperation] waitUntilFinished:NO];
//直接添加代碼塊任務
[queue addOperationWithBlock:^{
}];
//打印全部的NSOperation
for(int i=0; i<queue.operationCount; i++) {
NSLog(@"隊列%@的第%d個NSOperation:%@", queue.name, i, queue.operations[i]);
}
//終止全部NSOperation
// [queue cancelAllOperations];
//執行完全部NSOperation才能解除阻塞當前線程
// [queue waitUntilAllOperationsAreFinished];
複製代碼
注:隊列的串行和並行決定了任務以何種方式執行,執行的異步和同步決定是否須要開闢新線程處理任務。
/** 獲取隊列 */
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//獲取指定優先級的全局併發隊列(flag填0便可,僅預留的參數,使用其餘值可能會返回null)
dispatch_queue_t queue1 = dispatch_queue_create("testQueue1", DISPATCH_QUEUE_CONCURRENT);//建立自定義並行隊列
dispatch_queue_t queue2 = dispatch_get_main_queue();//獲取系統主線程關聯的串行隊列
dispatch_queue_t queue3 = dispatch_queue_create("testQueue3", DISPATCH_QUEUE_SERIAL);//建立自定義串行隊列
/** 提交任務 */
dispatch_async(queue, ^{
});//異步提交代碼塊到併發隊列
dispatch_sync(queue1, ^{
});//同步提交代碼塊到自定義併發隊列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5*NSEC_PER_SEC)), queue2, ^{
});//異步提交代碼塊到串行隊列,線程池將在指定時間執行代碼塊(實際是5秒後加入到隊列中,實際並不必定會立馬執行,通常精度要求下是沒問題的)
dispatch_apply(5, queue3, ^(size_t time) {
});//異步提交代碼到自定義串行隊列,同步函數,不管是在串行仍是並行隊列中執行,都要執行完才返回,因此要防止線程阻塞和死鎖,time表示當前是第幾回(若是提交給併發隊列,會啓動五個線程來執行)
static dispatch_once_t onceToken; //實際是個long類型變量,用於判斷該代碼塊是否被執行過
dispatch_once(&onceToken, ^{
});//主線程執行一次代碼塊
//等group執行完後,才能執行下一步
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
/** 組(用於須要等待多個任務所有執行完再進行下一步) */
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
});//併發執行的代碼塊1
dispatch_group_async(group, queue, ^{
});//併發執行的代碼塊2
dispatch_group_notify(group, queue, ^{
});//等待兩個代碼塊執行完彙總
/** 柵欄(用於須要依次執行完多個線程組) */
//併發隊列異步執行代碼塊1,2
dispatch_async(queue, ^{
//代碼塊1
});
dispatch_async(queue, ^{
//代碼塊2
});
//1,2執行完後纔會執行3,4
dispatch_barrier_async(queue, ^{
});
//併發隊列異步執行代碼塊3,4
dispatch_async(queue, ^{
//代碼塊3
});
dispatch_async(queue, ^{
//代碼塊4
});
/** 信號量(用於控制線程的等待和執行) */
//建立信號量,value表示初始信號總量,支持多少個操做來執行
dispatch_semaphore_t t = dispatch_semaphore_create(1);
//發送一個信號,讓信號總量+1
dispatch_semaphore_signal(t);
//使信號總量-1,若是總量爲0,則會一直等待(阻塞所在線程),直到總量大於0則繼續執行
dispatch_semaphore_wait(t, DISPATCH_TIME_FOREVER);
/*1.能夠將異步執行變爲同步執行,如須要等待下載完後再直接返回數據(咱們也能夠經過block回調)*/
//總信號量設置爲0
dispatch_semaphore_t t1 = dispatch_semaphore_create(0);
//執行耗時代碼
void (^downloadTask)(void) = ^ {
//下載圖片
...
...
//完成後發送信號量
dispatch_semaphore_signal(t1);
};
downloadTask();
//一直等到信號量計數爲1才執行下一步,也就是等到圖片下載完後
dispatch_semaphore_wait(t1, DISPATCH_TIME_FOREVER);
/*2.保證線程安全*/
//設置信號量初始計數爲1,保證只能有一個操做能進來
dispatch_semaphore_t t2 = dispatch_semaphore_create(1);
//至關於加鎖,消耗使用計數,若是已經被一個線程使用,後續只能掛起等待信號量回復
dispatch_semaphore_wait(t2, DISPATCH_TIME_FOREVER);
//執行業務代碼
...
...
//解鎖
dispatch_semaphore_signal(t2);
/*3.模擬NSOperationQueue的最大併發操做數*/
//最大併發操做支持10
dispatch_semaphore_t t3 = dispatch_semaphore_create(10);
//剩餘操做同上,其實就是相似於將NSOperationQueue的maxConcurrentOperationCount設置爲10
複製代碼
在App程序進入後臺時,咱們應該儘可能釋放內存和保存用戶數據或者狀態信息。在默認狀況下,應用僅有5秒鐘處理這些工做,咱們能夠經過調用UIApplication
的beginBackgroundTaskWithExpirationHandler
方法來申請延長處理時間,最多有十分鐘。
- (void)applicationDidEnterBackground:(UIApplication *)application {
//聲明關閉後臺任務代碼塊
void (^endBackgroundTask)(UIBackgroundTaskIdentifier backgroudTask) = ^(UIBackgroundTaskIdentifier backgroudTask) {
[[UIApplication sharedApplication] endBackgroundTask:backgroudTask];
backgroudTask = UIBackgroundTaskInvalid;
};
//開啓後臺任務
__block UIBackgroundTaskIdentifier backgroudTask;
backgroudTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//十分鐘內仍然沒有完成,系統處理終止句柄
endBackgroundTask(backgroudTask);
}];
//執行相關代碼
//結束後臺任務
endBackgroundTask(backgroudTask);
}
複製代碼
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
複製代碼
在主隊列中增長同步代碼塊,就會形成死鎖,因爲同步是須要當即順序執行的,上述代碼中,Block中的方法須要在viewDidLoad
結束後才能完成,可是viewDidLoad
想要結束又必須得先結束Block中的方法,因此相互永久等待,形成了死鎖。
直接使用GCD的相關API通常是不會的,block結束後沒有循環引用的條件,YYKit的Issues下有個有趣的討論:dispatch_async的block裏面須要__weak self 嗎?
線程安全主要是因爲系統的線程調度具備必定的隨機性形成的,因爲是多併發,多個線程同時對一份數據進行讀寫,就可能在讀取執行一半的時候另一個線程去寫入,致使數據異常。
爲了解決這個問題,Objective-C的多線程支持引入同步,使用@synchronized
修飾代碼塊,被修飾的代碼塊可簡稱爲同步代碼塊。語法格式以下:
@synchronized (obj) {
//同步代碼塊
}
複製代碼
其中obj
就是同步監視器,當一個線程執行同步前,必須先得到同步監視器的鎖定,任什麼時候刻只能有一個線程得到鎖定,執行完成後,纔會釋放,若是此時有新線程訪問,那麼新線程會進入休眠狀態。一般推薦使用可能被併發訪問的的共享資源做爲同步監視器。
除了上面的同步代碼塊,還支持顯示的同步鎖NSLock
,在須要加鎖的代碼塊首尾使用- (void)lock;
和- (void)unlock;
來加鎖和釋放鎖。
當新線程訪問代碼時,若是發現有其餘的線程正在訪問,新線程會用死循環的方式去等待當前線程的訪問結束,比較耗性能,例如atomic
就是採用的自旋鎖。 可使用OSSpinLock
,可是有安全問題,iOS10之後可使用os_unfair_lock
。
//加讀鎖
pthread_rwlock_rdlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
//加寫鎖
pthread_rwlock_wrlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
複製代碼