ios RunLoop的相关类

先来一张图认识一下


ios RunLoop的相关类_第1张图片
屏幕快照 2018-12-21 下午5.12.59.png
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 *)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];
}

现在我们点击一下,控制台会打印:

{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];
    }
}
屏幕快照 2018-12-24 下午5.07.26.png

发送成功

Timer:

   CFRunLoopTimerRef timer =  CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
       NSLog(@"%s", __func__);
    });
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
ios RunLoop的相关类_第2张图片
屏幕快照 2018-12-24 下午5.15.21.png

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);

你可能感兴趣的:(ios RunLoop的相关类)