iOS 多线程学习笔记 —— NSThread
本文复制、参考自文章:iOS多线程编程之NSThread的使用 ,主要为了加强个人对知识的理解和记忆,不做他用。原作者声明:
著作权声明:本文由http://blog.csdn.net/totogo2010/原创,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
这里对原作者的辛勤工作表示感谢!
1. 简介
1.1 iOS的多线程编程技术分类
(1)NSThread
(2) Cocoa NSOperation
(3) GCD (Grand Central Dispatch)
这三种方式从上到下,抽象层次逐渐增高,使用也越来越简单。
1.2 三种方式的优缺点
优点 | 缺点 | |
NSThread | 轻量 | 需要自己管理线程的生命周期,线程同步。线程同步加锁时,会有一定的系统开销。 |
NSOperation | 无需关心线程管理,数据同步,可以把精力放在自己需要的执行操作上 | |
GCD | iOS4.0后出现,以替代NSThread,NSOperation等技术的,很高效、强大 |
2. NSThread的使用
2.1 创建方式
(1) 实例方法创建
- (id) initWithTarget:(id)target selector:(SEL)selector object:(id) argument
示例: NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:nil]; [myThread start];
(2) NSThread 类方法创建
+ (void)detachNewThreadSelector:(SEL) aSelector toTarget:(id)aTarget withObject:(id)anArgument 示例: [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
(3)NSObject 非显示方法创建
+ (void) performSelectorInBackground:(SEL)aSelector withObject:(id)anArgument 示例 [Obj performSelectorInBackground:@selector(doSomething:)withObject:nil];
selector: 线程执行的方法,这个selector只能有一个参数,而且不能有返回值;
target: selector消息发送的对象
argument:传输给selector的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程,第二种方法是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息。
2.2 使用示例1: 异步加载图片
(1)在viewDidLoad中创建子线程:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:kURL]; // 子线程执行方法 downloadImage [thread start]; // 启用子线程 }
(2)在线程执行方法 downloadImage 中执行加载数据操作,并使用performSelectorOnMainThread方法 通知主线程进行渲染操作。(线程调回主线程,并传递了数据)
- (void)downloadImage:(NSString *) url { NSData* data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]]; UIImage* image = [[UIImage alloc] initWithData:data]; if(image == nil) { NSLog(@"image load failed..."); } else { [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; // 与主线程通信,传递了image数据 } }
同样,还可以使用:
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array
方法来于其他线程通信。
2.3 使用示例2: 多线程卖票
(1) 定义public的数据结构,AppDelegate.h中
@interface AppDelegate : UIResponder <UIApplicationDelegate> { int tickets; int count; NSThread* ticketsThreadOne; NSThread* ticketsThreadTwo; NSCondition* ticketsCondition; NSLock* theLock; }
这里测试使用了NSCondition和NSLock两种加锁方法。
(2)初始化, AppDelegate.m中
tickets = 100; count = 0; theLock = [[NSLock alloc] init]; // 锁对象 ticketsCondition = [[NSCondition alloc] init]; ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [ticketsThreadOne setName:@"Thread-1"]; [ticketsThreadOne start]; ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [ticketsThreadTwo setName:@"Thread-2"]; [ticketsThreadTwo start];
(3)执行操作中,加锁,防止资源抢占
- (void)run { while (TRUE) { // 上锁 // [theLock lock]; [ticketsCondition lock]; if (tickets >= 0) { [NSThread sleepForTimeInterval:0.09]; count = 100 -tickets; NSLog(@"当前票数是:%d, 售出:%d, 线程名:%@", tickets, count, [[NSThread currentThread] name]); tickets--; } else { break; } [ticketsCondition unlock]; // [theLock unlock]; } }
NSCondition 可以使用 [ticketsCondition wait];等待,并由其他线程使用[ticketsCondition signal];唤起等待。
2.4 使用@synchoronized
我们还可以使用@synchoronized来简化NSLock的使用,这样就不必显示创建和调用NSLock对象,而自动创建了一个互斥锁(mutex lock),防止对资源的抢占。 如下
obj1 = [[NSObject alloc]init]; // 两个线程,分别传递两个对象用于原操作标识 obj2 = [[NSObject alloc]init]; ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:obj1]; [ticketsThreadOne setName:@"Thread-1"]; [ticketsThreadOne start]; ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:obj2]; [ticketsThreadTwo setName:@"Thread-2"]; [ticketsThreadTwo start];
- (void)run:(NSObject*) obj { @synchronized(obj) { while (TRUE) { // 上锁 // [theLock lock]; // [ticketsCondition lock]; int currentTicketNum = [ticketCounter currentTicketNum]; if ( currentTicketNum >= 0) { [NSThread sleepForTimeInterval:0.09]; NSLog(@"当前票数是:%d, 售出:%d, 线程名:%@", currentTicketNum, ticketCounter.initTicketNum - currentTicketNum, [[NSThread currentThread] name]); ticketCounter.currentTicketNum--; } else { break; } // [ticketsCondition unlock]; // [theLock unlock]; } } }