4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)
本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末
如果觉得本文内容过长,请前往本人 “简书”
本文源码 Demo 详见 Github
https://github.com/shorfng/iOS-4.0-multithreading.git
因为Pthread很少用到,所以对于Pthread的知识没有抠那么细致,所以将Pthread和 NSThread放在了一起。
4.1 Pthread
4.1-1.0 创建线程 - pthread_create
1 /* 2 <#pthread_t *restrict#> 线程的id,指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 * 3 <#const pthread_attr_t *restrict#> 用来设置线程的属性 (一般为 NULL) 4 <#void *(*)(void *)#> 新建立的线程执行代码的函数,线程开启后需要调用的函数或方法 (指向函数的指针) 5 <#void *restrict#> 运行函数的参数,线程的限制 (一般为 NULL) 6 */ 7 8 返回值: 9 - 若线程创建成功,则返回0 10 - 若线程创建失败,则返回出错编号 11 12 pthread_create( 13 <#pthread_t *restrict#>, // pthread_t :线程标识符. 14 <#const pthread_attr_t *restrict#>, 15 <#void *(*)(void *)#>, 16 <#void *restrict#> 17 );
4.1-1.1 __bridge 桥接
__bridge 桥接:
1、在c语言和OC之间,对数据类型进行转成换
2、通过桥接告诉c语言的函数,name就由C语言去管了
|
桥接的目的 :
就是为了告诉编译器如何管理内存,为OC添加自动内存管理操作
|
小结 :
number = 1: 表示 主线程
number != 1: 表示 子线程
C语言中 void * == OC中的id
C语言的数据类型,一般以 Ref / _t
|
|
4.1-1.2【代码】Pthread
1 首先导入头文件 2 3 #import <pthread.h> 4 5 代码创建: 6 7 // 创建线程,并且在线程中执行 demo 函数 8 - (void)pthreadDemo { 9 10 pthread_t threadId = NULL; 11 NSString *str = @"Hello Pthread"; 12 13 int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str)); 14 15 if (result == 0) { 16 NSLog(@"创建线程 OK"); 17 } else { 18 NSLog(@"创建线程失败 %d", result); 19 } 20 } 21 22 // 后台线程调用函数 23 void *demo(void *params) { 24 NSString *str = (__bridge NSString *)(params); 25 26 NSLog(@"%@ - %@", [NSThread currentThread], str); 27 28 return NULL; 29 }
4.2 NSThread
4.2-1.0 创建线程
|
4.2-1.1 创建线程1 - 对象方法alloc/init
一个NSThread对象就代表一条线程
1 创建方式1 : 通过NSThread的对象方法 (先创建初始化线程alloc/init , 再 start 开启线程) ——调试方便 2 3 NSThread *thread = [[NSThread alloc]initWithTarget:<#(nonnull id)#> 4 selector:<#(nonnull SEL)#> 5 object:<#(nullable id)#> ];
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @end 5 6 @implementation ViewController 7 8 - (void)viewDidLoad { 9 [super viewDidLoad]; 10 // Do any additional setup after loading the view, typically from a nib. 11 } 12 13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 14 [self threadDemo1]; 15 } 16 17 #pragma mark - 对象方法alloc/init 18 /* 19 - 在 OC 中,任何一个方法的代码都是从上向下顺序执行的 20 - 同一个方法内的代码,都是在相同线程执行的(block除外) 21 */ 22 - (void)threadDemo1 { 23 NSLog(@"before %@", [NSThread currentThread]); 24 25 // 线程一启动,就会在线程thread中执行self的run方法 26 NSThread *thread = [[NSThread alloc] initWithTarget:self 27 selector:@selector(longOperation:) 28 object:@"THREAD"]; 29 30 //开启线程,通过start方法,就会将我们创建出来的当前线程加入到`可调度线程池`,供CPU调度 31 //[thread start];执行后,会在另外一个线程执行 longOperation: 方法 32 [thread start]; 33 34 NSLog(@"after %@", [NSThread currentThread]); 35 } 36 37 - (void)longOperation:(id)obj { 38 NSLog(@"%@ - %@", [NSThread currentThread], obj); 39 } 40 41 - (void)didReceiveMemoryWarning { 42 [super didReceiveMemoryWarning]; 43 // Dispose of any resources that can be recreated. 44 } 45 46 @end
打印结果: 2016-03-17 18:19:41.878 创建线程1 - 对象方法[2543:387435] before <NSThread: 0x7ffd28c00ca0>{number = 1, name = main} 2016-03-17 18:19:41.880 创建线程1 - 对象方法[2543:387711] <NSThread: 0x7ffd28d77be0>{number = 2, name = (null)} - THREAD 2016-03-17 18:19:41.880 创建线程1 - 对象方法[2543:387435] after <NSThread: 0x7ffd28c00ca0>{number = 1, name = main}
4.2-1.1.1 Target
NSThread 的实例化方法中的 target 指的是开启线程后,在线程中执行 哪一个对象 的 @selector 方法
1 NSThread *thread = [[NSThread alloc] initWithTarget:self.person 2 selector:@selector(longOperation:) 3 object:@"THREAD"]; 4 5 [thread start];
- 通过指定不同的 target 会在后台线程执行该对象的 @selector 方法
- 不要看见 target 就写 self
4.2-1.2 创建线程2 - 类方法
1 创建方式2 : 通过NSThread的类方法 (创建线程后直接自动启动线程) 2 3 [NSThread detachNewThreadSelector:<#(nonnull SEL)#> 4 toTarget:<#(nonnull id)#> 5 withObject:<#(nullable id)#> ];
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @end 5 6 @implementation ViewController 7 8 - (void)viewDidLoad { 9 [super viewDidLoad]; 10 // Do any additional setup after loading the view, typically from a nib. 11 } 12 13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 14 [self threadDemo2]; 15 } 16 17 #pragma mark - 类方法 18 - (void)threadDemo2 { 19 NSLog(@"before %@", [NSThread currentThread]); 20 21 // detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行@selector方法 22 // 它会自动给我们做两件事 : 1.创建线程对象 2.添加到`可调度线程池` 23 // 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度 24 [NSThread detachNewThreadSelector:@selector(longOperation:) 25 toTarget:self 26 withObject:@"DETACH"]; 27 28 NSLog(@"after %@", [NSThread currentThread]); 29 } 30 31 - (void)longOperation:(id)obj { 32 NSLog(@"%@ - %@", [NSThread currentThread], obj); 33 } 34 35 - (void)didReceiveMemoryWarning { 36 [super didReceiveMemoryWarning]; 37 // Dispose of any resources that can be recreated. 38 } 39 40 @end
打印结果: 2016-03-17 18:36:05.339 创建线程2 - 类方法[2647:404930] before <NSThread: 0x7fddf8f01eb0>{number = 1, name = main} 2016-03-17 18:36:05.340 创建线程2 - 类方法[2647:404930] after <NSThread: 0x7fddf8f01eb0>{number = 1, name = main} 2016-03-17 18:36:05.340 创建 线程2 - 类方法[2647:405061] <NSThread: 0x7fddf8e0e7a0>{number = 2, name = (null)} - DETACH
4.2-1.3 创建线程3 - 分类方法(NSObject)
1 创建方式3 : 通过NSObject的分类方法 (隐式创建并直接自动启动线程) ——推荐,开发常用 2 3 // 此方法在后台线程中执行 (即是 : 在子线程中执行) 4 [self performSelectorInBackground:<#(nonnull SEL) #> 5 withObject:<#(nullable id) #> per];
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @end 5 6 @implementation ViewController 7 8 - (void)viewDidLoad { 9 [super viewDidLoad]; 10 // Do any additional setup after loading the view, typically from a nib. 11 } 12 13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 14 [self threadDemo3]; 15 } 16 17 #pragma mark - 分类方法 18 - (void)threadDemo3 { 19 NSLog(@"before %@", [NSThread currentThread]); 20 21 // performSelectorInBackground 是NSObject的分类方法,会自动在后台线程执行@selector方法 22 // 没有 thread 字眼,隐式创建并启动线程 23 // 所有 NSObject 都可以使用此方法,在其他线程执行方法 24 // 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度 25 // PerformSelectorInBackground 可以让方便地在后台线程执行任意NSObject对象的方法 26 [self performSelectorInBackground:@selector(longOperation:) 27 withObject:@"PERFORM"]; 28 29 NSLog(@"after %@", [NSThread currentThread]); 30 } 31 32 - (void)longOperation:(id)obj { 33 NSLog(@"%@ - %@", [NSThread currentThread], obj); 34 } 35 36 - (void)didReceiveMemoryWarning { 37 [super didReceiveMemoryWarning]; 38 // Dispose of any resources that can be recreated. 39 } 40 41 @end
打印结果: 2016-03-17 18:41:58.696 创建线程3 - 分类方法[2711:412522] before <NSThread: 0x7ff078c02320>{number = 1, name = main} 2016-03-17 18:41:58.696 创建线程3 - 分类方法[2711:412522] after <NSThread: 0x7ff078c02320>{number = 1, name = main} 2016-03-17 18:41:58.697 创建线程3 - 分类方法[2711:412751] <NSThread: 0x7ff078c0e390>{number = 2, name = (null)} - PERFORM
方式2和方式3的优缺点 : |
优点:简单快捷
缺点:无法对线程进行更详细的设置
|
4.2-2.0 线程属性
1 主线程相关方法 : 2 3 + (NSThread *)mainThread; // 获得主线程 4 - (BOOL)isMainThread; // 是否为主线程 5 + (BOOL)isMainThread; // 是否为主线程 6 7 NSThread *main = [NSThread mainThread]; // + (NSThread *)mainThread; 获得主线程 8 9 [NSThread isMainThread]; // + (BOOL)isMainThread; 类方法判断,该方法是否为主线程 10 11 [main isMainThread]; // - (BOOL)isMainThread; 对象方法判断,该对象是否为主线程
1 其他用法: 2 (1) currentThread - 获得当前线程 : 3 4 举例 : 5 NSThread *current = [NSThread currentThread]; //获得当前线程 6 7 (2) threadPriority - 线程的调度优先级 : 8 9 优先级,是一个浮点数,取值范围从 0~1.0 默认优先级是0.5 值越大,优先级越高 10 11 优先级高只是保证 CPU 调度的可能性会高 12 13 建议:在开发的时候,不要修改优先级 14 多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互! 15 多线程开发的原则:简单 16 17 //返回当前方法所在线程的优先级 18 + (double)threadPriority; 19 举例:[NSThread threadPriority]; 20 21 //设置线程的优先级 22 + (BOOL)setThreadPriority:(double)p; 23 举例:self.thread1.threadPriority = 1.0; 24 25 - (double)threadPriority;//返回当前方法所在线程的优先级 26 - (BOOL)setThreadPriority:(double)p;//设置线程的优先级 27 28 (3) name - 线程的名字 : 在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程 29 30 - (void)setName:(NSString *)n; //set 方法 31 - (NSString *)name; //get 方法 32 33 举例: 34 thread.name = @"线程A"; 35 36 (4) stackSize - 栈区大小 37 38 - 默认情况下,无论是主线程还是子线程,栈区大小都是 512K 39 - 栈区大小虽然可以设置,但是我们一般都使用系统默认的大小就行了 40 41 举例: 42 [NSThread currentThread].stackSize = 1024 * 1024;
代码示例:
1 // 线程属性 2 - (void)threadProperty { 3 NSThread *t1 = [[NSThread alloc] initWithTarget:self 4 selector:@selector(demo) 5 object:nil]; 6 7 // 1. 线程名称 8 t1.name = @"Thread AAA"; 9 10 // 2. 优先级 11 t1.threadPriority = 0; 12 13 [t1 start]; 14 15 NSThread *t2 = [[NSThread alloc] initWithTarget:self 16 selector:@selector(demo) 17 object:nil]; 18 // 1. 线程名称 19 t2.name = @"Thread BBB"; 20 // 2. 优先级 21 t2.threadPriority = 1; 22 23 [t2 start]; 24 } 25 26 - (void)demo { 27 for (int i = 0; i < 10; ++i) { 28 // 堆栈大小 29 NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread],[NSThread currentThread].stackSize / 1024); 30 } 31 32 // 模拟崩溃 33 // 判断是否是主线程 34 if (![NSThread currentThread].isMainThread) { 35 NSMutableArray *a = [NSMutableArray array]; 36 [a addObject:nil]; 37 } 38 }
4.2-3.0 线程状态/线程生命周期
|
(1)新建状态 : 实例化线程对象
说明:创建线程有多种方式,这里不做过多的介绍
(2)就绪状态 / 启动线程: ( 进入就绪状态 ->运行状态。当线程任务执行完毕,自动进入死亡状态 )
线程开启 : 线程进入可调度线程池
|
(3)运行状态:
|
|
(4)阻塞状态 / 暂停线程 : 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
1 方法执行过程,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态 2 3 sleepForTimeInterval: //休眠指定时长 (从现在起睡多少秒) 4 sleepUntilDate: //休眠到指定日期 (从现在起睡到指定的日期) 5 @synchronized(self) { } //互斥锁 6 7 (1)阻塞2秒 8 [NSThread sleepForTimeInterval:2]; // 阻塞状态 9 10 (2)以当前时间为基准阻塞4秒 11 NSDate *date = [NSDate dateWithTimeIntervalSinceNow:4.0]; //从现在开始多少秒 12 [NSThread sleepUntilDate:date]; //睡眠多少秒
(5)死亡状态 (一旦线程停止或死亡了,就不能再次开启任务 , 后续的所有代码都不会被执行 )
(1) 正常死亡
(2) 非正常死亡
注意:在终止线程之前,应该注意释放之前分配的对象!
|
生命周期示意图:
代码示例:
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @end 5 6 @implementation ViewController 7 8 - (void)viewDidLoad { 9 [super viewDidLoad]; 10 // Do any additional setup after loading the view, typically from a nib. 11 } 12 13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 14 15 // 1.在主线程中创建一个子线程(实例化线程对象) ---> 新建状态 16 NSThread *Th = 17 [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; 18 19 // 2.将 Th 线程加入到可调度线程池,等待CPU调度--->就绪状态 20 [Th start]; 21 22 // 3.让主线程阻塞,让当前线程(主线程)休眠 23 [NSThread sleepForTimeInterval:1.0]; 24 25 // 4.在主线程给 Th 线程打死亡标签 26 [Th cancel]; //只是打了个标签,并没有执行,需要在子线程中 27 } 28 29 // Th 线程---> 运行状态 30 - (void)run { 31 32 NSThread *huThread = [NSThread currentThread]; 33 34 CGMutablePathRef path = CGPathCreateMutable(); 35 36 for (int i = 0; i < 30; i++) { 37 if ([huThread isCancelled]) { 38 NSLog(@"good bye1"); 39 return; // --->非正常死亡(被逼着死亡) 40 } 41 42 if (i == 5) { 43 [NSThread sleepForTimeInterval:3.0]; //--->huThread阻塞状态3秒 44 // [NSThread sleepUntilDate:[NSDate distantFuture]]; // 睡到遥远的未来 45 // [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; //线程睡到从现在开始后的2秒为止 46 } 47 48 if ([huThread isCancelled]) { 49 NSLog(@"good bye2"); 50 return; 51 } 52 53 if (i == 20) { 54 //清空资源 55 CGPathRelease(path); 56 57 //在调用下面方法之前,必须清空资源 非正常死亡--自杀(退出线程) 58 [NSThread exit]; 59 } 60 61 if ([huThread isCancelled]) { 62 NSLog(@"good bye3"); 63 return; 64 } 65 NSLog(@"%d", i); 66 } 67 } //--->huThread死亡状态 (正常死亡状态) 68 69 - (void)didReceiveMemoryWarning { 70 [super didReceiveMemoryWarning]; 71 // Dispose of any resources that can be recreated. 72 } 73 74 @end
4.2-4.1 多线程安全隐患 - 资源共享/抢夺
(1) 起因 :
资源共享概念 : 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
主要是因为多条线程,对`同一资源同时操作`,导致的问题
|
(2) 举例 : 比如多个线程访问同一个对象、同一个变量、同一个文件 |
(3) 结果 : 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题 (资源强夺) |
(4) 解决方案 : 互斥锁 / 同步锁 |
代码示例 : (卖票案例)
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
|
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @property(nonatomic, strong) NSThread *thread01; // 售票员01 5 @property(nonatomic, strong) NSThread *thread02; // 售票员02 6 @property(nonatomic, strong) NSThread *thread03; // 售票员03 7 8 @property(nonatomic, assign) NSInteger ticketCount; //票的总数 9 @end 10 11 @implementation ViewController 12 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 // Do any additional setup after loading the view, typically from a nib. 16 17 self.ticketCount = 10; 18 19 //创建线程 20 self.thread01 = [[NSThread alloc] initWithTarget:self 21 selector:@selector(saleTicket) 22 object:nil]; 23 self.thread01.name = @"售票员01"; 24 25 self.thread02 = [[NSThread alloc] initWithTarget:self 26 selector:@selector(saleTicket) 27 object:nil]; 28 self.thread02.name = @"售票员02"; 29 30 self.thread03 = [[NSThread alloc] initWithTarget:self 31 selector:@selector(saleTicket) 32 object:nil]; 33 self.thread03.name = @"售票员03"; 34 } 35 36 // 开启线程 37 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 38 [self.thread01 start]; 39 [self.thread02 start]; 40 [self.thread03 start]; 41 } 42 43 // 卖票 44 - (void)saleTicket { 45 46 while (1) { 47 48 @synchronized(self) { //互斥锁(控制器做锁对象) 49 // 先取出总数 50 NSInteger count = self.ticketCount; 51 52 // 判断还有没有余票 53 if (count > 0) { 54 self.ticketCount = count - 1; 55 NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, 56 self.ticketCount); 57 } else { 58 NSLog(@"票已经卖完了"); 59 break; 60 } 61 } 62 } 63 } 64 65 - (void)didReceiveMemoryWarning { 66 [super didReceiveMemoryWarning]; 67 // Dispose of any resources that can be recreated. 68 } 69 70 @end
打印结果: 2016-03-17 19:37:27.429 线程安全[3386:472835] 售票员02卖了一张票,还剩下9张 2016-03-17 19:37:27.430 线程安全[3386:472836] 售票员03卖了一张票,还剩下8张 2016-03-17 19:37:27.430 线程安全[3386:472834] 售票员01卖了一张票,还剩下7张 2016-03-17 19:37:27.430 线程安全[3386:472835] 售票员02卖了一张票,还剩下6张 2016-03-17 19:37:27.430 线程安全[3386:472836] 售票员03卖了一张票,还剩下5张 2016-03-17 19:37:27.430 线程安全[3386:472834] 售票员01卖了一张票,还剩下4张 2016-03-17 19:37:27.431 线程安全[3386:472835] 售票员02卖了一张票,还剩下3张 2016-03-17 19:37:27.431 线程安全[3386:472836] 售票员03卖了一张票,还剩下2张 2016-03-17 19:37:27.431 线程安全[3386:472834] 售票员01卖了一张票,还剩下1张 2016-03-17 19:37:27.431 线程安全[3386:472835] 售票员02卖了一张票,还剩下0张 2016-03-17 19:37:27.432 线程安全[3386:472836] 票已经卖完了 2016-03-17 19:37:27.434 线程安全[3386:472834] 票已经卖完了 2016-03-17 19:37:27.434 线程安全[3386:472835] 票已经卖完了
4.2-4.2 安全隐患解决 – 互斥锁 / 同步锁
互斥锁使用技术 : 线程同步
概念:多条线程按顺序地执行任务 |
引申 : 互斥锁,就是使用了线程同步技术 |
互斥锁使用格式 :
1 //锁对象为能够加锁的任意 NSObject 对象 2 //锁对象一定要保证所有的线程都能够访问 3 //如果代码中只有一个地方需要加锁,大多都使用self,这样可以避免单独再创建一个锁对象 4 5 @synchronized(锁对象) { 6 //需要锁定的代码 7 }
注意:
|
互斥锁的使用前提:
多条线程抢夺同一块资源 |
互斥锁的优缺点 :
优点:能有效防止因多线程抢夺资源造成的数据安全问题 , 能保证数据的准确性 |
缺点:需要消耗大量的CPU资源 , 可能会导致执行速度变慢 |
4.2-4.3 原子属性/非原子属性
原子和非原子属性:
(默认) atomic | 原子属性 | 为setter方法加锁 | 线程安全,需要消耗大量的资源 |
(推荐) nonatomic | 非原子属性 | 不会为setter方法加锁 | 非线程安全,适合内存小的移动设备 |
atomic加锁原理:
1 @property (assign, atomic) int age; 2 - (void)setAge:(int)age 3 { 4 @synchronized(self) { 5 _age = age; 6 } 7 }
iOS开发的建议:
|
4.2-5.0 线程间通讯和常用方法
线程间通信:
概念 : 在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 |
线程间通信的体现 :
|
线程间通信常用方法 :
1 //在主线程执行操作 (从子线程回到主线程)(推荐) 2 - (void)performSelectorOnMainThread:(SEL)aSelector 3 withObject:(id)arg 4 waitUntilDone:(BOOL)wait; 5 6 - (void)performSelector:(SEL)aSelector 7 onThread:(NSThread *)thr 8 withObject:(id)arg 9 waitUntilDone:(BOOL)wait;
另外一种线程之间的通信方式:NSPort(端口)
包括的子类:
(1)NSMessagePort;
(2)NSMachPort;
|
1 #import "ViewController.h" 2 3 @interface ViewController () 4 @property(weak, nonatomic) IBOutlet UIImageView *imageView; 5 @end 6 7 @implementation ViewController 8 9 - (void)viewDidLoad { 10 [super viewDidLoad]; 11 // Do any additional setup after loading the view, typically from a nib. 12 } 13 14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 15 // 开启子线程(在子线程中调用download方法下载图片) 16 [self performSelectorInBackground:@selector(download) withObject:nil]; 17 } 18 19 #pragma mark - 图片下载 20 - (void)download { 21 // 1.图片的网络路径 22 NSURL *url = 23 [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; 24 25 // 2.下载图片并把图片转换为二进制的数据(耗时操作) 26 NSData *data = [NSData dataWithContentsOfURL:url]; 27 28 // 3.生成图片(把数据转换成图片) 29 UIImage *image = [UIImage imageWithData:data]; 30 31 // 4.回到主线程中设置图片 32 // 方法1: 33 [self.imageView 34 performSelector:@selector(setImage:) 35 onThread:[NSThread mainThread] 36 withObject:image 37 waitUntilDone:NO]; //是否等到@selector的方法完成后再往下执行,NO表示否 38 39 //方法2: 40 [self.imageView performSelectorOnMainThread:@selector(setImage:) 41 withObject:image 42 waitUntilDone:NO]; 43 // 方法3:代码一 44 [self performSelectorOnMainThread:@selector(showImage:) 45 withObject:image 46 waitUntilDone:NO]; 47 } 48 49 // 方法3:代码二 50 - (void)showImage:(UIImage *)image { 51 self.imageView.image = image; //设置显示图片 52 } 53 54 #pragma mark - 测试图片下载时间 方法1:NSDate 55 - (void)download1 { 56 // 1.图片的网络路径 57 NSURL *url = 58 [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; 59 60 NSDate *begin = [NSDate date]; //开始时间 61 62 // 2.根据图片的网络路径去下载图片数据 63 NSData *data = [NSData dataWithContentsOfURL:url]; 64 65 NSDate *end = [NSDate date]; //结束时间 66 67 NSLog(@"%f", [end timeIntervalSinceDate:begin]); // 时间间隔 = 开始-结束 68 69 // 3.显示图片 70 self.imageView.image = [UIImage imageWithData:data]; 71 } 72 73 #pragma mark - 测试图片下载时间 方法2:CFTimeInterval 74 - (void)download2 { 75 // 1.图片的网络路径 76 NSURL *url = 77 [NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; 78 79 CFTimeInterval begin = CFAbsoluteTimeGetCurrent(); // 开始时间 80 81 // 2.根据图片的网络路径去下载图片数据 82 NSData *data = [NSData dataWithContentsOfURL:url]; 83 84 CFTimeInterval end = CFAbsoluteTimeGetCurrent(); //结束时间 85 86 NSLog(@"%f", end - begin); // 时间间隔 = 开始-结束 87 88 // 3.显示图片 89 self.imageView.image = [UIImage imageWithData:data]; 90 } 91 92 - (void)didReceiveMemoryWarning { 93 [super didReceiveMemoryWarning]; 94 // Dispose of any resources that can be recreated. 95 } 96 97 @end
如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
如有疑问,请发送邮件至 shorfng@126.com 联系我。