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]);
- 让当前线程挂起的方法
+ (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];
更复杂的操作就需要借助锁了;