您的位置:首页 > 其它

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 / init

NSThread *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];
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: