多线程cocoa开发iOS进阶

NSThread

2016-04-28  本文已影响4154人  月下独酌灬

NSThread

创建线程三种方式

准备新线程执行的方法

- (void)demo:(id)obj
{
    NSLog(@"传入参数 => %@",obj);
    NSLog(@"hello %@",[NSThread currentThread]);
}

对象方法创建

- (void)threadDemo1
{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
    // 手动启动线程
    [thread start];
}

类方法创建

- (void)threadDemo2
{
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}

NSObject(NSThreadPerformAdditions) 的分类创建

- (void)threadDemo3
{
    [self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}

总结

target和@selector的关系

代码演练

准备Person对象

@interface Person : NSObject

/// 人名
@property (nonatomic,copy) NSString *name;
/// 创建人得构造方法
+ (instancetype)personWithDict:(NSDictionary *)dict;
/// 人有个方法
- (void)personDemo:(id)obj;

@end

@implementation Person

+ (instancetype)personWithName:(NSString *)name
{
    Person *person = [[Person alloc] init];
    person.name = name;
    return person;
}

- (void)personDemo:(id)obj
{
    NSLog(@"创建的人名 => %@",self.name);
    NSLog(@"hello %@",[NSThread currentThread]);
}

@end

控制器中的使用

定义属性

@interface ViewController ()
@property (nonatomic,strong) Person *person;
@end

懒加载Person

@implementation ViewController

- (Person *)person
{
    if (_person==nil) {
        _person = [Person personWithName:@"zhangjie"];
    }
    return _person;
}

新的实例化方法

// 崩溃
[self performSelectorInBackground:@selector(personDemo:) withObject:@"perform"];
// 正确的调用方式
[self.person performSelectorInBackground:@selector(personDemo:) withObject:@"perform"];
// 崩溃
[NSThread detachNewThreadSelector:@selector(personDemo:) toTarget:self withObject:@"detach"];

// 正确的调用方式
[NSThread detachNewThreadSelector:@selector(personDemo:) toTarget:self.person withObject:@"detach"];
// 崩溃
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(personDemo:) object:@"alloc"];
// 手动开启线程
[thread start];
// 正确的调用方式
NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(personDemo:) object:@"alloc"];
// 手动开启线程
[thread start];

线程状态

线程生命周期的控制

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
[thread start];

代码演练

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 新建状态
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
    // 就绪状态 : 将线程放进"可调度线程池",等待被CPU调度.
    [thread start];

    // 主线程中的危险操作,不能在主线程中调用该方法.会使主线程退出
//    [NSThread exit];
}
- (void)threadDemo
{
    for (int i = 0; i < 6; i++) {

        NSLog(@"%d",i);

        //1. 当前线程,每循环一次,就休眠一秒
        [NSThread sleepForTimeInterval:1.0];

        //2. 满足某一条件再次休眠一秒
        if (2==i) {
            NSLog(@"我还想再睡一秒");
            // 休眠时间为从现在开始计时多少秒以后
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
        }

        //3. 满足某一条件线程死亡
        if (4==i) {
            NSLog(@"线程死亡");

            // 在调用exit方法之前一定要注意释放之前由C语言框架创建的对象.
            CGMutablePathRef path = CGPathCreateMutable();
            CGPathRelease(path);

            // 线程死亡
            [NSThread exit];

            // 当线程死亡之后,以后的代码都不会被执行
            NSLog(@"线程已经死亡");
        }
    }
    NSLog(@"循环结束");
}

关于exit的结论

线程属性

属性

代码演示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"主线程栈区空间大小 => %tu",[NSThread currentThread].stackSize/1024);

    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

    // 给线程起名字,可以方便运行调试,定位BUG
    // 在大型的商业软件中,都会设计专门的线程做特定的事情
    thread1.name = @"download A";

    // 线程调用优先级
    // 线程的"优先级"不是决定线程调用顺序的,他是决定线程备CPU调用的频率的
    // 范围在0~1之间,1最高,默认0.5,不建议修改线程优先级
    thread1.threadPriority = 1.0;

    // 线程就绪
    [thread1 start];

    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    thread2.name = @"download B";
    thread2.threadPriority = 0;
    [thread2 start];
}

- (void)demo
{
    NSLog(@"子线程栈区空间大小 => %tu",[NSThread currentThread].stackSize/1024);

    for (int i = 0; i < 10; i++) {
        NSLog(@"%@",[NSThread currentThread]);
    }
}

补充

占位符.png

资源共享-线程安全

多线程操作共享资源的问题

卖票逻辑

卖票系统的简单逻辑.png

开发提示

代码实现卖票逻辑

@interface ViewController ()

/// 总票数(共享的资源)
@property (nonatomic,assign) int tickets;

