本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末

如果觉得本文内容过长,请前往本人 “简书

本文源码 Demo 详见 Github
https://github.com/shorfng/iOS-4.0-multithreading.git

因为Pthread很少用到,所以对于Pthread的知识没有抠那么细致,所以将Pthread和 NSThread放在了一起。

4.1 Pthread


4.1-1.0 创建线程 - pthread_create

 /*
<#pthread_t *restrict#> 线程的id,指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
<#const pthread_attr_t *restrict#> 用来设置线程的属性 (一般为 NULL)
<#void *(*)(void *)#> 新建立的线程执行代码的函数,线程开启后需要调用的函数或方法 (指向函数的指针)
<#void *restrict#> 运行函数的参数,线程的限制 (一般为 NULL)
*/ 返回值:
- 若线程创建成功,则返回0
- 若线程创建失败,则返回出错编号 pthread_create(
<#pthread_t *restrict#>, // pthread_t :线程标识符.
<#const pthread_attr_t *restrict#>,
<#void *(*)(void *)#>,
<#void *restrict#>
);

4.1-1.1 __bridge 桥接

__bridge 桥接:
1、在c语言和OC之间,对数据类型进行转成换
2、通过桥接告诉c语言的函数,name就由C语言去管了
桥接的目的 : 
就是为了告诉编译器如何管理内存,为OC添加自动内存管理操作
 
小结 :
  • 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的
  • 通常,在 C 语言框架中,对象类型以 _t/Ref 结尾,而且声明时不需要使用 *
  • C 语言中的 void * 和 OC 中的 id 是等价的
  • 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用 __bridge 进行桥接,
  • 桥接的添加可以借助 Xcode 的辅助功能添加
 
number = 1: 表示 主线程
number != 1: 表示 子线程
 
C语言中 void * == OC中的id
C语言的数据类型,一般以  Ref / _t
  • OC框架 Foundation
  • C语言 Core Foundation
 
 4.1-1.2【代码】Pthread
 首先导入头文件

 #import <pthread.h>

 代码创建:

 // 创建线程,并且在线程中执行 demo 函数
- (void)pthreadDemo { pthread_t threadId = NULL;
NSString *str = @"Hello Pthread"; int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str)); if (result == ) {
NSLog(@"创建线程 OK");
} else {
NSLog(@"创建线程失败 %d", result);
}
} // 后台线程调用函数
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params); NSLog(@"%@ - %@", [NSThread currentThread], str); return NULL;
}

4.2  NSThread


4.2-1.0 创建线程

  • 第一种:通过NSThread的对象方法 (alloc / init - start)
  • 第二种:通过NSThread的类方法    (detachNewThreadSelector)
  • 第三种:通过NSObject的方法
 4.2-1.1 创建线程1 - 对象方法alloc/init

