深入淺出iOS多線程(一)——線程的概念
深入淺出iOS多線程(二)——pthraed和NSThread的使用
深入淺出iOS多線程(三)——GCD多線程
深入淺出iOS多線程(四)——NSOperation多線程
深入淺出iOS多線程(五)——多線程鎖
pthread
pthread簡(jiǎn)介
pthread 是屬于 POSIX 多線程開(kāi)發(fā)框架,POSIX表示可移植操作系統(tǒng)接口(Portable Operating System Interface of UNIX,縮寫(xiě)為 POSIX ),如果想學(xué)習(xí)這套API,在網(wǎng)上是可以找到相關(guān)的資料等,由于在iOS中有NSThrad,如果不考慮移植性,那么在iOS開(kāi)發(fā)中基本上不回去使用,所以只是了解,pthread是多線程的一種技術(shù)實(shí)現(xiàn)。
iOS中的pthread
在iOS中需要導(dǎo)入頭文件pthread.h才能夠使用pthrad的Api
#import <pthread.h>
pthraed的特點(diǎn)
- 一套通用的多線程API
- 跨平臺(tái)可移植
- 使用難度比較大
- 基于C語(yǔ)言的開(kāi)發(fā)
pthread的簡(jiǎn)單使用
/**
參數(shù):
1.指向線程標(biāo)示的指針
2.線程的屬性
3.指向函數(shù)的指針
4.傳遞給該函數(shù)的參數(shù)
返回值
- 如果是0,標(biāo)示正確
- 如果非0,標(biāo)示錯(cuò)誤代碼
void * (*) (void *)
返回值 (函數(shù)指針) (參數(shù))
void * 和OC中的 id 是等價(jià)的!
*/
pthread_t pthreadId ;
NSString *str = @"敲代碼";
int result = pthread_create(&pthreadId,
NULL,
&doing,
(__bridge void *)(str)
);
if(result == 0){
NSLog(@"開(kāi)啟成功");
}else{
NSLog(@"開(kāi)啟失敗");
}
void * doing(void * param){
NSLog(@"%@,%@",[NSThread currentThread],param);
return NULL;
}
NSThread
iOS的多線程N(yùn)SThread簡(jiǎn)介
NSThread是蘋(píng)果官方提供面向?qū)ο蟛僮骶€程的技術(shù),簡(jiǎn)單方便,可以直接操作對(duì)象,需要手動(dòng)控制線程的生命周期,平時(shí)iOS開(kāi)發(fā)較少使用,使用最多的是獲取當(dāng)前線程
NSThread特點(diǎn)
- 面向?qū)ο蟮亩嗑€程編程
- 簡(jiǎn)單易用,可直接操作線程對(duì)象
- 需要手動(dòng)管理線程的生命周期
NSThread的詳細(xì)使用介紹
如何開(kāi)啟NSThread線程
NSThread初始化API
//初始化的API
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
- (instancetype)initWithBlock:(void (^)(void))block
//類對(duì)象方法
+ (void)detachNewThreadWithBlock:(void (^)(void))block
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
上述實(shí)例方法以及類對(duì)象方法,一樣都是創(chuàng)建一個(gè)新的線程,不一樣的是,類對(duì)象方法不需要?jiǎng)?chuàng)建完成以后調(diào)用start方法,而alloc創(chuàng)建的線程需要手動(dòng)start開(kāi)啟。
NSThread代碼實(shí)現(xiàn)
//方法一:
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(demo1Doing:) object:@"hello"];
[thread start];
//方法二:
NSThread *thread1 = [[NSThread alloc]initWithBlock:^{
NSLog(@"%s",__func__);
}];
[thread1 start];
//方法三:
[NSThread detachNewThreadSelector:@selector(demo1Doing:) toTarget:self withObject:@"hello"];
//方法四
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%s",__func__);
}];
NSThread主線程的API和獲取主線程
-
判斷是否是主線程,獲取主線程
+ (NSThread *)mainThread; // 獲得主線程 - (BOOL)isMainThread; // 是否為主線程 + (BOOL)isMainThread; // 是否為主線程 -
獲取當(dāng)前線程
NSThread *current = [NSThread currentThread]; -
設(shè)置和獲取線程的名字
- (void)setName:(NSString *)n; - (NSString *)name;
其他創(chuàng)建線程的方式
-
自啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(demo1Doing:) toTarget:self withObject:@"hello"]; [NSThread detachNewThreadWithBlock:^{ NSLog(@"%s",__func__); }]; -
隱式創(chuàng)建并啟動(dòng)線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; - (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 modes:(nullable NSArray<NSString *> *)array; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait; - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
總結(jié)
上述兩種創(chuàng)建線程方式的優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):簡(jiǎn)單快捷
- 缺點(diǎn):無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置和管理
控制線程狀態(tài)
-
啟動(dòng)線程
// 進(jìn)入就緒狀態(tài) -> 運(yùn)行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動(dòng)進(jìn)入死亡狀態(tài) - (void)start; -
阻塞線程
//進(jìn)入阻塞線程狀態(tài) 休眠 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; -
強(qiáng)制停止線程
//進(jìn)入死亡狀態(tài) + (void)exit;
線程的優(yōu)先級(jí)
-
線程的優(yōu)先級(jí)API
+ (double)threadPriority; + (BOOL)setThreadPriority:(double)p; -
優(yōu)先級(jí)設(shè)置
- Priorit的值 0.0~1.0之間
- 優(yōu)先級(jí)只能保證CPU調(diào)度的可能性會(huì)高,歸根究底你還是無(wú)法控制多線程的順序,如果就靠?jī)?yōu)先級(jí),來(lái)誤認(rèn)為控制多線程額順序是不嚴(yán)謹(jǐn)?shù)摹?
- 多線程的目的:是不阻塞UI線程
- 建議不要修改優(yōu)先級(jí)
- 多線程開(kāi)發(fā)中不能相信一次的運(yùn)行結(jié)果
- 優(yōu)先級(jí)翻轉(zhuǎn),優(yōu)先級(jí)低的任務(wù)太耗時(shí)放到最后面,然后后面排的任務(wù)比較多,優(yōu)先級(jí)高的任務(wù)被堵死了
多線程的安全隱患問(wèn)題
安全隱患?
- 資源共享
- 一塊資源可能會(huì)被多個(gè)線程共享,也就是說(shuō)多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
- 例如:多個(gè)線程同時(shí)訪問(wèn)修改同一個(gè)變量、同一個(gè)文件、同一個(gè)對(duì)象
- 數(shù)據(jù)錯(cuò)亂
- 同一個(gè)線程修改一個(gè)數(shù)據(jù),不同線程不同的運(yùn)行結(jié)束時(shí)間,有可能得到不一樣結(jié)果
安全隱患問(wèn)題分析
- ThreadA去訪問(wèn)一塊內(nèi)容中的integer數(shù)據(jù),得到數(shù)據(jù)17,17+1 = 18寫(xiě)入內(nèi)存
- ThreadB也在同一時(shí)間訪問(wèn)了integer數(shù)據(jù),得到數(shù)據(jù)17,17+1 = 18寫(xiě)入內(nèi)存
-
結(jié)果非常有意思的是+1了兩次應(yīng)該是19才對(duì),最終結(jié)果是18,這就是多線程的安全隱患問(wèn)題,如下圖所示
安全隱患.png
如何解決多線程的安全隱患(線程鎖)
互斥鎖
當(dāng)ThreadA去訪問(wèn)一塊內(nèi)容中的integer數(shù)據(jù)的時(shí)候,首先上一把鎖
lock得到數(shù)據(jù)17,17+1 = 18寫(xiě)入內(nèi)存,最后在unlock-
ThreadB也在同一時(shí)間訪問(wèn)了integer數(shù)據(jù):
由于ThreadA已經(jīng)在數(shù)據(jù)上面加了鎖
lock,所以必須等到ThreadA完成以后才能去訪問(wèn)這個(gè)數(shù)據(jù)-
ThreadA完成,訪問(wèn)integer數(shù)據(jù)時(shí),時(shí)候
lock然后在獲取數(shù)據(jù)18,18+1 = 19寫(xiě)入內(nèi)存,最后在unlock//互斥鎖 @synchronized (self) { } `` -
@synchronized的參數(shù):- 任意OC對(duì)象都可以加鎖
- 加鎖一定要加鎖共有的對(duì)象,一般用self
-
互斥鎖的優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
- 缺點(diǎn):需要消耗大量的CPU資源
-
互斥鎖的使用前提
- 多條線程搶奪同一塊資源
-
線程同步
- 線程同步的意思就是:多條線程同一條線上工作(按順序地執(zhí)行任務(wù))
- 互斥鎖,就使用了線程同步技術(shù)
互斥鎖需要注意的地方
保證代碼內(nèi)的代碼,同一時(shí)間,只有一條線程執(zhí)行
互斥鎖的范圍應(yīng)該盡量小,范圍大了,效率就差
-
結(jié)果本身是一個(gè)多線程開(kāi)發(fā),最后結(jié)果變成了同步去執(zhí)行,互斥鎖,也就是同步線程技術(shù),如下圖所示:
互斥鎖.png
如何解決多線程的安全隱患(原子與非原子對(duì)象)
nonatomic 非原子屬性
- 不會(huì)為setter方法加鎖
- 非原子屬性,因?yàn)?code>atomic所有對(duì)這個(gè)對(duì)象的操作之前會(huì)加鎖,所以會(huì)很耗費(fèi)資源,在沒(méi)有安全隱患的問(wèn)題上在加鎖,是不必要的
atomic 原子屬性
為setter方法加鎖(默認(rèn)是atomic)
-
原子屬性,保證這個(gè)屬性的安全性(線程安全),多線程寫(xiě)入這個(gè)對(duì)象的時(shí)候,保證同一時(shí)間只有一個(gè)線程能夠執(zhí)行!
- 模擬一個(gè)
atomic原子屬性
//模擬原子屬性 - (void)setMyAtomic:(NSObject *)myAtomic{ @synchronized (self) { _myAtomic = myAtomic; } }- 實(shí)際上,原子屬性內(nèi)部有一個(gè)鎖,自旋鎖:
- 自旋鎖和互斥鎖不一樣的地方
- 共同點(diǎn):都能夠保證線程的安全
- 不同點(diǎn):互斥鎖:如果線程被鎖到外面,線程就會(huì)進(jìn)入休眠狀態(tài),等待鎖打開(kāi),打開(kāi)之后被喚醒;自旋鎖:如果線程被鎖在外面,就會(huì)用死循環(huán)的方式,一直等待鎖打開(kāi)。
- 無(wú)論什么鎖,都會(huì)消耗新能,效率不高
- 線程安全
- 在多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然保證數(shù)據(jù)正確
- 模擬一個(gè)
-
UI線程
- 共同的約定,所有更新UI的操作都放在主線程執(zhí)行
- 因?yàn)閁IKit 框架都是線程不安全的(因?yàn)榫€程安全效率低下)
-
nonatomic和atomic對(duì)比
- atomic:線程安全,需要消耗大量的資源
- nonatomic:非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備
-
iOS開(kāi)發(fā)使用nonatomic和atomic
- 所有屬性都應(yīng)聲明nonatomic
- 盡量避免多線程搶奪同一塊資源
- 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器處理,減小移動(dòng)客戶端的壓力
注意一個(gè)小細(xì)節(jié)
- OC中:定義一個(gè)屬性,通常會(huì)生成成員變量,如果同時(shí)重寫(xiě)了getter、setter那么成員變量就不會(huì)自動(dòng)生成
- 如果想要同時(shí)重寫(xiě)了getter、setter,那么就直接使用 @synthesize myAtomic = _myAtomic;
NSThread自定義
在NSThread的有init初始化方法:
//用alloc init 適用于自定義NSThread (子類)
NSThread * t = [[NSThread alloc]init];
需要?jiǎng)?chuàng)建一個(gè)新的子類繼承NSThread方法,然后重寫(xiě)main方法
多線程下載網(wǎng)絡(luò)圖片
-(void)loadView{
}
如果重新了上述方法,SB和XIB都無(wú)效
代碼如下:
#import "ViewController.h"
@interface ViewController ()<UIScrollViewDelegate>
@property(nonatomic,strong)UIScrollView * scrollView;
@property(nonatomic,weak) UIImageView * imageView;
@property(nonatomic,strong) UIImage * image;
@end
@implementation ViewController
/**
加載視圖結(jié)構(gòu)的,純代碼開(kāi)發(fā)
功能 SB&XIB 是一樣
如果重寫(xiě)了這個(gè)方法,SB和XIB 都無(wú)效
*/
-(void)loadView{
//搭建界面
self.scrollView = [[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.view = self.scrollView;
//MARK:- 設(shè)置縮放屬性
self.scrollView.delegate = self;
self.scrollView.minimumZoomScale = 0.1;
self.scrollView.maximumZoomScale = 2.0;
//imageView
UIImageView * iv = [[UIImageView alloc]init];
//會(huì)調(diào)用View的getter方法. loadView方法在執(zhí)行的過(guò)程中!如果self.view == nil,會(huì)自動(dòng)調(diào)用loadView加載!
[self.view addSubview:iv];
self.imageView = iv;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSThread * t1 = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage) object:nil];
[t1 start];
}
//MARK: - 下載圖片
-(void)downloadImage{
NSLog(@"%@",[NSThread currentThread]);
//NSURL -> 統(tǒng)一資源定位符,每一個(gè)URL 對(duì)應(yīng)一個(gè)網(wǎng)絡(luò)資源!
NSURL * url = [NSURL URLWithString:@"https://images.unsplash.com/photo-1496840220025-4cbde0b9df65?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80"];
//下載圖片(在網(wǎng)絡(luò)上傳輸?shù)乃袛?shù)據(jù)都是二進(jìn)制!!)
//為什么是二進(jìn)制:因?yàn)槲锢韺?!是網(wǎng)線!!網(wǎng)線里面是電流!!電流有高低電頻!!高低電頻表示二進(jìn)制!!!
NSData * data = [NSData dataWithContentsOfURL:url];
//將二進(jìn)制數(shù)據(jù)轉(zhuǎn)成圖片并且設(shè)置圖片
//提示:不是所有的更新UI在后臺(tái)線程支持都會(huì)有問(wèn)題!!!
//重點(diǎn)提示:不要去嘗試在后臺(tái)線程更新UI!!!出了問(wèn)題是非常詭異的!!
// self.image = [UIImage imageWithData:data];
//在UI線程去更新UI
/**
* 1.SEL:在主線程執(zhí)行的方法
* 2.傳遞給方法的參數(shù)
* 3.讓當(dāng)前線程等待 (注意點(diǎn)!! 如果當(dāng)前線程是主線程!哥么YES沒(méi)有用!!)
*/
// 線程間通訊
[self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:NO];
}
//這種寫(xiě)法 省略一個(gè) _image ,主要原因是因?yàn)閕mage 保存在了imageView里面了!
-(UIImage *)image{
return self.imageView.image;
}
-(void)setImage:(UIImage *)image{
NSLog(@"更新 UI 在====%@",[NSThread currentThread]);
//直接將圖片設(shè)置到控件上
self.imageView.image = image;
//讓imageView和image一樣大
[self.imageView sizeToFit];
//指定ScrollView 的contentSize
self.scrollView.contentSize = image.size;
NSLog(@"\n\n\n\n\n\n\n\n\n\n\n%@",self.image);
}
#pragma mark - <scrollView代理>
//告訴 ScrollView 縮放哪個(gè)View
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.imageView;
}
/**
* transform 矩陣
* CGFloat a(縮放比例), b, c, d(縮放比例); 共同決定角度!
* CGFloat tx(x方向位移), ty(y方向的位移);
*
*/
-(void)scrollViewDidZoom:(UIScrollView *)scrollView
{
NSLog(@"%@",NSStringFromCGAffineTransform(self.imageView.transform));
}
@end
提示:不是所有的更新UI在后臺(tái)線程支持都會(huì)有問(wèn)題!!!
重點(diǎn)提示:不要去嘗試在后臺(tái)線程更新UI!!!出了問(wèn)題是非常詭異的!!
-
在UI線程去更新UI
/** * 1.SEL:在主線程執(zhí)行的方法 * 2.傳遞給方法的參數(shù) * 3.是否讓當(dāng)前線程等待 (注意點(diǎn)!! 如果當(dāng)前線程是主線程!YES沒(méi)有用!!) * NO當(dāng)前線程不需要等待@selector(setImage:)執(zhí)行完成,YES當(dāng)前線程需要等待@selector(setImage:)執(zhí)行完成 */ // 線程間通訊 [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:NO];
線程間的通信
@interface NSObject (NSThreadPerformAdditions)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (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 modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
@end
NSPort實(shí)現(xiàn)線程通信
代碼如下:
@interface ViewController () <NSPortDelegate>
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
}
- (void) task {
NSThread* thread = [[NSThread alloc] initWithBlock:^{
self.subThreadPort = [NSPort port];
self.subThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread setName:@"子線程"];
[thread start];
}
- (void)handlePortMessage:(id)message {
NSLog(@"%@", [NSThread currentThread]);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray* sendComponents = [NSMutableArray array];
NSData* data = [@"world" dataUsingEncoding:NSUTF8StringEncoding];
[sendComponents addObject:data];
[self.mainThreadPort sendBeforeDate:[NSDate date] components:sendComponents from:self.subThreadPort reserved:0];
return;
}
sleep(2);
NSMutableArray* components = [message valueForKey:@"components"];
if ([components count] > 0) {
NSData* data = [components objectAtIndex:0];
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
@end
NSThread需要注意的地方
子線程執(zhí)行太快,還會(huì)調(diào)用線程通信的代碼嗎?
不會(huì)
有時(shí)候會(huì)出現(xiàn)這個(gè)問(wèn)題,代碼如下:
NSThread * t1 = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t1 start];
//不執(zhí)行地方原因,是因?yàn)?demo 方法執(zhí)行的快!""
[self performSelector:@selector(otherMethod) onThread:t1 withObject:nil waitUntilDone:NO];
-(void)demo{
NSLog(@"%@",[NSThread currentThread]);
}
-(void)otherMethod{
self.finished = YES;
}
- Demo執(zhí)行額太快,因?yàn)樽泳€程是沒(méi)有RunLoop的,當(dāng)demo執(zhí)行完成以后就消失了,所以不會(huì)在執(zhí)行
otherMethod。
如何解決上述問(wèn)題
在子線程開(kāi)啟RunLoop,
NSThread * t1 = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t1 start];
//不執(zhí)行地方原因,是因?yàn)?demo 方法執(zhí)行的快!""
[self performSelector:@selector(otherMethod) onThread:t1 withObject:nil waitUntilDone:NO];
-(void)demo{
NSLog(@"%@",[NSThread currentThread]);
[[NSRunLoop currentRunLoop] run];
}
-(void)otherMethod{
NSLog(@"%s %@",__FUNCTION__,[NSThread currentThread]);
}
RunLoop開(kāi)啟了循環(huán),這樣就會(huì)無(wú)限制的進(jìn)行循環(huán),這樣這個(gè)子線程就永遠(yuǎn)不會(huì)釋放
改進(jìn)的辦法就是,從外面創(chuàng)建一個(gè)
BOOL來(lái)判斷是否需要關(guān)閉RunLoop
@interface ViewController ()
/** 循環(huán)條件 */
@property(assign,nonatomic,getter=isFinished)BOOL finished;
@end
@implementation ViewController
NSThread * t1 = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t1 start];
self.finished = NO;
//不執(zhí)行地方原因,是因?yàn)?demo 方法執(zhí)行的快!""
[self performSelector:@selector(otherMethod) onThread:t1 withObject:nil waitUntilDone:NO];
-(void)demo{
NSLog(@"%@",[NSThread currentThread]);
//啟動(dòng)當(dāng)前RunLoop 哥么就是一個(gè)死循環(huán)!!
//使用這種方式,可以自己創(chuàng)建一個(gè)線程池!
// [[NSRunLoop currentRunLoop] run];
//在OC中使用比較多的,退出循環(huán)的方式!
while (!self.isFinished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}
-(void)otherMethod{
for (int i = 0; i<10; i++) {
NSLog(@"%s %@",__FUNCTION__,[NSThread currentThread]);
}
self.finished = YES;
}

