NSThread线程对象
2015-08-16 00:15
519 查看
NSThread
创建线程的方式
准备在后台线程调用的方法longOperation:
- (void)longOperation:(id)obj { NSLog(@"%@ - %@", [NSThread currentThread], obj); }
方式1:alloc / init - start
- (void)threadDemo1 { NSLog(@"before %@", [NSThread currentThread]); NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"THREAD"]; [thread start]; NSLog(@"after %@", [NSThread currentThread]); }
代码小结
[thread start];执行后,会在另外一个线程执行
longOperation:方法
在 OC 中,任何一个方法的代码都是从上向下顺序执行的
同一个方法内的代码,都是在相同线程执行的(
block除外)
方式2:detachNewThreadSelector
- (void)threadDemo2 { NSLog(@"before %@", [NSThread currentThread]); [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"]; NSLog(@"after %@", [NSThread currentThread]); }
代码小结
detachNewThreadSelector类方法不需要启动,会自动创建线程并执行
@selector方法
方式3:分类方法
- (void)threadDemo3 { NSLog(@"before %@", [NSThread currentThread]); [self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"]; NSLog(@"after %@", [NSThread currentThread]); }
代码小结
performSelectorInBackground是
NSObject的分类方法
会自动在后台线程执行
@selector方法
没有
thread字眼,
隐式创建并启动线程
所有
NSObject都可以使用此方法,在其他线程执行方法
NSThread 的 Target
NSThread的实例化方法中的
target指的是开启线程后,在线程中执行
哪一个对象的
@selector方法
代码演练
准备对象@interface Person : NSObject @property (nonatomic, copy) NSString *name; @end @implementation Person + (instancetype)personWithDict:(NSDictionary *)dict { id obj = [[self alloc] init]; [obj setValuesForKeysWithDictionary:dict]; return obj; } - (void)longOperation:(id)obj { NSLog(@"%@ - %@ - %@", [NSThread currentThread], self.name, obj); } @end
定义属性
@property (nonatomic, strong) Person *person;
懒加载
- (Person *)person { if (_person == nil) { _person = [Person personWithDict:@{@"name": @"zhangsan"}]; } return _person; }
三种线程调度方法
alloc / initNSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(longOperation:) object:@"THREAD"]; [thread start];
detach
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
分类方法
[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
代码小结
通过指定不同的target会在后台线程执行该对象的
@selector方法
提示:不要看见
target就写
self
performSelectorInBackground可以让方便地在后台线程执行任意
NSObject对象的方法
线程状态
线程状态
新建
实例化线程对象
就绪
向线程对象发送
start消息,线程对象被加入
可调度线程池等待 CPU 调度
detach方法和
performSelectorInBackground方法会直接实例化一个线程对象并加入
可调度线程池
运行
CPU 负责调度
可调度线程池中线程的执行
线程执行完成之前,状态可能会在
就绪和
运行之间来回切换
就绪和
运行之间的状态变化由 CPU 负责,程序员不能干预
阻塞
当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
sleepForTimeInterval:休眠指定时长
sleepUntilDate:休眠到指定日期
@synchronized(self):乎斥锁
死亡
正常死亡
线程执行完毕
非正常死亡
当满足某个条件后,在线程内部中止执行
当满足某个条件后,在主线程中止线程对象
代码演练
- (void)statusDemo { NSLog(@"先睡会"); [NSThread sleepForTimeInterval:1.0]; for (int i = 0; i < 20; i++) { if (i == 9) { NSLog(@"再睡会"); [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; } NSLog(@"%d %@", i, [NSThread currentThread]); if (i == 16) { NSLog(@"88"); // 终止线程之前,需要记住释放资源 [NSThread exit]; } } NSLog(@"over"); }
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 注意不要在主线程上调用 exit 方法 // [NSThread exit]; // 实例化线程对象(新建) NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil]; // 线程就绪(被添加到可调度线程池中) [t start]; }
代码小结
阻塞
方法执行过程,符合某一条件时,可以利用sleep方法让线程进入
阻塞状态
sleepForTimeInterval从现在起睡多少
秒
sleepUntilDate从现在起睡到指定的日期
死亡
[NSThread exit];
一旦强行终止线程,后续的所有代码都不会被执行
注意:在终止线程之前,应该注意释放之前分配的对象!
注意:线程从
就绪和
运行状态之间的切换是由
CPU负责的,程序员无法干预
线程属性
代码演练
// MARK: - 线程属性 - (void)threadProperty { NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 1. 线程名称 t1.name = @"Thread AAA"; // 2. 优先级 t1.threadPriority = 0; [t1 start]; NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 1. 线程名称 t2.name = @"Thread BBB"; // 2. 优先级 t2.threadPriority = 1; [t2 start]; } - (void)demo { for (int i = 0; i < 10; ++i) { // 堆栈大小 NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024); } // 模拟崩溃 // 判断是否是主线程 // if (![NSThread currentThread].isMainThread) { // NSMutableArray *a = [NSMutableArray array]; // // [a addObject:nil]; // } }
属性
1. name
- 线程名称
在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程2. threadPriority
- 线程优先级
优先级,是一个浮点数,取值范围从 0~1.0
1.0表示优先级最高
0.0表示优先级最低
默认优先级是
0.5
优先级高只是保证 CPU 调度的可能性会高
刀哥个人建议,在开发的时候,不要修改优先级
多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
多线程开发的原则:简单
3. stackSize
- 栈区大小
默认情况下,无论是主线程还是子线程,栈区大小都是 512K
栈区大小可以设置
[NSThread currentThread].stackSize = 1024 * 1024;
4. isMainThread
- 是否主线程
资源共享
资源共享-卖票
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:首先确保单个线程执行正确
添加线程
卖票逻辑
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { self.tickets = 20; [self saleTickets]; } /// 卖票逻辑 - 每一个售票逻辑(窗口)应该把所有的票卖完 - (void)saleTickets { while (YES) { if (self.tickets > 0) { self.tickets--; NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]); } else { NSLog(@"没票了 %@", [NSThread currentThread]); break; } } }
添加线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { self.tickets = 20; NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; t1.name = @"售票员 A"; [t1 start]; NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; t2.name = @"售票员 B"; [t2 start]; }
添加休眠
- (void)saleTickets { while (YES) { // 模拟休眠 [NSThread sleepForTimeInterval:1.0]; if (self.tickets > 0) { self.tickets--; NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]); } else { NSLog(@"没票了 %@", [NSThread currentThread]); break; } } }
运行测试结果
互斥锁
添加互斥锁
- (void)saleTickets { while (YES) { [NSThread sleepForTimeInterval:1.0]; @synchronized(self) { if (self.tickets > 0) { self.tickets--; NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]); continue; } } NSLog(@"没票了 %@", [NSThread currentThread]); break; } }
互斥锁小结
保证锁内的代码,同一时间,只有一条线程能够执行!互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
速记技巧
[[NSUserDefaults standardUserDefaults] synchronize];
互斥锁参数
能够加锁的任意NSObject对象
注意:锁对象一定要保证所有的线程都能够访问
如果代码中只有一个地方需要加锁,大多都使用
self,这样可以避免单独再创建一个锁对象
原子属性
原子属性(线程安全),是针对多线程设计的,是默认属性多个线程在写入原子属性时(调用
setter方法),能够保证同一时间只有一个线程执行写入操作
原子属性是一种
单(线程)写多(线程)读的多线程技术
原子属性的效率比互斥锁高,不过可能会出现
脏数据
在定义属性时,必须显示地指定
nonatomic
代码演练
定义属性@property (nonatomic, strong) NSObject *obj1; @property (atomic, strong) NSObject *obj2; @property (nonatomic, strong) NSObject *obj3;
模拟原子属性
@synthesize obj3 = _obj3; - (void)setObj3:(NSObject *)obj3 { @synchronized(self) { _obj3 = obj3; } } - (NSObject *)obj3 { return _obj3; } * 性能测试 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { int largeNumber = 1000 * 10000; NSLog(@"非原子属性"); CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < largeNumber; i++) { self.obj1 = [[NSObject alloc] init]; } NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start); NSLog(@"原子属性"); start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < largeNumber; i++) { self.obj2 = [[NSObject alloc] init]; } NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start); NSLog(@"模拟原子属性"); start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < largeNumber; i++) { self.obj3 = [[NSObject alloc] init]; } NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start); }
原子属性内部的锁是
自旋锁,自旋锁的执行效率比互斥锁高
自旋锁 & 互斥锁
共同点都能够保证同一时间,只有一条线程执行锁定范围的代码
不同点
互斥锁:如果发现有其他线程正在执行锁定的代码,线程会
进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被
唤醒
自旋锁:如果发现有其他线程正在执行锁定的代码,线程会以
死循环的方式,一直等待锁定代码执行完成
结论
自旋锁更适合执行非常短的代码
无论什么锁,都是要付出代价
线程安全
多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全要实现线程安全,必须要用到
锁
为了得到更佳的用户体验,
UIKit 不是线程安全的
约定:所有更新 UI 的操作都必须主线程上执行!
因此,
主线程又被称为
UI 线程
iOS 开发建议
所有属性都声明为nonatomic
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
线程间通讯
主线程实现
定义属性
/// 根视图是滚动视图 @property (nonatomic, strong) UIScrollView *scrollView; /// 图像视图 @property (nonatomic, weak) UIImageView *imageView; /// 网络下载的图像 @property (nonatomic, weak) UIImage *image;
loadView
加载视图层次结构用纯代码开发应用程序时使用
功能和
Storyboard&
XIB是等价的
如果重写了
loadView,
Storyboard&
XIB都无效
- (void)loadView { _scrollView = [[UIScrollView alloc] init]; _scrollView.backgroundColor = [UIColor orangeColor]; self.view = _scrollView; UIImageView *iv = [[UIImageView alloc] init]; [self.view addSubview:iv]; _imageView = iv; }
viewDidLoad
视图加载完成后执行可以做一些数据初始化的工作
如果用纯代码开发,不要在此方法中设置界面 UI
- (void)viewDidLoad { [super viewDidLoad]; // 下载图像 [self downloadImage]; }
下载网络图片
- (void)downloadImage { // 1. 网络图片资源路径 NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"]; // 2. 从网络资源路径实例化二进制数据(网络访问) NSData *data = [NSData dataWithContentsOfURL:url]; // 3. 将二进制数据转换成图像 UIImage *image = [UIImage imageWithData:data]; // 4. 设置图像 self.image = image; }
设置图片
- (void)setImage:(UIImage *)image { // 1. 设置图像视图的图像 self.imageView.image = image; // 2. 按照图像大小设置图像视图的大小 [self.imageView sizeToFit]; // 3. 设置滚动视图的 contentSize self.scrollView.contentSize = image.size; }
设置滚动视图的缩放
1> 设置滚动视图缩放属性// 1> 最小缩放比例 self.scrollView.minimumZoomScale = 0.5; // 2> 最大缩放比例 self.scrollView.maximumZoomScale = 2.0; // 3> 设置代理 self.scrollView.delegate = self;
2> 实现代理方法 - 告诉滚动视图缩放哪一个视图
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return self.imageView; }
3> 跟踪
scrollView缩放效果
- (void)scrollViewDidZoom:(UIScrollView *)scrollView { NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform)); }
线程间通讯
在后台线程下载图像[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
在主线程设置图像
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
相关文章推荐
- Codeforces Gym 100203G G - Good elements 标记暴力
- JAVA编程——Throwable常见异常及源码详解
- HDU 2222 Keywords Search(AC自动机模板题)
- windows常用快捷运行命令
- windows10编译Box2D源文件
- 利于SEO的PDF展示方案集锦,pdf to html 每个插件都是楼主亲测
- 两道阿里前端面试中的逻辑题
- 2015华为软件精英挑战赛德州扑克之手牌处理
- 目标文件中变量的位置以及默认值
- ERROR 1010 (HY000): Error dropping database (can't rmdir '.\kehuanedu_db', errno: 41)
- Android中startService的使用及Service生命周期
- 以核心图表解析游戏核心机制设计原理
- poi Build path 配置,顺序问题
- 海量数据处理 - 10亿个数中找出最大的10000个数(top K问题)
- Java之面向对象详细总结
- 8.16 lru缓存java版
- 海量数据处理 - 10亿个数中找出最大的10000个数(top K问题)
- HDU 1014 Uniform Generator
- 我的YUV播放器MFC小笔记:右键菜单事件和非标题实现鼠标拖动
- Objective-C Runtime (二)