NSThread

189 阅读4分钟

NSThread 是苹果官方提供的,可以直接操作线程对象。不过也需要需要程序员自己管理线程的生命周期(主要是创建),我们在开发的过程中偶尔使用 NSThread。比如我们会经常调用[NSThread currentThread]来显示当前的进程信息。

NSThread的创建

NSThread 有多种创建方式。

方式一 :创建线程并手动使用 start 启动线程

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
- (instancetype)initWithBlock:(void (^)(void))block

上述两个方法是创建线程的方法,线程创建好以后,需要我们手动调用 start 方法来启动线程。

// 1. 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2. 启动线程
[thread start];    // 线程一启动,就会在线程thread中执行self的run方法
- (void)run {
    ...
}

方式二 :创建线程且自动启动线程

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
+ (void)detachNewThreadWithBlock:(void (^)(void))block

利用这两种方法创建的线程,不需要手动启动线程,会自动启动线程调用run方法。

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 新线程自动调用方法,里边为需要执行的任务
- (void)run {
     NSLog(@"%@", [NSThread currentThread]);
}

方式三 :隐式创建线程(NSObject 的方法)

// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
	// equivalent to the first method with kCFRunLoopCommonModes

//在指定线程执行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
	// equivalent to the first method with kCFRunLoopCommonModes
        
//在后台线程(一条新的线程)执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg

// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

//延迟执行,NSRunLoop 相关,如果是子线程执行,需要开启runloop
//将延迟指定为0并不一定会使选择器立即执行。选择器仍在线程的运行循环中排队,并尽快执行。
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;//未指定mode,默认为NSDefaultRunLoopMode

NSThread的一些用法

// 获得主线程
+ (NSThread *)mainThread;    

// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;

// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;    

// 获得当前线程
NSThread *currentThread = [NSThread currentThread];

// 给线程设置名称——setter方法
- (void)setName:(NSString *)n;    

// 获取线程的名称
- (NSString *)name;    

线程的状态

线程的状态有新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)状态。

当线程被创建以后会在内存中开辟一块内存存放这个线程对象,当调用了[thread start] 后这个线程会被标记为可调度的线程对象,存放在可调度线程池中,如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态。 如果CPU在运行当前线程对象的时候调用了sleep方法\等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时\得到同步锁,则回到就绪状态。 如果CPU在运行当前线程对象的时候线程任务执行完毕\异常强制退出,则当前线程对象进入死亡状态。

1609ff53d954eb6.png

NSThread的状态

//使线程启动
- (void)start;
//使线程退出执行--》死亡
+ (void)exit;
//使线程阻塞
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

线程间通信

假如我需要下载一张大图图片,然后在主线程更新

- (void)downloadImage {
    NSLog(@"current thread -- %@", [NSThread currentThread]);
    // 1. 获取图片 imageUrl
    NSURL *imageUrl = [NSURL URLWithString:@"https://aaa.jpg"];
    
    // 2. 从 imageUrl 中读取数据(下载图片) -- 耗时操作
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    // 通过二进制 data 创建 image
    UIImage *image = [UIImage imageWithData:imageData];
    
    // 3. 回到主线程进行图片赋值和界面刷新
    [self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:YES]; //waitUntilDone参数的设置为YES表示需要阻塞线程等待耗时操作执行完成。
}

/**
 * 回到主线程进行图片赋值和界面刷新
 */
- (void)refreshOnMainThread:(UIImage *)image {
    NSLog(@"current thread -- %@", [NSThread currentThread]);
    
    // 赋值图片到imageview
    self.imageView.image = image;
}

线程安全

为了保证线程安全,需要对线程加锁,iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、dispatch_semaphore等。

依然是买票问题:

/**
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSave {
    // 1. 设置剩余火车票为 50
    self.ticketSurplusCount = 50;
    
    // 2. 设置北京火车票售卖窗口的线程
    self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
    self.ticketSaleWindow1.name = @"北京火车票售票窗口";
    
    // 3. 设置上海火车票售卖窗口的线程
    self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
    self.ticketSaleWindow2.name = @"上海火车票售票窗口";
    
    // 4. 开始售卖火车票
    [self.ticketSaleWindow1 start];
    [self.ticketSaleWindow2 start];
    
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 互斥锁
        @synchronized (self) {
            //如果还有票,继续售卖
            if (self.ticketSurplusCount > 0) {
                self.ticketSurplusCount --;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
                [NSThread sleepForTimeInterval:0.2];
            }
            //如果已卖完,关闭售票窗口
            else {
                NSLog(@"所有火车票均已售完");
                break;
            }
        }
    }
}