@end
- (void)viewDidLoad {
    [super viewDidLoad];

    // 设置余票数
    self.tickets = 10;
}
-  (void)saleTickets
{
    // while 循环保证每个窗口都可以单独把所有的票卖完
    while (YES) {

        // 模拟网络延迟
        [NSThread sleepForTimeInterval:1.0];

        // 判断是否有票
        if (self.tickets>0) {
            // 有票就卖
            self.tickets--;
            // 卖完一张票就提示用户余票数
            NSLog(@"剩余票数 => %zd %@",self.tickets,[NSThread currentThread]);
        } else {
            // 没有就提示用户
            NSLog(@"没票了");
            // 此处要结束循环,不然会死循环
            break;
        }
    }
}

单线程

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 在主线程中卖票
    [self saleTickets];
}

多线程

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 在主线程中卖票
    // [self saleTickets];

    // 售票口 A
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    thread1.name = @"售票口 A";
    [thread1 start];

    // 售票口 B
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    thread2.name = @"售票口 B";
    [thread2 start];
}

资源抢夺结果

错误的卖票结果.png

出错原因分析

资源共享问题分析.png
资源共享问题解决.png

解决多线程操作共享资源的问题

添加互斥锁

- (void)saleTickets
{
    // while 循环保证每个窗口都可以单独把所有的票卖完
    while (YES) {
        // // 模拟休眠网络延迟
        [NSThread sleepForTimeInterval:1.0];

        // 添加互斥锁
        @synchronized(self) {
            // 判断是否有票
            if (self.tickets>0) {
                // 有票就卖
                self.tickets--;
                // 卖完一张票就提示用户余票数
                NSLog(@"剩余票数 => %zd",self.tickets);
            } else {
                // 没有就提示用户
                NSLog(@"没票了");
                // 此处要结束循环,不然会死循环
                break;
            }
        }
    }
}

互斥锁小结

原子属性

模拟原子属性

/// 非原子属性
@property (nonatomic,strong) NSObject *obj1;
/// 原子属性:内部有"自旋锁"
@property (atomic,strong) NSObject *obj2;
/// 模拟原子属性
@property (atomic,strong) NSObject *obj3;
// 合成指令
@synthesize obj3 = _obj3;

/// obj3的setter方法
- (void)setObj3:(NSObject *)obj3
{
    @synchronized(self) {
        _obj3 = obj3;
    }
}

/// obj3的getter方法
- (NSObject *)obj3
{
    return _obj3;
}

性能测试

/// 测试"非原子属性","互斥锁","自旋锁"的性能
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSInteger largeNum = 1000*1000;

    NSLog(@"非原子属性");
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj1 = [[NSObject alloc] init];
    }
    NSLog(@"非原子属性 => %f",CFAbsoluteTimeGetCurrent()-start);

    NSLog(@"原子属性");
    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj2 = [[NSObject alloc] init];
    }
    NSLog(@"原子属性 => %f",CFAbsoluteTimeGetCurrent()-start);

    NSLog(@"模拟原子属性");
    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj3 = [[NSObject alloc] init];
    }
    NSLog(@"模拟原子属性 => %f",CFAbsoluteTimeGetCurrent()-start);
}

测试结果

num08.png

互斥锁和自旋锁对比

共同点

不同点

开发建议

异步下载网络图片

需求 : 异步下载网络图片并展示.图片可以滚动,滚动视图要是根视图.

ATS

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

代码实现

定义属性

@interface ViewController ()

/// 滚动视图
@property (nonatomic,strong) UIScrollView *scrollView;
/// 图片视图
@property (nonatomic,weak) UIImageView *imageView;

@end

加载视图层次

- (void)loadView
{
    // 创建滚动视图
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    // 将滚动视图设置成根视图
    self.view = self.scrollView;
    self.scrollView.backgroundColor = [UIColor redColor];

    // 创建图片视图
    UIImageView *imageView = [[UIImageView alloc] init];
    [self.view addSubview:imageView];
    self.imageView = imageView;
}

异步下载网络数据

- (void)viewDidLoad {
    [super viewDidLoad];

    // 下载网络数据
    // [self downloadImageData];

    // 开启新线程异步下载图片
    [self performSelectorInBackground:@selector(downloadImageData) withObject:nil];
}

/// 异步下载网络数据
- (void)downloadImageData
{
    // 图片资源地址
    NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/c995d143ad4bd1130c0ee8e55eafa40f4afb0521.jpg"];
    // 所有的网络数据都是以二进制的形式传输的,所以用NSData来接受
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 设置图片视图
    // [self setupImageViewWithImage:image];

    // 回到主线程更新UI
    // waitUntilDone:是否等待主线程执行结束再执行"下一行代码",一般设置成NO,不用等待
    [self performSelectorOnMainThread:@selector(setupImageViewWithImage:) withObject:image waitUntilDone:NO];

    // 测试 waitUntilDone:
    NSLog(@"下一行代码");
}

设置图片视图

/// 设置图片视图
- (void)setupImageViewWithImage:(UIImage *)imgae
{
    NSLog(@"setupImageView");

    // 设置图片视图
    self.imageView.image = image;
    // 设置图片视图的大小跟图片一般大
    [self.imageView sizeToFit];

    // 设置滚动视图的滚动:滚动范围跟图片一样大
    [self.scrollView setContentSize:image.size];
}

strong和weak补充

num09.png
上一篇下一篇

猜你喜欢

热点阅读