先来一张图认识一下
CFRunLoop是一个结构体:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;//线程
uint32_t _winthread;
CFMutableSetRef _commonModes;//模式
CFMutableSetRef _commonModeItems;//包含了:source0 source1 Observer Timer
CFRunLoopModeRef _currentMode;//当前的模式
CFMutableSetRef _modes;//当前模式里面的对象
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
CFRunLoopMode包含了:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;//有序集合
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;//有序数组
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
在viewDidLoad中打印:
CFRunLoopRef r1 = CFRunLoopGetCurrent();
CFRunLoopMode mod = CFRunLoopCopyCurrentMode(r1);
kCFRunLoopDefaultMode
(
UITrackingRunLoopMode,//界面跟踪,主要用于scrollView的滑动
GSEventReceiveRunLoopMode,//接受系统事件,基本用不到
kCFRunLoopDefaultMode,//默认模式
kCFRunLoopCommonModes//结合了UITrackingRunLoopMode和kCFRunLoopDefaultMode
)
基本认识之后,开始介绍mode里面的source,Observer还有Timer
CFRunLoopSource
先上源码:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {//联合体
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
可以看到结构体里面包含了一个union,这个是c语言里面的联合体
这里稍微讲一下联合体,先定义一个联合体如下:
typedef union {
int a ;
float b;
}UnionType;
UnionType type;
type.a = 3;
NSLog(@"a --- %p",&type.a);
NSLog(@"b --- %p",&type.b);
NSLog(@"%zd",sizeof(UnionType));
2018-12-24 15:47:57.630546+0800 runloop与线程的关系[29756:732129] a --- 0x7ffee48a19d0
2018-12-24 15:47:57.630615+0800 runloop与线程的关系[29756:732129] b --- 0x7ffee48a19d0
2018-12-24 15:47:57.630693+0800 runloop与线程的关系[29756:732129] 4
可以看到a和b的地址是一样的,这个联合体所占的字节是4个字节
创建一个简单的Source0(并没什么卵用),了解下触发回调的时机即可:
/*自定义源只能为source0,而source1是系统的源*/
CFRunLoopSourceContext context = {
0,//版本
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,//回调
cancel,//回调
perform//回调
};
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
//回调:
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__);
}
要想触发schedule,需要添加源:
CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
触发perform,需要标识为待处理
CFRunLoopSourceSignal(source0);
CFRunLoopWakeUp(CFRunLoopGetCurrent());
触发cancel,需要将源移除
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
最后记得销毁源:
CFRelease(source0);
在介绍source1之前,先了解下NSPort:
- NSPort是什么?
通信通道的抽象类 - 能干什么?
我们能够使用端口进行线程间的通信 - 要接受传入消息,必须将NSPort对象添加到NSRunLoop对象作为输入源
- 端口用完之后,如果不用,要释放,不然产生的端口对象可能会逗留并创建内存泄漏.要使使端口对象无效,请调用它的invalidate方法.
- Foundation定义了NSPort的三个具体子类NSMachPoart和NSMessagePoart只允许本地(在同一台机器上)通信,NSSocketPoart支持本地和远程通信,但是对于本地情况,可能要比其他的要昂贵,在使用allocwithZone或poart创建NSPort对象时,将创建一个NSMatchPoart对象.
下面我将写一个demo,尝试主线程向子线程发送消息:
定义两个端口:
//子线程端口
@property (nonatomic,strong)NSPort *subThreadPort;
//主线程端口
@property (nonatomic,strong)NSPort *mainThreadPort;
在viewDidLoad创建主线程,在task中使用NSThread创建子线程:
self.mainThreadPort = [NSPort port];
//如果要监听端口的消息,需要遵循端口的代理:NSPortDelegate
self.mainThreadPort.delegate = self;
//添加到runLoop中作为输入源
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
实现NSPortDelegate代理:
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"%@",[NSThread currentThread]);
}
在点击事件中,主线程向子线程发送消息,需要注意的是发送消息必须要是二进制流,所以需要转一下格式:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//主线程向子线程发送消息
NSMutableArray *components = [NSMutableArray array];
NSData *data = [@"主线程向子线程发送消息" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
现在我们点击一下,控制台会打印:
<NSThread: 0x600002ca21c0>{number = 3, name = 子线程}
说明确实向子线程发送消息成功了,但是是仅仅是打印出来线程,还没有打印出来我们发送的消息,紧接着点击看看NSPortMessage是个什么鬼:
@class NSConnection, NSPortMessage;
仅仅是类的声明,看不到里面的实例变量,但是我们通过macOS的foundation框架中找到NSPortMessage:
@interface NSPortMessage : NSObject {
@private
NSPort *localPort;
NSPort *remotePort;
NSMutableArray *components;
uint32_t msgid;
void *reserved2;
void *reserved;
}
接下来我们可以使用kvc的方式访问私有变量
- (void)handlePortMessage:(id)message{
NSLog(@"%@",[NSThread currentThread]);
NSMutableArray *components = [message valueForKey:@"components"];
//这里仅仅是demo,所以处理起来没那么严谨
if (components.count > 0) {
NSData *data = components[0];
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
}
}
我们收到的消息是在子线程里面的,我们可以再这里给主线程发送一条消息:
- (void)handlePortMessage:(id)message{
NSLog(@"%@",[NSThread currentThread]);
NSMutableArray *components = [message valueForKey:@"components"];
if (components.count > 0) {
NSData *data = components[0];
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
}
sleep(2);
if ([[NSThread currentThread]isMainThread] == NO) {
//子线程向主线程发送消息
NSMutableArray *components = [NSMutableArray array];
NSData *data = [@"子线程向主线程发送消息" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
[self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
}
}
发送成功
Timer:
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
NSLog(@"%s", __func__);
});
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
observer:
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/*
kCFRunLoopEntry = (1UL << 0), 即将进入runloop
kCFRunLoopBeforeTimers = (1UL << 1), 即将处理timer事件
kCFRunLoopBeforeSources = (1UL << 2),即将处理source事件
kCFRunLoopBeforeWaiting = (1UL << 5),即将进入睡眠
kCFRunLoopAfterWaiting = (1UL << 6), 被唤醒
kCFRunLoopExit = (1UL << 7), runloop退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
NSLog(@"%lu", activity);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);