一个NSThread对象就代表一条线程
 创建方式1 : 通过NSThread的对象方法 (先创建初始化线程alloc/init , 再 start 开启线程)   ——调试方便

 NSThread *thread = [[NSThread alloc]initWithTarget:<#(nonnull id)#>
selector:<#(nonnull SEL)#>
object:<#(nullable id)#> ];
 #import "ViewController.h"

 @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self threadDemo1];
} #pragma mark - 对象方法alloc/init
/*
- 在 OC 中,任何一个方法的代码都是从上向下顺序执行的
- 同一个方法内的代码,都是在相同线程执行的(block除外)
*/
- (void)threadDemo1 {
NSLog(@"before %@", [NSThread currentThread]); // 线程一启动,就会在线程thread中执行self的run方法
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(longOperation:)
object:@"THREAD"]; //开启线程,通过start方法,就会将我们创建出来的当前线程加入到`可调度线程池`,供CPU调度
//[thread start];执行后,会在另外一个线程执行 longOperation: 方法
[thread start]; NSLog(@"after %@", [NSThread currentThread]);
} - (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @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 方法
 NSThread *thread = [[NSThread alloc] initWithTarget:self.person
selector:@selector(longOperation:)
object:@"THREAD"]; [thread start];
  • 通过指定不同的 target 会在后台线程执行该对象的 @selector 方法
  • 不要看见 target 就写 self
4.2-1.2 创建线程2 - 类方法
 创建方式2 : 通过NSThread的类方法 (创建线程后直接自动启动线程)

 [NSThread detachNewThreadSelector:<#(nonnull SEL)#>
toTarget:<#(nonnull id)#>
withObject:<#(nullable id)#> ];
 #import "ViewController.h"

 @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self threadDemo2];
} #pragma mark - 类方法
- (void)threadDemo2 {
NSLog(@"before %@", [NSThread currentThread]); // detachNewThreadSelector 类方法不需要启动,会自动创建线程并执行@selector方法
// 它会自动给我们做两件事 : 1.创建线程对象 2.添加到`可调度线程池`
// 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度
[NSThread detachNewThreadSelector:@selector(longOperation:)
toTarget:self
withObject:@"DETACH"]; NSLog(@"after %@", [NSThread currentThread]);
} - (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @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)

 创建方式3 : 通过NSObject的分类方法  (隐式创建并直接自动启动线程)   ——推荐,开发常用

 // 此方法在后台线程中执行 (即是 : 在子线程中执行)
[self performSelectorInBackground:<#(nonnull SEL) #>
withObject:<#(nullable id) #> per];
 #import "ViewController.h"

 @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self threadDemo3];
} #pragma mark - 分类方法
- (void)threadDemo3 {
NSLog(@"before %@", [NSThread currentThread]); // performSelectorInBackground 是NSObject的分类方法,会自动在后台线程执行@selector方法
// 没有 thread 字眼,隐式创建并启动线程
// 所有 NSObject 都可以使用此方法,在其他线程执行方法
// 通过NSThread的类和NSObject的分类方法直接加入到可调度线程池里面去,等待CPU调度
// PerformSelectorInBackground 可以让方便地在后台线程执行任意NSObject对象的方法
[self performSelectorInBackground:@selector(longOperation:)
withObject:@"PERFORM"]; NSLog(@"after %@", [NSThread currentThread]);
} - (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @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 线程属性

 主线程相关方法 :

 + (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程 NSThread *main = [NSThread mainThread]; // + (NSThread *)mainThread; 获得主线程 [NSThread isMainThread]; // + (BOOL)isMainThread; 类方法判断,该方法是否为主线程 [main isMainThread]; // - (BOOL)isMainThread; 对象方法判断,该对象是否为主线程
 其他用法:
() currentThread - 获得当前线程 : 举例 :
NSThread *current = [NSThread currentThread]; //获得当前线程 () threadPriority - 线程的调度优先级 : 优先级,是一个浮点数,取值范围从 ~1.0 默认优先级是0. 值越大,优先级越高 优先级高只是保证 CPU 调度的可能性会高 建议:在开发的时候,不要修改优先级
多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
多线程开发的原则:简单 //返回当前方法所在线程的优先级
+ (double)threadPriority;
举例:[NSThread threadPriority]; //设置线程的优先级
+ (BOOL)setThreadPriority:(double)p;
举例:self.thread1.threadPriority = 1.0; - (double)threadPriority;//返回当前方法所在线程的优先级
- (BOOL)setThreadPriority:(double)p;//设置线程的优先级 () name - 线程的名字 : 在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程 - (void)setName:(NSString *)n; //set 方法
- (NSString *)name; //get 方法 举例:
thread.name = @"线程A"; () stackSize - 栈区大小 - 默认情况下,无论是主线程还是子线程,栈区大小都是 512K
- 栈区大小虽然可以设置,但是我们一般都使用系统默认的大小就行了 举例:
[NSThread currentThread].stackSize = * ;

代码示例:

 // 线程属性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self
selector:@selector(demo)
object:nil]; // 1. 线程名称
t1.name = @"Thread AAA"; // 2. 优先级
t1.threadPriority = ; [t1 start]; NSThread *t2 = [[NSThread alloc] initWithTarget:self
selector:@selector(demo)
object:nil];
// 1. 线程名称
t2.name = @"Thread BBB";
// 2. 优先级
t2.threadPriority = ; [t2 start];
} - (void)demo {
for (int i = ; i < ; ++i) {
// 堆栈大小
NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread],[NSThread currentThread].stackSize / );
} // 模拟崩溃
// 判断是否是主线程
if (![NSThread currentThread].isMainThread) {
NSMutableArray *a = [NSMutableArray array];
[a addObject:nil];
}
}

