- (void)longOperation {
for (int i = 0; i < 10000; ++i) {
NSLog(@"%@ %d", [NSThread currentThread], i);
}
}
直接調用耗時方法
// 1> 直接調用耗時方法
[self longOperation];
在後台執行耗時方法運行測試效果
// 2> 在後台執行耗時方法
[self performSelectorInBackground:@selector(longOperation) withObject:nil];
運行測試效果
[NSThread currentThread]
:當前線程對象number
number == 1
主線程 number != 1
後台線程 不要糾結 number 的具體數字
pthread
是 POSIX
多線程開發框架,由於是跨平台的 C 語言框架,在蘋果的頭文件中並沒有詳細的注釋 要查閱 pthread
有關資料,可以訪問 http://baike.baidu.com
#import
// 創建線程,並且在線程中執行 demo 函數
- (void)pthreadDemo {
/**
參數:
1> 指向線程標識符的指針,C 語言中類型的結尾通常 _t/Ref,而且不需要使用 *
2> 用來設置線程屬性
3> 線程運行函數的起始地址
4> 運行函數的參數
返回值:
- 若線程創建成功,則返回0
- 若線程創建失敗,則返回出錯編號
*/
pthread_t threadId = NULL;
NSString *str = @"Hello Pthread";
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創建線程 OK");
} else {
NSLog(@"創建線程失敗 %d", result);
}
}
// 後台線程調用函數
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
對象
的概念,對象是以結構體
的方式來實現的 通常,在 C 語言框架中,對象類型以 _t/Ref
結尾,而且聲明時不需要使用 *
C 語言中的 void *
和 OC 中的 id
是等價的 內存管理ARC
開發,編譯器會在編譯時,根據代碼結構,自動添加 retain
/release
/autorelease
但是,ARC
只負責管理 OC
部分的內存管理,而不負責 C 語言
代碼的內存管理 因此,開發過程中,如果使用的 C
語言框架出現 retain
/create
/copy
/new
等字樣的函數,大多都需要 release
,否則會出現內存洩漏 在混合開發時,如果在 C
和 OC
之間傳遞數據,需要使用 __bridge
進行橋接,橋接
的目的就是為了告訴編譯器如何管理內存 橋接的添加可以借助 Xcode 的輔助功能添加 MRC
中不需要使用橋接
// MARK: - 後台線程調用函數
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
}
// MARK: - NSThread 演練
- (void)threadDemo1 {
// 1. 實例化線程對象 => alloc(分配內存) / init(初始化)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"alloc/init"];
// 2. 啟動線程
[t start];
// 3. 當前線程?
NSLog(@"%@", [NSThread currentThread]);
}
[t start];
執行後,會在另外一個線程執行 demo
方法 在 OC 中,任何一個方法的代碼都是從上向下順序執行的 同一個方法內的代碼,都是在相同線程執行的(block
除外)
- (void)threadDemo2 {
// detach => 分離一個子線程執行 demo: 方法
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"Detach"];
// 2. 當前線程?
NSLog(@"%@", [NSThread currentThread]);
}
detachNewThreadSelector
類方法不需要啟動,創建線程後自動啟動線程執行 @selector
方法
- (void)threadDemo3 {
// 1. 在後台執行 @selector 方法
[self performSelectorInBackground:@selector(longOperation:) withObject:@"category"];
// 2. 當前線程?
NSLog(@"%@", [NSThread currentThread]);
}
performSelectorInBackground
是 NSObject
的分類方法 沒有 thread
字眼,會立即在後台線程執行 @selector
方法 所有 NSObject
都可以使用此方法,在其他線程執行方法!
// MARK: - Person 類
@interface Person : NSObject
/// 姓名
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
/// 使用字典實例化對象
+ (instancetype)personWithDict:(NSDictionary *)dict {
Person *p = [[Person alloc] init];
[p setValuesForKeysWithDictionary:dict];
return p;
}
/// 加載數據
- (void)loadData {
NSLog(@"加載數據 %@ %@", [NSThread currentThread], self.name);
}
@end
- (void)threadDemo4 {
Person * p = [Person personWithDict:@{@"name": @"zhangsan"}];
[p performSelectorInBackground:@selector(loadData) withObject:nil];
}
// MARK: - 線程狀態演練
- (void)statusDemo {
NSLog(@"睡會");
[NSThread sleepForTimeInterval:1.0];
for (int i = 0; i < 20; ++i) {
if (i == 8) {
NSLog(@"再睡會");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
NSLog(@"%@ %d", [NSThread currentThread], i);
if (i == 10) {
NSLog(@"88");
[NSThread exit];
}
}
NSLog(@"能來嗎?");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 注意不要在主線程上調用 exit 方法
// [NSThread exit];
// 實例化線程對象(新建)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];
// 線程就緒(被添加到可調度線程池中)
[t start];
}
sleep
方法讓線程進入 阻塞
狀態
1> sleepForTimeInterval
秒
2> sleepUntilDate
[NSThread exit];
線程從
就緒
和運行
狀態之間的切換是由CPU
負責的,程序員無法干預
// MARK: - 線程屬性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 線程名稱
t1.name = @"Thread AAA";
// 2. 優先級
t1.threadPriority = 0;
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 線程名稱
t2.name = @"Thread BBB";
// 2. 優先級
t2.threadPriority = 1;
[t2 start];
}
- (void)demo {
for (int i = 0; i < 10; ++i) {
// 堆棧大小
NSLog(@"%@ 堆棧大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024);
}
// 模擬崩潰
// 判斷是否是主線程
// if (![NSThread currentThread].isMainThread) {
// NSMutableArray *a = [NSMutableArray array];
//
// [a addObject:nil];
// }
}
name
- 線程名稱threadPriority
- 線程優先級0~1.0
1.0
表示優先級最高 0.0
表示優先級最低 默認優先級是0.5
優先級高只是保證 CPU 調度的可能性會高 刀哥個人建議,在開發的時候,不要修改優先級 多線程的目的:是將耗時的操作放在後台,不阻塞主線程和用戶的交互! 多線程開發的原則:簡單
stackSize
- 棧區大小512K
棧區大小可以設置
[NSThread currentThread].stackSize = 1024 * 1024;
isMainThread
- 是否主線程多線程開發的復雜度相對較高,在開發時可以按照以下套路編寫代碼:
首先確保單個線程執行正確 添加線程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
[self saleTickets];
}
/// 賣票邏輯 - 每一個售票邏輯(窗口)應該把所有的票賣完
- (void)saleTickets {
while (YES) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒票了 %@", [NSThread currentThread]);
break;
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票員 A";
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票員 B";
[t2 start];
}
- (void)saleTickets {
while (YES) {
// 模擬休眠
[NSThread sleepForTimeInterval:1.0];
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒票了 %@", [NSThread currentThread]);
break;
}
}
}
運行測試結果
- (void)saleTickets {
while (YES) {
// 模擬休眠
[NSThread sleepForTimeInterval:1.0];
@synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒票了 %@", [NSThread currentThread]);
break;
}
}
}
}
[[NSUserDefaults standardUserDefaults] synchronize];
NSObject
對象 注意:鎖對象一定要保證所有的線程都能夠訪問 如果代碼中只有一個地方需要加鎖,大多都使用 self
,這樣可以避免單獨再創建一個鎖對象
setter
方法),能夠保證同一時間只有一個線程執行寫入操作 原子屬性是一種單(線程)寫多(線程)讀
的多線程技術 原子屬性的效率比互斥鎖高
,不過可能會出現髒數據
在定義屬性時,必須顯示地指定 nonatomic
@interface ViewController ()
@property (atomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
@end
@implementation ViewController
@synthesize obj1 = _obj1;
// 原子屬性模擬代碼
/// obj1 - getter
- (NSObject *)obj1 {
return _obj1;
}
/// obj1 - setter
- (void)setObj1:(NSObject *)obj1 {
@synchronized(self) {
_obj1 = obj1;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;
// 互斥鎖測試
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; ++i) {
self.obj1 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
// 自旋鎖測試
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; ++i) {
self.obj2 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}
@end
原子屬性內部的鎖是
自旋鎖
,自旋鎖的執行效率比互斥鎖高
共同點
都能夠保證同一時間,只有一條線程執行鎖定范圍的代碼不同點
互斥鎖
:如果發現有其他線程正在執行鎖定的代碼,線程會進入休眠狀態
,等待其他線程執行完畢,打開鎖之後,線程會被喚醒
自旋鎖
:如果發現有其他線程正在執行鎖定的代碼,線程會以死循環
的方式,一直等待鎖定代碼執行完成
結論
自旋鎖更適合執行非常短的代碼 無論什麼鎖,都是要付出代價鎖
為了得到更佳的用戶體驗,UIKit 不是線程安全的
因此,約定:所有更新 UI 的操作都必須主線程上執行!
主線程
又被稱為UI 線程
nonatomic
盡量避免多線程搶奪同一塊資源 盡量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力
/// 根視圖是滾動視圖
@property (nonatomic, strong) UIScrollView *scrollView;
/// 圖像視圖
@property (nonatomic, weak) UIImageView *imageView;
/// 網絡下載的圖像
@property (nonatomic, weak) UIImage *image;
loadView 方法的作用:
加載視圖層次結構 用純代碼開發應用程序時使用 功能和Storyboard
& XIB
是等價的
如果重寫了
loadView
,Storyboard
&XIB
都無效
- (void)loadView {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.backgroundColor = [UIColor orangeColor];
self.view = self.scrollView;
UIImageView *iv = [[UIImageView alloc] init];
[self.view addSubview:iv];
self.imageView = iv;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 下載圖像
[self downloadImage];
}
- (void)downloadImage {
// 1. 網絡圖片資源路徑
NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"];
// 2. 從網絡資源路徑實例化二進制數據(網絡訪問)
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 將二進制數據轉換成圖像
UIImage *image = [UIImage imageWithData:data];
// 4. 設置圖像
self.image = image;
}
- (void)setImage:(UIImage *)image {
// 1. 設置圖像視圖的圖像
self.imageView.image = image;
// 2. 按照圖像大小設置圖像視圖的大小
[self.imageView sizeToFit];
// 3. 設置滾動視圖的 contentSize
self.scrollView.contentSize = image.size;
}
1> 設置滾動視圖縮放屬性
// 1> 最小縮放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大縮放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 設置代理
self.scrollView.delegate = self;
2> 實現代理方法 - 告訴滾動視圖縮放哪一個視圖
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
3> 跟蹤 scrollView
縮放效果
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform));
}
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
在主線程設置圖像
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
}