美文网首页程序员
ios RunLoop的相关类

ios RunLoop的相关类

作者: wp_Demo | 来源:发表于2018-12-24 17:41 被阅读0次

    先来一张图认识一下


    屏幕快照 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<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];
        }
    }
    
    屏幕快照 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);
    
    屏幕快照 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的相关类

        本文链接:https://www.haomeiwen.com/subject/jglvkqtx.html