一、什么是多線程
一個iOS程序就像一個圓,不斷循環,直到將它切斷。一個運行著的程序就是一個進程或者叫做一個任務,一個進程至少包含一個線程,線程就是程序的執行流。iOS中的程序啟動,創建好一個進程的同時,一個線程便開始運行,這個線程叫主線程。主線程在程序中的地位和其他線程不同,它是其他線程最終的父線程,且所有界面的顯示操作即AppKit或UIKit的操作必須在主線程進行。 系統中的每一個進程都有自己獨立的虛擬內存空間,而同一個進程中的多個線程則共用進程的內存空間。每創建一個新的線程,都需要一些內存和消耗一定的CPU時間。另外當多個線程對同一個資源出現爭奪的時候需要注意線程安全問題。
二、創建線程
1. 使用NSThread
,創建一個NSThread的對象,調用其start方法。
// 創建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"http://b.png"];
//線程名字
thread.name = @"下載線程";
// 啟動線程(調用self的download方法)
[thread start];
2. 使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
這個類方法創建一個線程并且會自動啟動線程。
3. 使用NSObject 其實NSObject直接就加入了多線程的支持,允許對象的某個方法在后臺運行。如:
//(調用self的download方法)
[self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];
三、線程安全
多個線程可能會訪問同一塊資源很容易引發數據錯亂和數據安全問題
解決方法:
互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼 }
OC在定義屬性時有nonatomic和atomic兩種選擇
atomic
:原子屬性,為setter方法加鎖(默認就是atomic)
nonatomic
:非原子屬性,不會為setter方法加鎖
atomic加鎖原理
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
atomic
:線程安全,需要消耗大量的資源
nonatomic
:非線程安全,適合內存小的移動設備
iOS開發的建議:
1 .所有屬性都聲明為nonatomic
2 .盡量避免多線程搶奪同一塊資源
3 .盡量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力
四、線程間的通信
- 線程間通信的體現
1 .一個線程傳遞數據給另一個線程
2 .在一個線程中執行完特定任務后,轉到另一個線程繼續執行任務
- 線程間通信常用的方法
1. `NSThread`可以先將自己的當前線程對象注冊到某個全局的對象中去,這樣相互之間就可以獲取對方的線程對象,然后就可以使用下面的方法進行線程間的通信了,由于主線程比較特殊,所以框架直接提供了在主線程執行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
用法如下:
//點擊屏幕開始執行下載方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelectorInBackground:@selector(download) withObject:nil];
}
//下載圖片
- (void)download
{
// 1.圖片地址
NSString *urlStr = @"http://d.jpg";
NSURL *url = [NSURL URLWithString:urlStr];
// 2.根據地址下載圖片的二進制數據
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"---end");
// 3.設置圖片
UIImage *image = [UIImage imageWithData:data];
// 4.回到主線程,刷新UI界面(為了線程安全)
[self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:NO];
// [self performSelector:@selector(downloadFinished:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
- (void)downloadFinished:(UIImage *)image
{
self.imageView.image = image;
NSLog(@"downloadFinished---%@", [NSThread currentThread]);
}
2. `GCD`一個線程傳遞數據給另一個線程,如:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"donwload---%@", [NSThread currentThread]);
// 1.子線程下載圖片
NSURL *url = [NSURL URLWithString:@"http://d.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.回到主線程設置圖片
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"setting---%@ %@", [NSThread currentThread], image);
[self.button setImage:image forState:UIControlStateNormal];
});
});
}