YYKit源码解读

依靠大神的肩膀

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

未完待续。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,140评论 30 470
  • 1.内存管理 2.单例的理解 3.post和get的区别 4.md5和base64是什么,有什么区别 5.简单谈谈...
    coder_Wg阅读 1,291评论 1 6
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,715评论 0 9
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    small_Sun阅读 453评论 0 4
  • [玫瑰]20170317徐海波读《不输在家庭教育上》分享(上海,第221天) 《我最欣赏的教育理念》(作者:周国平...
    觉之灯阅读 173评论 0 0