runloop的使用
一、RunLoop是什么?
字面意思:运行循环,程序运行过程中循环的处理事情
它的实际:实际是一个对象, 这个对象提供一个入口函数,执行这个入口函数后,程序会进入一个do..while循环,循环的处理一些事情。
二、RunLoop有什么用?
int main(int argc, char * argv[]){
//没有runloop
@autoreleasepool {
NSLog(@“%s”, __func__);
}
return 0;
}
结果:程序执行完就会退出。
int main(int argc, char * argv[]){
//有runloop
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))
}
}
结果:程序一直执行没有退出。
RunLoop基本作用:
- 保持程序的持续运行
- 处理App中的各种事件(触摸、定时器、PerformSelector)
- 节省CPU资源、提高程序性能:该做事的时候做事,该休息的时候休息。
三、RunLoop怎么使用?
iOS提供了2套API来访问和使用RunLoop:
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/
- 线程与RunLoop是一一对应的
- 线程创建的时候,并没有创建RunLoop对象,RunLoop会在第一次获取的时候自动创建。
-
主线程默认开启了RunLoop, 子线程默认没有开启子线程
四、CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserverRef
runloopmodel.png
runloop结构.png
1.CFRunLoopRef
*一个RunLoop对应着一条线程
*一个RunLoop包含多个Mode,每个 Mode 又包含若干个 Source/Timer/Observer
*Source/Timer/Observer又叫mode item。不同mode下的mode item互不影响
*RunLoop运行过程中,只选择一种模式运行
*切换Mode,程序退出当前RunLoop mode,再重新指定Mode执行
2.CFRunLoopSourceRef
- source0:触摸事件,自定义输入源,performSelector:onThread:
- source1:端口(Port)
*计时源:NSTimer,performSelector:withObject:afterDelay
3.线程添加runloop
- (void)viewDidLoad {
[super viewDidLoad];
// CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
// CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
self.stopping = NO;
NSThread* th = [[NSThread alloc] initWithBlock:^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"定时打招呼!! 你好!");
if (self.stopping) {
[NSThread exit];//线程退出,runloop也同时结束
}
}];
// NSLog(@"%s", __func__);
[[NSRunLoop currentRunLoop] run];//线程创建的时候,并没有创建runloop对象,runloop会在第一次获取的时候自动创建
}];
[th start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.stopping = YES;
}
4.查看runloop模式
CFRunLoopRef rl = CFRunLoopGetCurrent();
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(rl);
NSLog(@"mode ---> %@", mode);//输出:kCFRunLoopDefaultMode
CFArrayRef array = CFRunLoopCopyAllModes(rl);
NSLog(@"array ---> %@", array);
/**
输出:
array ---> (
UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode,
kCFRunLoopCommonModes
)
*/
5.自定义输入源
- (void) sourceTest {
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform
};
// 触发schedule
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
// 触发perform
// CFRunLoopSourceSignal(source0);//标记
// CFRunLoopWakeUp(CFRunLoopGetCurrent());//唤醒
// 触发cancel
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
CFRelease(source0);
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
NSLog(@"%s", __func__);
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
NSLog(@"%s", __func__);
}
void perform(void *info) {
NSLog(@"%s", __func__);
}
自定义源.png
6.CFRunLoopTimerRef,定时器
@interface ViewController ()
{
CFRunLoopTimerRef timer;
}
@end
- (void) timerTest {
// 第一种方式
timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
NSLog(@"%s", __func__);
});
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
// 第二种方式
// CFRunLoopTimerContext context = {
// 0,
// (__bridge void *)self,
// CFRetain,
// CFRelease,
// NULL
// };
// timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, callBack, &context);
// CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
}
void callBack(CFRunLoopTimerRef timer, void *info){
NSLog(@"✈️✈️✈️✈️✈️✈️");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 销毁定时器
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
CFRelease(timer);
}
7.CFRunLoopObserverRef,观察者
- (void) observerTest {
// 第一种方式
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"%lu", activity);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 第二种方式
// // 定义观察者
// static CFRunLoopObserverRef runloopObserver;
// // 创建观察者
// CFRunLoopObserverContext context = {
// 0,
// (__bridge void *) self,
// &CFRetain,
// &CFRelease,
// NULL
// };
// //NULL此处相当于kCFAllocatorDefault
// runloopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeTimers, YES, 0, &callBack1, &context);
// // 添加观察者
// CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopCommonModes);
// 移除并释放
// CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// CFRelease(observer);
}
void callBack1(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
}
observer.png
五、NSPort
1.NSPort是通信通道的抽象类。
2.能干什么?:我们能够使用端口进行线程间的一个通信。
3.要接收传入消息,必须将NSPort对象添加到NSRunLoop对象中作为输入源
4.端口用完之后,如果不用, 要释放, 不然产生的端口对象可能会逗留并创建内 存泄漏。要使端口对象无效,请调用它的invalidate方法。
5.Foundation定义了NSPort的三个具体子类。NSMachPort和NSMessagePort只允许本地(在同一台机器上)通信。NSSocketPort支持本地和远程通信,但是对于本地情 况,可能比其他的要昂贵。在使用allocWithZone:或port创建NSPort对象时,将创建 一个NSMachPort对象。
6.使用allocWithZone:活着port创建NSPort对象那, 实际上是创建一个NSMachPort对象
@interface ViewController () <NSPortDelegate>
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];//主线程上
[self task];
}
- (void) task {
NSThread* thread = [[NSThread alloc] initWithBlock:^{
self.subThreadPort = [NSPort port];
self.subThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];//子线程上
[[NSRunLoop currentRunLoop] run];//子线程要开启runloop
}];
[thread setName:@"子线程"];
[thread start];
}
//- (void)handlePortMessage:(NSPortMessage *)message
- (void)handlePortMessage:(id)message {
NSLog(@"%@", [NSThread currentThread]);
//KVC 取值,在Macos的Foundation中的NSPortMessage.h中查看,components为私有的,所以要用kvc取值
NSMutableArray* components = [message valueForKey:@"components"];
if ([components count] > 0) {
NSData* data = [components objectAtIndex:0];
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
}
sleep(2);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray* sendComponents = [NSMutableArray array];
NSData* data = [@"world" dataUsingEncoding:NSUTF8StringEncoding];
[sendComponents addObject:data];
[self.mainThreadPort sendBeforeDate:[NSDate date] components:sendComponents from:self.subThreadPort reserved:0];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
/**
第一个参数:发送时间
第二个参数:发送的数据
第三个参数:从那个端口发送,此处从主线程端口self.mainThreadPort向子线程端口发送
第四个参数:保留位
*/
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
// soure0标示为待处理,要唤醒
}
六、runloop执行过程
执行过程.png
内部实现.png
休眠原理
RunLoop实现休眠的原理, 真正的原因是:
1.调用了内核的API(mach_msg), 进入内核态,由内核来将线程置于休眠
2.有消息,就唤醒线程,回到用户态,来处理消息.
休眠原理.png