總結(jié)ios開發(fā)中一些知識呛凶,有錯(cuò)或者有問題的歡迎交流罐呼。
這些資料也是看其他的一些文章總結(jié)的,首先把別人文章地址貼出來求泰。本文直接介紹pthread和NSThread芝加,其他多線程方式請看本人其他文章或者其他地方了解硅卢。
一 ?pthread
基本介紹
????pthread 是 POSIX 多線程開發(fā)框架〔卣龋跨平臺(tái)将塑,適用于多種操作系統(tǒng),可移植性強(qiáng)蝌麸,是一套純C語言的通用API点寥,且線程的生命周期需要程序員自己管理。
?????ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理来吩,而不負(fù)責(zé) C 語言 代碼的內(nèi)存管理敢辩。因此,開發(fā)過程中弟疆,如果使用的 C 語言框架出現(xiàn)retain/create/copy/new 等字樣的函數(shù)戚长,大多都需要 release,否則會(huì)出現(xiàn)內(nèi)存泄漏怠苔。在混合開發(fā)時(shí)同廉,如果在 C 和 OC 之間傳遞數(shù)據(jù),需要使用 __bridge 進(jìn)行橋接柑司,橋接的目的就是為了告訴編譯器如何管理內(nèi)存迫肖。
橋接的添加可以借助 Xcode 的輔助功能添加。
MRC 中不需要使用橋接攒驰。
使用方法
使用pthread 要引入頭文件
#import <pthread.h>
創(chuàng)建線程蟆湖,并在線程中執(zhí)行demo
/** pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
參數(shù):
1> 指向線程標(biāo)識符的指針,C 語言中類型的結(jié)尾通常 _t/Ref讼育,而且不需要使用 *
2> 用來設(shè)置線程屬性
3> 線程運(yùn)行函數(shù)的起始地址
4> 運(yùn)行函數(shù)的參數(shù)
返回值: - 若線程創(chuàng)建成功帐姻,則返回0 - 若線程創(chuàng)建失敗稠集,則返回出錯(cuò)編號
*/
pthread_t threadId = NULL; //一個(gè)結(jié)構(gòu)體,具有指向了當(dāng)前線程的指針
NSString *str = @"Hello Pthread";
// 這邊的demo函數(shù)名作為第三個(gè)參數(shù)寫在這里可以在其前面加一個(gè)&饥瓷,也可以不加剥纷,因?yàn)楹瘮?shù)名就代表了函數(shù)的地址。
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創(chuàng)建線程 OK");
} else {
NSLog(@"創(chuàng)建線程失敗 %d", result);
}
// pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會(huì)自動(dòng)釋放所有資源呢铆。
pthread_detach(threadId);
回調(diào)函數(shù)
// 后臺(tái)線程調(diào)用函數(shù)
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
} ?
函數(shù)介紹
pthread_create的函數(shù)原型為 ? ?
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict, void *(*)(void *), void * __restrict);
第一個(gè)參數(shù)pthread_t * __restrict
由于c語言沒有對象的概念晦鞋,所以pthread_t實(shí)際是一個(gè)結(jié)構(gòu)體
所以創(chuàng)建的thread是一個(gè)指向當(dāng)前新建線程的指針
typedef __darwin_pthread_t pthread_t;
typedef struct _opaque_pthread_t *__darwin_pthread_t;
struct _opaque_pthread_t {
long __sig;
struct __darwin_pthread_handler_rec *__cleanup_stack;
char __opaque[__PTHREAD_SIZE__];
}; ?
第二個(gè)參數(shù)const pthread_attr_t * __restrict
同樣是一個(gè)結(jié)構(gòu)體,這里是用來設(shè)置線程屬性的
typedef __darwin_pthread_attr_t pthread_attr_t;
typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
struct _opaque_pthread_attr_t {
long __sig;
char __opaque[__PTHREAD_ATTR_SIZE__];
}; ?
第三個(gè)參數(shù)void?()(void *)這里給出了一個(gè)函數(shù)指針棺克,指向的是一個(gè)函數(shù)的起始地址悠垛,所以是線程開啟后的回調(diào)函數(shù) ? ?
第四個(gè)參數(shù)是回調(diào)函數(shù)所用的參數(shù) ? ?
這里只是介紹了pthread的基本用法和其參數(shù)類型,和其他多線程方式比除了跨平臺(tái)還有什么優(yōu)點(diǎn)娜谊,蘋果提供pthread具體在項(xiàng)目中怎么使用确买,希望有做過的大神留言溝通,我也會(huì)繼續(xù)更新相關(guān)內(nèi)容纱皆。
二 NSThread ? 原文在這里
通過NSThread我們具體討論一些線程相關(guān)的問題湾趾,包括如下內(nèi)容:
使用NSThread創(chuàng)建線程
線程狀態(tài)
線程間通信
線程安全
1. 使用NSThread創(chuàng)建線程
使用NSThread創(chuàng)建線程有以下幾種方式:
使用NSThread的init方法顯式創(chuàng)建
使用NSThread類方法顯式創(chuàng)建并啟動(dòng)線程
隱式創(chuàng)建并啟動(dòng)線程
具體的代碼實(shí)現(xiàn)在下面已經(jīng)給出了,這里提醒大家注意一點(diǎn)派草。只有使用NSThread的init方法創(chuàng)建的線程才會(huì)返回具體的線程實(shí)例搀缠。也就是說如果想要對線程做更多的控制,比如添加線程的名字近迁、更改優(yōu)先級等操作艺普,要使用第一種方式來創(chuàng)建線程。但是此種方法需要使用start方法來手動(dòng)啟動(dòng)線程鉴竭。
/** * 隱式創(chuàng)建并啟動(dòng)線程 */
- (void)createThreadWithImplicit {
// 隱式創(chuàng)建并啟動(dòng)線程
[self performSelectorInBackground:@selector(threadMethod3:) withObject:@"implicitMethod"];
}
/** * 使用NSThread類方法顯式創(chuàng)建并啟動(dòng)線程 */
- (void)createThreadWithClassMethod {
// 使用類方法創(chuàng)建線程并自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(threadMethod2:) toTarget:self withObject:@"fromClassMethod"];
}
/** * 使用init方法顯式創(chuàng)建線程 */
- (void)createThreadWithInit {
// 創(chuàng)建線程
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod1) object:nil];
// 設(shè)置線程名
[thread1 setName:@"thread1"];
// 設(shè)置優(yōu)先級 優(yōu)先級從0到1 1最高
[thread1 setThreadPriority:0.9];
// 啟動(dòng)線程
[thread1 start];
} ?
2:線程狀態(tài)
線程狀態(tài)分為:啟動(dòng)線程歧譬,阻塞線程,結(jié)束線程?
啟動(dòng)線程: ??
- (void)start;
阻塞線程:
// 線程休眠到某一時(shí)刻
+ (void)sleepUntilDate:(NSDate *)date;
// 線程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
結(jié)束線程:
+ (void)exit;
大家在看官方api的時(shí)候可能會(huì)有一個(gè)疑問拓瞪,api里明明有cancel方法缴罗,為什么使用cancel方法不能結(jié)束線程?
當(dāng)我們使用cancel方法時(shí)祭埂,只是改變了線程的狀態(tài)標(biāo)識面氓,并不能結(jié)束線程,所以我們要配合isCancelled方法進(jìn)行使用蛆橡。具體實(shí)現(xiàn)如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 創(chuàng)建線程
[self createThread];
}
- (void)createThread {
// 創(chuàng)建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod) object:nil];
thread.name = @"i'm a new thread";
// 啟動(dòng)線程
[thread start];
}
/** * 線程方法 */
- (void)threadMethod {
NSLog(@"thread is create -- the name is: \"%@\"", [NSThread currentThread].name);
// 線程阻塞 -- 延遲到某一時(shí)刻 --- 這里的時(shí)刻是3秒以后
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(@"sleep end");
NSLog(@"sleep again"); // 線程阻塞 -- 延遲多久 -- 這里延遲2秒
[NSThread sleepForTimeInterval:2];
NSLog(@"sleep again end");
for (int i = 0 ; i < 100; i++) {
NSLog(@"thread working");
if(30 == i) {
NSLog(@"thread will dead");
[[NSThread currentThread] cancel];
}
if([[NSThread currentThread] isCancelled]) {
// 結(jié)束線程//
[NSThread exit]; return;
} }} ?
3:線程間通訊
線程間通信我們最常用的就是開啟子線程進(jìn)行耗時(shí)操作舌界,操作完畢后回到主線程,進(jìn)行數(shù)據(jù)賦值以及刷新主線程UI泰演。在這里呻拌,用一個(gè)經(jīng)典的圖片下載demo進(jìn)行簡述。首先我們先了解一下api給出的線程間通信的方法:
//與主線程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes//
與其他子線程通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); ?
以下是demo
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 下載圖片
[self downloadImage];
}
/** * 下載圖片 */
- (void)downloadImage {
// 創(chuàng)建線程下載圖片
[NSThread detachNewThreadSelector:@selector(downloadImageInThread) toTarget:self withObject:nil];
}
/** * 線程中下載圖片操作 */
- (void)downloadImageInThread {
NSLog(@"come in sub thread -- %@", [NSThread currentThread]);
// 獲取圖片url
NSURL *url = [NSURL URLWithString:@"http://img.ycwb.com/news/attachement/jpg/site2/20110226/90fba60155890ed3082500.jpg"];
// 計(jì)算耗時(shí)
NSDate *begin = [NSDate date];
// 使用CoreFoundation計(jì)算耗時(shí) CFDate
CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();
// 從url讀取數(shù)據(jù)(下載圖片) -- 耗時(shí)操作
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();
// 計(jì)算時(shí)間差
NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);
NSLog(@"time difference inCF -- %f", endInCF - beginInCF);
// 通過二進(jìn)制data創(chuàng)建image
UIImage *image = [UIImage imageWithData:imageData];
// 回到主線程進(jìn)行圖片賦值和界面刷新
[self performSelectorOnMainThread:@selector(backToMainThread:) withObject:image waitUntilDone:YES];
// 這里也可以使用imageView的set方法進(jìn)行操作//
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
/** * 回到主線程的操作 */
- (void)backToMainThread:(UIImage *)image {
NSLog(@"back to main thread --- %@", [NSThread currentThread]);
// 賦值圖片到imageview
self.imageView.image = image;
} ?
在demo中已經(jīng)把注釋寫的比較清晰了睦焕,需要補(bǔ)充的有三點(diǎn):
1.performSelectorOnMainThread:withObject:waitUntilDone:方法這里是回到了主線程進(jìn)行操作藐握,同樣也可以使用
[selfperformSelector:@selector(backToMainThread:) onThread:[NSThreadmainThread] withObject:image waitUntilDone:YES];
回到主線程靴拱,或者進(jìn)入其他線程進(jìn)行操作。
2.在實(shí)際項(xiàng)目中我們可能會(huì)分析耗時(shí)操作所花費(fèi)時(shí)間或者分析用戶行為的時(shí)候要計(jì)算用戶在當(dāng)前頁面所耗時(shí)間猾普,所以在demo中加入了時(shí)間的兩種計(jì)算方式袜炕,分別是CoreFoundation和Foundation中的。
// 計(jì)算耗時(shí)
NSDate *begin = [NSDate date];
// 使用CoreFoundation計(jì)算耗時(shí) CFDate
CFTimeInterval beginInCF = CFAbsoluteTimeGetCurrent();
// 從url讀取數(shù)據(jù)(下載圖片) -- 耗時(shí)操作
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
CFTimeInterval endInCF= CFAbsoluteTimeGetCurrent();
// 計(jì)算時(shí)間差 NSLog(@"time difference -- %f", [end timeIntervalSinceDate:begin]);
NSLog(@"time difference inCF -- %f", endInCF - beginInCF); ?
3.如果自己寫的項(xiàng)目無法運(yùn)行初家,可能是因?yàn)閄code7 創(chuàng)建HTTP請求報(bào)錯(cuò)導(dǎo)致偎窘,具體解決方案請點(diǎn)擊這里。
4:線程安全
因?yàn)槭嵌嗑€程操作溜在,所以會(huì)存在一定的安全隱患陌知。原因是多線程會(huì)存在不同線程的資源共享,也就是說我們可能在同一時(shí)刻兩個(gè)線程同時(shí)操作了某一個(gè)變量的值掖肋,但是線程的對變量的操作不同仆葡,導(dǎo)致變量的值出現(xiàn)誤差。下面是一個(gè)存取錢的demo片段:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 初始化狀態(tài) [self initStatus];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 啟動(dòng)線程 [self startThread];
}
/** * 初始化狀態(tài) */
- (void)initStatus {
// 設(shè)置存款
self.depositMoney = 5000;
// 創(chuàng)建存取錢線程
self.saveThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];
self.saveThread.name = @"save";
self.drawThread = [[NSThread alloc] initWithTarget:self selector:@selector(saveAndDraw) object:nil];
self.drawThread.name = @"draw";
}
/** * 開啟線程 */
- (void)startThread {
// 開啟存取錢線程
[self.saveThread start];
[self.drawThread start];
}
/** * 存取錢操作 */
- (void)saveAndDraw {
while(1) {
if(self.depositMoney > 3000) {
// 阻塞線程培遵,模擬操作花費(fèi)時(shí)間
[NSThread sleepForTimeInterval:0.05];
if([[NSThread currentThread].name isEqualToString:@"save"]) {
self.depositMoney += 100;
} else {
self.depositMoney -= 100;
}
NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);
} else {
NSLog(@"no money"); return;
} }}?
在上面的demo中我們發(fā)現(xiàn)浙芙,存取錢的線程是同時(shí)開啟的,而存取錢的錢數(shù)相同籽腕,所以每一次存取操作結(jié)束后,存款值應(yīng)該不會(huì)改變纸俭。大家可以運(yùn)行demo進(jìn)行查看結(jié)果皇耗。
所以需要在線程操作中加入鎖:
/** * 存取錢操作 */
- (void)saveAndDraw {
while(1) {
// 互斥鎖
@synchronized (self) {
if(self.depositMoney > 3000) {
// 阻塞線程,模擬操作花費(fèi)時(shí)間
[NSThread sleepForTimeInterval:0.05];
if([[NSThread currentThread].name isEqualToString:@"save"]) {
self.depositMoney += 100;
} else {
self.depositMoney -= 100;
}
NSLog(@"currentThread: %@, depositMoney: %d", [NSThread currentThread].name, self.depositMoney);
} else {
NSLog(@"no money"); return;
} } }} ?
線程安全解決方案 這邊有介紹關(guān)于鎖的一些文章可以看看?
ios開發(fā)中8種鎖? ?關(guān)于 @synchronized
* 互斥鎖@synchronized 的作用是創(chuàng)建一個(gè)互斥鎖揍很,保證此時(shí)沒有其它線程對鎖住的對象進(jìn)行修改郎楼。
* 互斥鎖使用格式:@synchronized (鎖對象) { // 需要鎖定的代碼 }
* 互斥鎖的優(yōu)缺點(diǎn):
優(yōu)點(diǎn): 防止多線程對共享資源進(jìn)行搶奪造成的數(shù)據(jù)安全問題
缺點(diǎn): 需要消耗大量cpu資源 ?
atomic屬性內(nèi)部的鎖稱為 自旋鎖
互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖窒悔。一旦被訪問的資源被解鎖呜袁,則等待資源的線程會(huì)被喚醒。 ? ?
自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了简珠,線程會(huì)以死循環(huán)的方式等待鎖阶界,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行聋庵。 ? ?
自旋鎖的效率高于互斥鎖膘融。
NSThread頭文件中的相關(guān)信息請通過command + 鼠標(biāo)左鍵的方式查看
總結(jié)?
pthread的線程方式雖然平時(shí)沒用到,但是也有其自己特點(diǎn)祭玉,希望了解的大佬能留言溝通氧映。
NSThread平時(shí)開發(fā)中,時(shí)常用來進(jìn)行線程間通訊脱货,AFN中也用來開啟一個(gè)常駐線程岛都。
還沒來得及自己去實(shí)驗(yàn)總結(jié) 如果有錯(cuò)誤希望能指出來律姨。