iOS多线程之performSelector&NSThread

867 阅读5分钟

performSelector开头的方法有很多,我们简单梳理一下

NSObject.h

@protocol NSObject
......
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
......
@end

这时的performSelector是同步调用,可以在主线程也可以在其他线程调用,但是和直接调用不同的是它会在运行时去找方法的实现,编译时不校验方法是否实现,一般和respondsToSelector配合使用,我们研究线程相关内容不涉及到它们

NSRunLoop.h

@interface NSObject (NSDelayedPerforming)
- (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;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end

这是两个异步调用函数,即使afterDelay设置为0仍然是异步调用,其实现逻辑是在RunLoop中开启一个计时器。这里有两个cancle方法,可以取消任务执行

    [self performSelector:@selector(fun) withObject:nil afterDelay:3];
    [self performSelector:@selector(fun1) withObject:nil afterDelay:2];
    
- (void)fun{
    NSLog(@"我是fun");
}

- (void)fun1{
    [NSRunLoop cancelPreviousPerformRequestsWithTarget:self selector:@selector(fun) object:nil];;
}

延迟三秒之后执行方法fun,但是2秒之后一个贱贱的方法取消了你的执行,于是fun就没有执行,为了防止方法泄漏,最好在dealloc方法中主动调用cancne方法,防止内存泄漏

注意因为自线程的runloop默认是不开启的,所以在子线程执行的时候要手动开启RunLoop,例如这样是不执行的

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(fun) withObject:nil afterDelay:1];
    });
    
    - (void)fun{
    NSLog(@"我是fun");
    }

修改一番

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(fun) withObject:nil afterDelay:1];
        [[NSRunLoop currentRunLoop] run];
    });
    
    - (void)fun{
        NSLog(@"我是fun");
    }

这样就可以正常执行fun

NSThread.h

@interface NSObject (NSThreadPerformAdditions)
- (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 API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes


- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

这几个方法我们需要重点关注一下。

前两个方法是在主线程执行,waitUntilDone设置为YES会阻塞当前线程,主线程执行完selector之后当前线程继续执行,设置为NO不会阻塞当前线程。modes设置RunLoop的mode,例如如果我们设置kCFRunLoopDefaultMode那么当列表滚动时就不会执行selector,如果我们设置kCFRunLoopCommonModes就可以正常执行。没有modes参数的方法默认设置为kCFRunLoopCommonModes

三四个方法和前两个类似,只不过是指定线程来执行,如果指定主线程,那么就和前两个方法效果一样。

第五个方法开启子线程在后台运行,隐式创建并启动线程

和GCD相比NSThread需要我们来管理线程的生命周期,所以我们更多的会选择使用更简单的GCD,但是NSThread的一些方法我们还是需要经常用到的,比如:

  • [NSThread currentThread]获取当前线程

  • [NSThread isMainThread]判断当前线程是否是主线程 我们看一下NSThread.h还有那些我们熟悉的方法

  • 用来获取当前线程的属性currentThread

@property (class, readonly, strong) NSThread *currentThread;
  • 创建线程并置为就绪状态(runable)
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
  • 是否多线程。如果是通过非Cocoa的api开启的线程,比如POSIX或者Multiprocessing Services APIs,被认为不是多线程。
+ (BOOL)isMultiThreaded;
  • 这是一个只读的可变字典,我们可以获取字典之后用来存储一下我们感兴趣的值
@property (readonly, retain) NSMutableDictionary *threadDictionary;

比如

    NSThread *thread = [[NSThread alloc]initWithBlock:^{}];
    NSMutableDictionary *dic = [thread threadDictionary];
    [dic setValue:@"aKey" forKey:@"aValue"];
    NSLog(@"threadDictionary == %@",[thread threadDictionary]);

image.png

  • 让当前线程挂起的方法
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 取消当前线程
+ (void)exit;
  • 操作线程权限
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;

@property double threadPriority API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)); // To be deprecated; use qualityOfService below
  • 获取/设置线程名字
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • 创建线程是有开销的(iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间)
@property NSUInteger stackSize API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • 判断线程是否是主线程(实例方法,判断当前线程实例是否是主线程)
@property (readonly) BOOL isMainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • 判断当前线程是否是主线程(类方法,判断当前线程是否是主线程)应用比较多
@property (class, readonly) BOOL isMainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); // reports whether current thread is main
  • 获取主线程
@property (class, readonly, strong) NSThread *mainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • 线程创建方法,创建成功不会自动运行,需要手动调用start方法
- (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
  • 判断线程状态
@property (readonly, getter=isExecuting) BOOL executing API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@property (readonly, getter=isFinished) BOOL finished API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@property (readonly, getter=isCancelled) BOOL cancelled API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
  • 修改线程状态
- (void)cancel API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)start API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)main API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); // thread body method

NSThread线程间通讯

场景:创建一个子线程执行耗时操作,执行结束以后回调主线程刷新页面

    NSThread *thread = [[NSThread alloc]initWithBlock:^{
        sleep(2);
        [self performSelectorOnMainThread:@selector(fun) withObject:nil waitUntilDone:YES];
    }];
    [thread start];

更复杂的操作就需要借助锁了;

参考文章

performSelector最全讲解

iOS performSelector(参考)

iOS 多线程:『pthread、NSThread』详尽总结

NSThread详解