NSThread

原创
2015/12/25 21:57
阅读数 60

创建线程的方式

  • 准备在后台线程调用的方法 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

代码演练

@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;
}

三种线程调度方法

NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(longOperation:) object:@"THREAD"];

[thread start];
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];

代码小结

线程状态

代码演练

- (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];
}

代码小结

阻塞

死亡

[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 - 线程优先级

3. stackSize - 栈区大小

[NSThread currentThread].stackSize = 1024 * 1024;

4. isMainThread - 是否主线程

资源共享-卖票

多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:

  1. 首先确保单个线程执行正确

  2. 添加线程

卖票逻辑

- (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;
   }
}

互斥锁小结

  1. 保证锁内的代码,同一时间,只有一条线程能够执行!

  2. 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!

  3. 速记技巧 [[NSUserDefaults standardUserDefaults] synchronize];

互斥锁参数

  1. 能够加锁的任意 NSObject 对象

  2. 注意:锁对象一定要保证所有的线程都能够访问

  3. 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象

原子属性

代码演练

@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);
}

原子属性内部的锁是自旋锁自旋锁的执行效率比互斥锁高

自旋锁 & 互斥锁

线程安全

约定:所有更新 UI 的操作都必须主线程上执行!

iOS 开发建议

  1. 所有属性都声明为 nonatomic

  2. 尽量避免多线程抢夺同一块资源

  3. 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

线程间通讯

主线程实现

定义属性

/// 根视图是滚动视图@property (nonatomic, strong) UIScrollView *scrollView;/// 图像视图@property (nonatomic, weak) UIImageView *imageView;/// 网络下载的图像@property (nonatomic, weak) UIImage *image;

loadView

  1. 加载视图层次结构

  2. 用纯代码开发应用程序时使用

  3. 功能和 Storyboard & XIB 是等价的

如果重写了 loadViewStoryboard & 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

  1. 视图加载完成后执行

  2. 可以做一些数据初始化的工作

  3. 如果用纯代码开发,不要在此方法中设置界面 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]


展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部