4.2-3.0 线程状态/线程生命周期 

  • 新建状态
  • 就绪状态/启动状态 : 线程在可调度线程池中
  • 运行状态
  • 阻塞状态/暂停线程 : 线程不在可调度线程池中,但是仍然存在内存中,只是不可用
  • 死亡状态 : 线程不在内存中
 

(1)新建状态 : 实例化线程对象
说明:创建线程有多种方式,这里不做过多的介绍
 

 
(2)就绪状态 / 启动线程: ( 进入就绪状态 ->运行状态。当线程任务执行完毕,自动进入死亡状态 )
线程开启 : 线程进入可调度线程池
 
  • 向线程对象发送 start 消息,线程对象被加入可调度线程池等待 CPU 调度
  • detachNewThreadSelector 方法和 performSelectorInBackground 方法会直接实例化一个线程对象并加入可调度线程池

(3)运行状态:
  • CPU 负责调度可调度线程池中线程的执行
  • 线程执行完成之前(死亡之前),状态可能会在就绪和运行之间来回切换
  • 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
 
  • 当 CPU 调度当前线程 , 进入运行状态
  • 当 CPU 调度其他线程 , 进入就绪状态
(4)阻塞状态 / 暂停线程 : 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行

 方法执行过程,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态

 sleepForTimeInterval:    //休眠指定时长    (从现在起睡多少秒)
sleepUntilDate: //休眠到指定日期 (从现在起睡到指定的日期)
@synchronized(self) { } //互斥锁 ()阻塞2秒
[NSThread sleepForTimeInterval:]; // 阻塞状态 ()以当前时间为基准阻塞4秒
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:4.0]; //从现在开始多少秒
[NSThread sleepUntilDate:date]; //睡眠多少秒

(5)死亡状态 (一旦线程停止或死亡了,就不能再次开启任务 , 后续的所有代码都不会被执行 )
(1) 正常死亡
  • 线程执行完毕
 
(2) 非正常死亡
  • (自杀)          当满足某个条件后,在线程内部自己中止执行
  • (被逼着死亡) 当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.
 
注意:在终止线程之前,应该注意释放之前分配的对象! 
 生命周期示意图:
 
 
 代码示例:

 #import "ViewController.h"

 @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 1.在主线程中创建一个子线程(实例化线程对象) ---> 新建状态
NSThread *Th =
[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; // 2.将 Th 线程加入到可调度线程池,等待CPU调度--->就绪状态
[Th start]; // 3.让主线程阻塞,让当前线程(主线程)休眠
[NSThread sleepForTimeInterval:1.0]; // 4.在主线程给 Th 线程打死亡标签
[Th cancel]; //只是打了个标签,并没有执行,需要在子线程中
} // Th 线程---> 运行状态
- (void)run { NSThread *huThread = [NSThread currentThread]; CGMutablePathRef path = CGPathCreateMutable(); for (int i = ; i < ; i++) {
if ([huThread isCancelled]) {
NSLog(@"good bye1");
return; // --->非正常死亡(被逼着死亡)
} if (i == ) {
[NSThread sleepForTimeInterval:3.0]; //--->huThread阻塞状态3秒
// [NSThread sleepUntilDate:[NSDate distantFuture]]; // 睡到遥远的未来
// [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; //线程睡到从现在开始后的2秒为止
} if ([huThread isCancelled]) {
NSLog(@"good bye2");
return;
} if (i == ) {
//清空资源
CGPathRelease(path); //在调用下面方法之前,必须清空资源 非正常死亡--自杀(退出线程)
[NSThread exit];
} if ([huThread isCancelled]) {
NSLog(@"good bye3");
return;
}
NSLog(@"%d", i);
}
} //--->huThread死亡状态 (正常死亡状态) - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

