YYKit源码解读

作者: Cocoaleeo | 来源:发表于2017-06-08 16:06 被阅读0次

    依靠大神的肩膀

    YYKit有多屌我就不废话了,上面的文章中各位大神都给出了自己的看法,本文只是记录一下YYKit中个人学习到的东西,如有失误,欢迎指正。

    1. YYKitMacro

    一个三目运算符,返回中间值

    YY_CLAMP(_x_, _low_, _high_)
    

    交换两个变量的值

    YY_SWAP(_a_, _b_)
    

    然后是一些断言,没什么可说的。

    这个在分析YYKit--宏定义的使用文中有提到,解释的很详细,以及weakify和strongify的使用

    YYSYNTH_DUMMY_CLASS(_name_)
    

    下面这个牛X了,我们都知道正常情况下类别中是无法直接添加属性的,但可以通过runtime来实现,这两个宏可以使你在类别中直接添加属性

    // 添加Objective-C类型属性
    YYSYNTH_DYNAMIC_PROPERTY_OBJECT(_getter_, _setter_, _association_, _type_) 
    // 添加C类型属性
    YYSYNTH_DYNAMIC_PROPERTY_CTYPE(_getter_, _setter_, _type_)
    

    后面还有一些封装的方法

    // range转换
    NSRange YYNSRangeFromCFRange(CFRange range)
    CFRange YYCFRangeFromNSRange(NSRange range)
    // time 
    YYBenchmark(void (^block)(void), void (^complete)(double ms))
    NSDate *_YYCompileTime(const char *data, const char *time)
    // GCD
    dispatch_time_t dispatch_time_delay(NSTimeInterval second)
    dispatch_time_t dispatch_walltime_delay(NSTimeInterval second)
    dispatch_time_t dispatch_walltime_date(NSDate *date)
    bool dispatch_is_main_queue()
    void dispatch_async_on_main_queue(void (^block)())
    void dispatch_sync_on_main_queue(void (^block)())
    

    2. Foundation

    这部分分别对NSObject, KVO, ARC, NSString, NSNumber, NSDate, NSData, NSArray, NSDictionary, NSNotificationCenter, NSKeyedUnarchiver, NSTimer, NSBundle, NSThread 进行了类别扩展

    2.1 NSObject+YYAdd
    // 发送消息,获取消息的返回值
    - (nullable id)performSelectorWithArgs:(SEL)sel, ...;
    // 几个方法,分别与线程和Delay相关
    - (void)performSelectorWithArgs:(SEL)sel afterDelay:(NSTimeInterval)delay, ...;
    - (nullable id)performSelectorWithArgsOnMainThread:(SEL)sel waitUntilDone:(BOOL)wait, ...;
    - (nullable id)performSelectorWithArgs:(SEL)sel onThread:(NSThread *)thread waitUntilDone:(BOOL)wait, ...;
    - (void)performSelectorWithArgsInBackground:(SEL)sel, ...;
    - (void)performSelector:(SEL)sel afterDelay:(NSTimeInterval)delay;
    

    Runtime是OC的一大特性,在实际开发中用到的大概就是通过runtime来获取属性列表,以及method swizzling!下面这几个方法是对runtime的封装,看名字都可以猜到实现方法!

    // 实例方法交换
    + (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel;
    // 类方法交换
    + (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel;
    
    //runtime 设置关联
    - (void)setAssociateValue:(nullable id)value withKey:(void *)key;
    // 设置声明为weak的属性
    - (void)setAssociateWeakValue:(nullable id)value withKey:(void *)key;
    // 获取关联属性
    - (nullable id)getAssociatedValueForKey:(void *)key;
    // 移除关联属性
    - (void)removeAssociatedValues;
    

    深拷贝,这个有点黑的感觉,通常情况下,我们要实现深拷贝会调用copy方法,这里直接将对象的数据复制一份赋给新对象,绕过了中间过程,直接底层访问,从而达到加快编译速度的目的;这个思路贯穿整个YYKit。

    - (id)deepCopy {
        id obj = nil;
        @try {
            obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self]];
        }
        @catch (NSException *exception) {
            NSLog(@"%@", exception);
        }
        return obj;
    }
    - (id)deepCopyWithArchiver:(Class)archiver unarchiver:(Class)unarchiver;
    
    2.2. NSString+YYAdd

    md2, md4, md5......一大串加密方法,URL和HTML归档方法。
    计算String 的size, 这个方法用到的可能性比较大!

    // 计算String 的size, 这个方法用到的可能性比较大!
    - (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode
    - (CGFloat)widthForFont:(UIFont *)font;
    - (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width;
    

    这里面有个容错处理,很优雅的处理方式,再也不用判断string的length了,我承认我菜!

    [self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]
    
    // 遍历字符串的元素
    - (void)enumerateUTF32CharInRange:(NSRange)range usingBlock:(void (^)(UTF32Char char32, NSRange range, BOOL *stop))block;
    
    2.3 NSThread+YYAdd

    只有一个方法

    + (void)addAutoreleasePoolToCurrentRunloop {
        if ([NSThread isMainThread]) return; // 当前线程为主线程时,直接返回,主线程自带autoreleasePool
        NSThread *thread = [self currentThread];
        if (!thread) return;
        if (thread.threadDictionary[YYNSThreadAutoleasePoolKey]) return; // already added
        YYRunloopAutoreleasePoolSetup(); // 进行配置,看下面
        thread.threadDictionary[YYNSThreadAutoleasePoolKey] = YYNSThreadAutoleasePoolKey; // mark the state
    }
    

    配置AutoreleasePool, 在配置中,添加了两个观察者,当RunLoop的状态发生改变时,进行YYRunLoopAutoreleasePoolObserverCallBack回调,这部分大神自己管理的内存,所以是不能在ARC中调用的。

    static void YYRunloopAutoreleasePoolSetup() {
        CFRunLoopRef runloop = CFRunLoopGetCurrent();
    
        CFRunLoopObserverRef pushObserver;
        pushObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopEntry,
                                               true,         // repeat
                                               -0x7FFFFFFF,  // before other observers
                                               YYRunLoopAutoreleasePoolObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, pushObserver, kCFRunLoopCommonModes);
        CFRelease(pushObserver);
        
        CFRunLoopObserverRef popObserver;
        popObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                              true,        // repeat
                                              0x7FFFFFFF,  // after other observers
                                              YYRunLoopAutoreleasePoolObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, popObserver, kCFRunLoopCommonModes);
        CFRelease(popObserver);
    }
    

    回调方法, 当runloop状态发生改变时,向PoolStack中添加(push)或移除(pop)autoreleasePool。

    static void YYRunLoopAutoreleasePoolObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
        switch (activity) {
            case kCFRunLoopEntry: {
                YYAutoreleasePoolPush();
            } break;
            case kCFRunLoopBeforeWaiting: {
                YYAutoreleasePoolPop();
                YYAutoreleasePoolPush();
            } break;
            case kCFRunLoopExit: {
                YYAutoreleasePoolPop();
            } break;
            default: break;
        }
    }
    

    Push

    static inline void YYAutoreleasePoolPush() {
        NSMutableDictionary *dic =  [NSThread currentThread].threadDictionary;
        NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
        
        if (!poolStack) {
            /*
             do not retain pool on push,
             but release on pop to avoid memory analyze warning
             */
            CFArrayCallBacks callbacks = {0};
            callbacks.retain = PoolStackRetainCallBack;
            callbacks.release = PoolStackReleaseCallBack;
            poolStack = (id)CFArrayCreateMutable(CFAllocatorGetDefault(), 0, &callbacks);
            dic[YYNSThreadAutoleasePoolStackKey] = poolStack;
            CFRelease(poolStack);
        }
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //< create
        [poolStack addObject:pool]; // push
    }
    

    Pop

    static inline void YYAutoreleasePoolPop() {
        NSMutableDictionary *dic =  [NSThread currentThread].threadDictionary;
        NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
        [poolStack removeLastObject]; // pop
    }
    

    ** 小结一下:** 在Foundation这部分中,其作者添加了很多方便的方法来,上文中只提到了其中的部分代码;像在NSArray, NSDictionary等其他类目中添加了从头plist文件中读取文件,添加,删除元素,倒叙,打乱排序一类的方法,各位可自行探索,没有什么难点,都可以看懂的!

    3. YYModel

    YYModel用来将网络请求到的数据转换为需要的model,具有以下特性:

    • 优良的性能
    • 自动类型转换
    • 类型安全
    • 不需要继承其他基础类
    • 轻量级
    // 创建一个YYTestModel类
    @interface YYTestModel : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger uid;
    @property (nonatomic, strong) NSDate *created;
    
    @end
    
    // 测试代码
    - (void)yy_test
    {
        // 数据,可以是NSData,NSDictionary,NSString类型
        NSDictionary *data = @{
                                   @"uid":@123456,
                                   @"name":@"Harry",
                                   @"created":@"1965-07-31T00:00:00+0000"
                               };
        // 数据转模型
        YYTestModel *model = [YYTestModel modelWithJSON:data];
    }
    

    点modelWithJSON:进去,其思路通过_YYModelMeta类获取到属性信息,然后通过消息机制调用Setter方法进行赋值,前面通过一系列转换后拿到Dictionary,看核心方法

    - (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
        if (!dic || dic == (id)kCFNull) return NO;
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        // 创建元类,元类中会包含model的属性信息
        _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
        // 如果属性个数为0,则返回
        if (modelMeta->_keyMappedCount == 0) return NO;
        
        if (modelMeta->_hasCustomWillTransformFromDictionary) {
            dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
            if (![dic isKindOfClass:[NSDictionary class]]) return NO;
        }
        
        ModelSetContext context = {0};
        context.modelMeta = (__bridge void *)(modelMeta);
        context.model = (__bridge void *)(self);
        context.dictionary = (__bridge void *)(dic);
    
        // 对属性赋值,在ModelSetWithPropertyMetaArrayFunction方法内部通过消息机制调用setter方法,对属性进行赋值,下面的几个方法也类似
        if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
            
            CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
            if (modelMeta->_keyPathPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
            if (modelMeta->_multiKeysPropertyMetas) {
                CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                     CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                     ModelSetWithPropertyMetaArrayFunction,
                                     &context);
            }
        } else {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                                 CFRangeMake(0, modelMeta->_keyMappedCount),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        
        if (modelMeta->_hasCustomTransformFromDictionary) {
            return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
        }
        return YES;
    }
    

    未完待续。。。

    相关文章

      网友评论

        本文标题:YYKit源码解读

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