iOS Developer好东西小知识点

YYKit源码解读

2017-06-08  本文已影响0人  Cocoaleeo

依靠大神的肩膀

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

未完待续。。。

上一篇下一篇

猜你喜欢

热点阅读