在平时开发中可能会有这样的需求,就是频繁的在子线程中执行任务,比如下载很多小文件,图片等,如果每次执行任务都开启新的线程会导致频繁的创建线程和销毁线程,这些开销是比较大的,所以最好的方法就是创建一些线程,然后自己控制这些线程的生命周期,保持线程一直处于可用状态。
下面就做一个简单的封装,方便以后使用。
YHPermenantThread.h
#import <Foundation/Foundation.h>
typedef void (^YHPermenantThreadTask)(void);
@interface YHPermenantThread : NSObject
/**
在当前子线程执行一个任务
*/
- (void)executeTask:(YHPermenantThreadTask)task;
/**
结束线程
*/
- (void)stop;
@end
YHPermenantThread.m
#import "YHPermenantThread.h"
/** YHThread **/
@interface YHThread : NSThread
@end
@implementation YHThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** YHPermenantThread **/
@interface YHPermenantThread()
@property (strong, nonatomic) YHThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation YHPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[YHThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(YHPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(YHPermenantThreadTask)task
{
task();
}
@end
上面代码有几个需要注意的点:
-
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
,最后一个参数需要为YES,就是等待线程里边内容执行完毕才执行这行代码下面代码。如果为NO,子线程和主线程同时执行,在子线程执行的过程中self
已被释放,子线程属于self
,线程已经不存在了,所以会引起崩溃问题。 - 这里使用
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
,不可使用[[NSRunLoop currentRunLoop] run];
。因为从源码可以看到- (void)run;
方法内部调用的就是- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
,而且是循环调用该方法,所以即便是外部停止了一次,内部又调用了- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
。所以这里需要自己使用while做判断。
上面代码看着太复杂,接下来精简一下。
这里不在使用OC语法,而是使用C语法,因为偏向底层,可控制的地方就比较多,所以像循环run
这个方法就可以精简掉了。
YHPermenantThread.m修改以后如下:
#import "YHPermenantThread.h"
/** YHThread **/
@interface YHThread : NSThread
@end
@implementation YHThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** YHPermenantThread **/
@interface YHPermenantThread()
@property (strong, nonatomic) YHThread *innerThread;
@end
@implementation YHPermenantThread
#pragma mark - public methods
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.innerThread = [[YHThread alloc] initWithBlock:^{
// 创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 销毁source
CFRelease(source);
// 启动
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(YHPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(YHPermenantThreadTask)task
{
task();
}
@end
上面代码有几个需要注意的点:
-
CFRunLoopSourceContext context = {0};
结构体需要初始化,如果简单的这样CFRunLoopSourceContext context;
会导致context含有脏数据。 -
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
最后一个参数设置为true
,代表执行完source
后就会退出当前loop
。设置为false
表示执行完source
不会退出当前loop
。 -
1.0e10
表示非常大的数字,参考的苹果源码。
使用:
self.thread = [[YHPermenantThread alloc] init];
[self.thread executeTask:^{
NSLog(@"执行任务 - %@", [NSThread currentThread]);
}];
如果想手动释放就调用stop
,如果不想手动释放,self.thread
会在失去引用的时候自动释放。
对于以上代码如果继续封装可以做成线程池。