• NSThread线程对象


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

    代码小结

    • performSelectorInBackgroundNSObject 的分类方法
    • 会自动在后台线程执行 @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 - 是否主线程

    资源共享

    资源共享-卖票

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

    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,这样可以避免单独再创建一个锁对象

    原子属性

    • 原子属性(线程安全),是针对多线程设计的,是默认属性
    • 多个线程在写入原子属性时(调用 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 开发建议

    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];
  • 相关阅读:
    优秀大数据GitHub项目一览
    自定义组件-BreadcrumbTreeView 的使用
    IOS中的属性列表----Property List
    即时通讯之smack客户端配置
    Android studio 使用问题汇总
    触摸事件UITouch的应用
    Android界面设计之对话框——定制Toast、AlertDialog
    android_orm框架之greenDAO(一)
    火速提升Android仿真器的运行速度 ——仿真器Genymotion
    Android 中的缓存机制与实现
  • 原文地址:https://www.cnblogs.com/jiahao89/p/5118274.html
Copyright © 2020-2023  润新知