美文网首页
YYTransaction源码分析

YYTransaction源码分析

作者: 繁星mind | 来源:发表于2018-07-24 20:25 被阅读44次

    YYTransaction源码分析

    YYTransaction绘制任务的机制是仿照CoreAnimation的绘制机制,监听主线程RunLoop,在空闲阶段插入绘制任务,并将任务优先级设置在CoreAnimation绘制完成之后,然后遍历绘制任务集合进行绘制工作并且清空集合,具体可以看源码。

    /**
     YYTransaction let you perform a selector once before current runloop sleep.
     */
    @interface YYTransaction : NSObject
    
    /**
     Creates and returns a transaction with a specified target and selector.
     
     @param target    A specified target, the target is retained until runloop end.
     @param selector  A selector for target.
     
     @return A new transaction, or nil if an error occurs.
     */
    + (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector;
    
    /**
     Commit the trancaction to main runloop.
     
     @discussion It will perform the selector on the target once before main runloop's
     current loop sleep. If the same transaction (same target and same selector) has 
     already commit to runloop in this loop, this method do nothing.
     */
    - (void)commit;
    
    @end
    

    上面的YYTransaction.h文件中有两个方法:
    第一个方法是+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector,根据传入的target和selector来创建一个任务。
    第二个方法是- (void)commit;,它用来在runloop睡眠的时候,执行传入任务,并且对于相同的任务,在runloop中只执行一次。

    看到这里你应该有两个疑问,
    第一是如何来实现在runLoop将要休眠的时候,来执行传进来的任务???
    第二是如何保证相同的任务只执行一次???

    what?why?.jpg

    那么下面我们来看下源码,分析上面的两个问题👆

    // 注册 Runloop Observer
    static void YYTransactionSetup() {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            transactionSet = [NSMutableSet new];
            CFRunLoopRef runloop = CFRunLoopGetMain();
            CFRunLoopObserverRef observer;
    /**
             创建一个RunLoop的观察者
             allocator:该参数为对象内存分配器,一般使用默认的分配器kCFAllocatorDefault。或者nil
             activities:该参数配置观察者监听Run Loop的哪种运行状态,这里我们监听beforeWaiting和exit状态
             repeats:CFRunLoopObserver是否循环调用。
             order:CFRunLoopObserver的优先级,当在Runloop同一运行阶段中有多个CFRunLoopObserver时,根据这个来先后调用CFRunLoopObserver,0为最高优先级别。正常情况下使用0。
             callout:观察者的回调函数,在Core Foundation框架中用CFRunLoopObserverCallBack重定义了回调函数的闭包。
             context:观察者的上下文。 (类似与KVO传递的context,可以传递信息,)因为这个函数创建ovserver的时候需要传递进一个函数指针,而这个函数指针可能用在n多个oberver 可以当做区分是哪个observer的状机态。(下面的通过block创建的observer一般是一对一的,一般也不需要Context,),还有一个例子类似与NSNOtificationCenter的 SEL和 Block方式
             */
            observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                               kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                               true,      // repeat
                                               0xFFFFFF,  // after CATransaction(2000000)
                                               YYRunLoopObserverCallBack, NULL);
            CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
            CFRelease(observer);
        });
    }
    
    //监听回调的方法
    static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
        if (transactionSet.count == 0) return;
        NSSet *currentSet = transactionSet;
        transactionSet = [NSMutableSet new];
        [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [transaction.target performSelector:transaction.selector];
    #pragma clang diagnostic pop
        }];
    }
    
    

    上面的代码就是runLoop将要休眠的时候,执行任务的核心代码
    首先我们来看一下YYTransactionSetup方法中CFRunLoopObserverCreate函数的参数选取:
    1. 任务执行的时机
    从上面源码可以看出是在 kCFRunLoopBeforeWaiting | kCFRunLoopExit,也就是在将要睡眠和推出的时候来执行。
    2.执行的优先级
    从上面源码可以看出是0xFFFFFF, // after CATransaction(2000000),这是在CoreAnimation绘制完成之后之后执行。
    3.执行的runLoop
    从上面源码可以看出是 CFRunLoopGetMain();也就是主线程的runLoop中执行这个回调,这是因为执行的任务跟UI相关,必须要在主线程执行。

    然后我们看下回调方法,是通过执行 transactionSet 中的 transaction来执行具体的方法,看到这个第一个问题“如何来实现在runLoop将要休眠的时候,执行任务”已经有答案了吧。

    下面我们来看下“如何保证相同的任务只执行一次???”

    - (void)commit {
        if (!_target || !_selector) return;
        YYTransactionSetup();
        [transactionSet addObject:self];
    }
    
    - (NSUInteger)hash {
        long v1 = (long)((void *)_selector);
        long v2 = (long)_target;
        return v1 ^ v2;
    }
    
    - (BOOL)isEqual:(id)object {
        if (self == object) return YES;
        if (![object isMemberOfClass:self.class]) return NO;
        YYTransaction *other = object;
        return other.selector == _selector && other.target == _target;
    }
    
    

    就是在commit的时候将transaction添加到transactionSet中,而我们知道NSMutableSet中不会出现相同的对象,所以这就实现了相同的任务只执行一次,同事由于NSMutableSet中是通过isEqual和hash来判断对象是否相同的,所以将这两个方法重写,保证transactionSet中任务的唯一性。

    参考资料:
    https://github.com/ibireme/YYAsyncLayer
    https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
    https://blog.ibireme.com/2015/05/18/runloop/
    https://juejin.im/post/5a0a52b5f265da43247ff4ad
    https://www.jianshu.com/p/58e7571d7806

    相关文章

      网友评论

          本文标题:YYTransaction源码分析

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