iOS開發中多線程基礎

來源:互聯網
上載者:User

iOS開發中多線程基礎
耗時操作演練代碼演練編寫耗時方法

- (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]:當前線程對象
可以在所有的多線程技術中使用! 通常用來在多線程開發中,Log 代碼是否在主線程運行 number
number == 1 主線程 number != 1 後台線程 不要糾結 number 的具體數字pthread演練 pthreadPOSIX 多線程開發架構,由於是跨平台的 C 語言架構,在蘋果的標頭檔中並沒有詳細的注釋 要查閱 pthread 有關資料,可以訪問 http://baike.baidu.com匯入標頭檔
#import 
pthread演練
// 建立線程,並且線上程中執行 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 語言中,沒有 對象的概念,對象是以 結構體的方式來實現的 通常,在 C 語言架構中,物件類型以 _t/Ref 結尾,而且聲明時不需要使用 * C 語言中的 void * 和 OC 中的 id 是等價的 記憶體管理
在 OC 中,如果是 ARC 開發,編譯器會在編譯時間,根據代碼結構,自動添加 retain/ release/ autorelease 但是, ARC 只負責管理 OC 部分的記憶體管理,而不負責 C 語言 代碼的記憶體管理 因此,開發過程中,如果使用的 C 語言架構出現 retain/ create/ copy/ new 等字樣的函數,大多都需要 release,否則會出現記憶體流失 在混合開發時,如果在 COC 之間傳遞資料,需要使用 __bridge 進行橋接, 橋接的目的就是為了告訴編譯器如何管理記憶體 橋接的添加可以藉助 Xcode 的協助工具功能添加 MRC 中不需要使用橋接三種建立線程的方法準備函數
// MARK: - 後台線程調用函數- (void)longOperation:(id)obj {    NSLog(@"%@ - %@", [NSThread currentThread], obj);}
1. alloc / init - start
// 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除外)2. detachNewThreadSelector
- (void)threadDemo2 {    // detach => 分離一個子線程執行 demo: 方法    [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"Detach"];    // 2. 當前線程?    NSLog(@"%@", [NSThread currentThread]);}
演練小結 detachNewThreadSelector 類方法不需要啟動,建立線程後自動啟動線程執行 @selector 方法3. 分類方法
- (void)threadDemo3 {    // 1. 在後台執行 @selector 方法    [self performSelectorInBackground:@selector(longOperation:) withObject:@"category"];    // 2. 當前線程?    NSLog(@"%@", [NSThread currentThread]);}
performSelectorInBackgroundNSObject 的分類方法 沒有 thread 字眼,會立即在後台線程執行 @selector 方法 所有 NSObject 都可以使用此方法,在其他線程執行方法!自訂對象Person 類
// 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
Person 類使用分類方法
- (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];//    }}
屬性1. name - 線程名稱在大的商業項目中,通常需要在程式崩潰時,擷取程式準確執行所在的線程2. threadPriority - 線程優先順序優先順序,是一個浮點數,取值範圍從 0~1.0
1.0表示優先順序最高 0.0表示優先順序最低 預設優先順序是 0.5 優先順序高只是保證 CPU 調度的可能性會高 刀哥個人建議,在開發的時候,不要修改優先順序 多線程的目的:是將耗時的操作放在後台,不阻塞主線程和使用者的互動! 多線程開發的原則:簡單3. stackSize - 棧區大小預設情況下,無論是主線程還是子線程,棧區大小都是 512K 棧區大小可以設定
[NSThread currentThread].stackSize = 1024 * 1024;
4. 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 線程iOS 開發建議所有屬性都聲明為 nonatomic 盡量避免多線程搶奪同一塊資源 盡量將加鎖、資源搶奪的商務邏輯交給伺服器端處理,減小移動用戶端的壓力線程間通訊主線程實現定義屬性
/// 根視圖是滾動視圖@property (nonatomic, strong) UIScrollView *scrollView;/// 映像視圖@property (nonatomic, weak) UIImageView *imageView;/// 網路下載的映像@property (nonatomic, weak) UIImage *image;
loadView

loadView 方法的作用:

載入視圖階層 用純程式碼開發應用程式時使用 功能和 Storyboard & XIB 是等價的

如果重寫了 loadViewStoryboard & 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;}
viewDidLoad視圖載入完成後執行 可以做一些資料初始化的工作 如果用純程式碼開發,不要在此方法中設定介面 UI
- (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];}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.