4.2-4.1 多线程安全隐患 - 资源共享/抢夺

(1) 起因 : 
 
资源共享概念 : 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
 
主要是因为多条线程,对`同一资源同时操作`,导致的问题
(2) 举例 : 比如多个线程访问同一个对象、同一个变量、同一个文件
(3) 结果 : 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题 (资源强夺)
(4) 解决方案 : 互斥锁 / 同步锁 

代码示例 :  (卖票案例)
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
  • 首先确保单个线程执行正确
  • 添加线程
 #import "ViewController.h"

 @interface ViewController ()
@property(nonatomic, strong) NSThread *thread01; // 售票员01
@property(nonatomic, strong) NSThread *thread02; // 售票员02
@property(nonatomic, strong) NSThread *thread03; // 售票员03 @property(nonatomic, assign) NSInteger ticketCount; //票的总数
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. self.ticketCount = ; //创建线程
self.thread01 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread01.name = @"售票员01"; self.thread02 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread02.name = @"售票员02"; self.thread03 = [[NSThread alloc] initWithTarget:self
selector:@selector(saleTicket)
object:nil];
self.thread03.name = @"售票员03";
} // 开启线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
} // 卖票
- (void)saleTicket { while () { @synchronized(self) { //互斥锁(控制器做锁对象)
// 先取出总数
NSInteger count = self.ticketCount; // 判断还有没有余票
if (count > ) {
self.ticketCount = count - ;
NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name,
self.ticketCount);
} else {
NSLog(@"票已经卖完了");
break;
}
}
}
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @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 安全隐患解决 – 互斥锁 / 同步锁

互斥锁使用技术 : 线程同步
概念:多条线程按顺序地执行任务
引申 : 互斥锁,就是使用了线程同步技术
 
互斥锁使用格式 :

 //锁对象为能够加锁的任意 NSObject 对象
//锁对象一定要保证所有的线程都能够访问
//如果代码中只有一个地方需要加锁,大多都使用self,这样可以避免单独再创建一个锁对象 @synchronized(锁对象) {
//需要锁定的代码
}
注意:
  • 锁定1份代码只用1把锁,用多把锁是无效的
  • 保证锁内的代码,同一时间,只有一条线程能够执行!
  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
  • 速记 :  [[NSUserDefaults standardUserDefaults] synchronize];
 
互斥锁的使用前提:
多条线程抢夺同一块资源
 
互斥锁的优缺点 :
优点:能有效防止因多线程抢夺资源造成的数据安全问题 , 能保证数据的准确性
缺点:需要消耗大量的CPU资源 , 可能会导致执行速度变慢
 
 
4.2-4.3 原子属性/非原子属性

原子和非原子属性:
(默认) atomic 原子属性 为setter方法加锁 线程安全,需要消耗大量的资源
(推荐) nonatomic 非原子属性 不会为setter方法加锁 非线程安全,适合内存小的移动设备
 
atomic加锁原理:
 @property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}

iOS开发的建议:

  • 所有属性都声明为nonatomic
  • 尽量避免多线程抢夺同一块资源
  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
 
 4.2-5.0 线程间通讯和常用方法

线程间通信:
概念 : 在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
线程间通信的体现 : 
  • 1个线程传递数据给另1个线程
  • 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
 
 
线程间通信常用方法 :

 //在主线程执行操作 (从子线程回到主线程)(推荐)
- (void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg
waitUntilDone:(BOOL)wait; - (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thr
withObject:(id)arg
waitUntilDone:(BOOL)wait;

另外一种线程之间的通信方式:NSPort(端口)

包括的子类:
(1)NSMessagePort;
(2)NSMachPort;
 
 

4.2-5.1 图片下载示例

 #import "ViewController.h"

 @interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *imageView;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 开启子线程(在子线程中调用download方法下载图片)
[self performSelectorInBackground:@selector(download) withObject:nil];
} #pragma mark - 图片下载
- (void)download {
// 1.图片的网络路径
NSURL *url =
[NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; // 2.下载图片并把图片转换为二进制的数据(耗时操作)
NSData *data = [NSData dataWithContentsOfURL:url]; // 3.生成图片(把数据转换成图片)
UIImage *image = [UIImage imageWithData:data]; // 4.回到主线程中设置图片
// 方法1:
[self.imageView
performSelector:@selector(setImage:)
onThread:[NSThread mainThread]
withObject:image
waitUntilDone:NO]; //是否等到@selector的方法完成后再往下执行,NO表示否 //方法2:
[self.imageView performSelectorOnMainThread:@selector(setImage:)
withObject:image
waitUntilDone:NO];
// 方法3:代码一
[self performSelectorOnMainThread:@selector(showImage:)
withObject:image
waitUntilDone:NO];
} // 方法3:代码二
- (void)showImage:(UIImage *)image {
self.imageView.image = image; //设置显示图片
} #pragma mark - 测试图片下载时间 方法1:NSDate
- (void)download1 {
// 1.图片的网络路径
NSURL *url =
[NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; NSDate *begin = [NSDate date]; //开始时间 // 2.根据图片的网络路径去下载图片数据
NSData *data = [NSData dataWithContentsOfURL:url]; NSDate *end = [NSDate date]; //结束时间 NSLog(@"%f", [end timeIntervalSinceDate:begin]); // 时间间隔 = 开始-结束 // 3.显示图片
self.imageView.image = [UIImage imageWithData:data];
} #pragma mark - 测试图片下载时间 方法2:CFTimeInterval
- (void)download2 {
// 1.图片的网络路径
NSURL *url =
[NSURL URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; CFTimeInterval begin = CFAbsoluteTimeGetCurrent(); // 开始时间 // 2.根据图片的网络路径去下载图片数据
NSData *data = [NSData dataWithContentsOfURL:url]; CFTimeInterval end = CFAbsoluteTimeGetCurrent(); //结束时间 NSLog(@"%f", end - begin); // 时间间隔 = 开始-结束 // 3.显示图片
self.imageView.image = [UIImage imageWithData:data];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end


如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
 
 
作者:蓝田(Loto)
出处:http://www.cnblogs.com/shorfng/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
 

如有疑问,请发送邮件至 shorfng@126.com 联系我。
 

 

 

4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)的更多相关文章

  1. 4.4 多线程进阶篇<下>(NSOperation)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人"简书" 本文源码 Demo 详见 Github https://github.c ...

  2. 4.3 多线程进阶篇<中>(GCD)

    更正:队列名称的作用的图中,箭头标注的有些问题,已修正 本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Gith ...

  3. JVM性能调优与实战进阶篇-上

    ZGC 诞生原因 Java生态非常强大,但还不够,有些场景仍处于劣势,而ZGC的出现可以让Java语言抢占其他语言的某些特定领域市场.比如 谷歌主导的Android手机系统显示卡顿. 证券交易市场,实 ...

  4. java 网络编程(五)----TCP进阶篇上传文本文件

    设计需求:从客户端上传txt文件到服务器,服务端收到文件后,发送消息给客户端接收完成. 1. 服务器端: public class UpLoadFileServer { public static v ...

  5. form表单那点事儿(下) 进阶篇

    form表单那点事儿(下) 进阶篇 上一篇主要温习了一下form表单的属性和表单元素,这一片主要讲解用JavaScript如何操作form. 目录: 表单操作 取值 赋值 重置 校验 提交 技巧 不提 ...

  6. Java进阶篇(六)——Swing程序设计(上)

    Swing是GUI(图形用户界面)开发工具包,内容有很多,这里会分块编写,但在进阶篇中只编写Swing中的基本要素,包括容器.组件和布局等,更深入的内容会在高级篇中出现.想深入学习的朋友们可查阅有关资 ...

  7. Visual Studio调试之断点进阶篇

    Visual Studio调试之断点进阶篇 在上一篇文章Visual Studio调试之断点基础篇里面介绍了什么是断点,INT 是Intel系列CPU的一个指令,可以让程序产生一个中断或者异常.程序中 ...

  8. .NET进阶篇-丑话先说,Flag先立--致青春

    作为开发者,工作了半年,也总觉得技术栈和刚毕业区别不大,用的技术还都是N年前的,每每看到新东西,也只心里哇塞惊叹一下,然后就回归于忙碌.怪自己的技术池太浅,热门的令人称奇的技术也都是在其他巨人的肩膀上 ...

  9. .NET进阶篇-丑话先说,Flag先立

    作为开发者,工作了几年,也总觉得技术栈和刚毕业区别不大,用的技术还都是N年前的,每每看到新东西,也只心里哇塞惊叹一下,然后就回归于忙碌.怪自己的技术池太浅,热门的令人称奇的技术也都是在其他巨人的肩膀上 ...

随机推荐

  1. JS中给正则表达式加变量

    前不久同事询问我js里面怎么给正则中添加变量的问题,遂写篇博客记录下.   一.字面量 其实当我们定义一个字符串,一个数组,一个对象等等的时候,我们习惯用字面量来定义,例如: var s = &quo ...

  2. 巧用 mask-image 实现简单进度加载界面

    最近给 nzoo 折腾官网,拿 angular2.0 + webpack 实现SPA,然后觉得最终打包后的出口文件有点大,用户首次访问会有一个时间较长的白屏等候界面,感觉体验不太好. 于是希望在用户下 ...

  3. Lesson 16 A polite request

    Text If you park your car in the wrong place, a traffic policeman will soon find it. You will be ver ...

  4. stanford corenlp的TokensRegex

    最近做一些音乐类.读物类的自然语言理解,就调研使用了下Stanford corenlp,记录下来. 功能 Stanford Corenlp是一套自然语言分析工具集包括: POS(part of spe ...

  5. iOS 4s-6Plus屏幕自动适配及颜色转换为十六进制

    iOS各种屏幕自动适配及颜色转换为十六进制 ★★★XLJMatchScreen自动适配屏幕★★★ 支持pod导入 pod 'XLJScreenMatching', '~> 1.0.3' 如果发现 ...

  6. scope.$apply是干嘛的

    开始用angular做项目的时候,一定碰到过$scope.$apply()方法,表面上看,这像是一个帮助你进行数据更新的方法,那么,它为何存在,我们又该如何使用它呢. JavaScript执行顺序 J ...

  7. angular2

    1 class两种写法 (1).直接写 class="{{}}"; (2).数组 arr[a,b,c] ng-class = "arr" 2.class和sty ...

  8. JS中的数学计算<之简单实例讲解>

    1.取余数   % var a=10%3; //a=1 2.取绝对值  Math.abs() var a=Math.abs(-102.1); var b=Math.abs(102.1); //a=10 ...

  9. 初始Bootstrap

    使用示例 ①下载Bootstrap框架 网址:http://v3.bootcss.com/getting-started/#download ②解压得到三个文件     ③将文件添加进项目后,在页面中 ...

  10. NodeJs对Mysql封装

    之前在学习NodeJs的时候,每次操作数据库都需要连接数据库然后开始写Sql操作,这样非常麻烦,然后自己对Mysql进行了封装,一共100多行代码. github地址: Mysql操作 我在里面对My ...