iOS开发进阶:三方源码解读

一、YYMemoryCache的源码解读

  • YYKit项目

YYMemoryCache是用来做内存管理的类,他支持设置缓存对象的个数、最大占用内存大小、时间等限制来达到较好的存储状态,他内部支持通过LRU淘汰策略来清理低频使用的数据。

1.YYMemoryCache分为两个主体部分:
  • YYMemoryCache
//通过key来查询一个对象
- (nullable id)objectForKey:(id)key;

//添加一个实体到缓存
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;

//通过key来删除一个缓存实体
- (void)removeObjectForKey:(id)key;

//清理所有的缓存实体
- (void)removeAllObjects;

//通过count、内存大小、时间等条件删除缓存实体
- (void)trimToCount:(NSUInteger)count;
- (void)trimToCost:(NSUInteger)cost;
- (void)trimToAge:(NSTimeInterval)age;
  • _YYLinkedMap类和_YYLinkedMapNode类,是一个与CFMutableDictionaryRef结合的双向链表.
//新插入一个节点到头部
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;

//将节点移动到头部
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;

//移除一个节点
- (void)removeNode:(_YYLinkedMapNode *)node;

//移除尾节点
- (_YYLinkedMapNode *)removeTailNode;

//移除所有节点
- (void)removeAll;

核心部分是_YYLinkedMap链表,他通过hash表来维护节点的生命周期,根据LRU将最新访问的对象移动/插入到链表的头部,那么响应的不常用的就回跑到链表的末尾,再根据内存限制、个数显示等因素移除的不常用的数据。

2.读取:- (id)objectForKey:(id)key
  • 该方法是一个简单的查询的方法,入惨为缓存实体的key,预处理判空。
  • 通过key查询hash表中的节点,如果存在则更新节点的最后访问时间为当前时间,并且将节点移动到双向链表的头部;最后返回节点上存储的实体。
3.写入:- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost
  • 该方法是一个设置实体的方法,入参包括实体的key、实体本身、占用内存大小
  • 预处理判空:如果key为空则return;如果实体为空,则通过key移除hash表的数据、移除链表中的节点
  • 否则通过key查询hash中的节点。
  • 如果节点已经存在,则更新节点的占用内存大小、时间、实体数据,并修改整体占用内存大小,再将该节点移动到链表的头部
  • 如果节点不存在,则新建节点,绑定相关信息后插入链表头部(存入hash表)
  • 根据限制的占用内存大小、缓存个数清理掉多余的数据。对于内存的清理可能回清理掉多个节点的数据,但是对于个数现在通常只会多一个,如果多了移除为节点就可以了。
4.链表:_YYLinkedMap
  • 内部使用双向链表(Double Linked List)和哈希表(Dictionary)
  • 自身持有链表的头节点和尾节点(非虚拟节点)
  • 在插入到头部insertNodeAtHead、移除指定节点removeNode、移除尾节点removeTailNode、移除全部removeAll这些涉及到数据留存的时候内部进行了hash表的增删;而在链表中移动bringNodeToHead则不需要更改hash表。
  • 移除全部的时候,直接进行将总个数、占用内存滞空、hash表等置空。
  • 为什么使用双向链表?

1.使用双向链表可以快速的移动节点:假如把第N个节点移动到第1个位置,则只需要操作一次就到位了,时间复杂度为O(1);如果使用数组则需要将1N-1之间的数据都向后移动一位,需要执行N-2次,时间复杂度为O(n)
2.使用双向链表可以快速增删元素:假如删除指定的节点,使用爽链表则直接将删除节点的上一节点的next指针指向当前节点的下一个节点,将当前节点的next节点的prev指针指向当前节点的上一个节点,一次操作完成任务,时间复杂度O(1);如果使用单向链表,在做删除时,无法直接拿到当前节点的上一个节点,只能从头开始遍历,找到当前节点的前一个节点,时间复杂度为O(n)

  • _YYLinkedMapNodeprevnext指针,为什么使用__unsafe_unretained修饰?

1._YYLinkedMapNode存入hash表的时候,会对其进行retain操作,所以这里不需要强持有。
2.如果这强持有会造成循环引用:相邻的两个节点A.next = B, B.prev = A,构成循环引用了,在释放的时候必须手动将prev、next指针置为nil,并且在removeAll的时候需要手动将每一个节点的前驱、后续指针置为nil,多了很多操作。

  • 为什么使用CFMutableDictionaryRef,而不用NSMutableDictionary?

两者功能相近,CFMutableDictionaryRef是基于C的实现,效率更高,而NSMutableDictionary是对CFMutableDictionaryRef的封装。


二、Aspects源码解读

1.Objective-Cruntime
  • Objective-Cruntime可以允许我们在运行时进行方法交换,或者在原方法之前插入自定义方法:
+ (void)hookClass:(Class)classObject fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector {
    Class class = classObject;
    // 得到被交换类的实例方法
    Method fromMethod = class_getInstanceMethod(class, fromSelector);
    // 得到交换类的实例方法
    Method toMethod = class_getInstanceMethod(class, toSelector);
    
    // class_addMethod() 函数返回成功表示被交换的方法没实现,然后会通过 class_addMethod() 函数先实现;返回失败则表示被交换方法已存在,可以直接进行 IMP 指针交换 
    if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
        // 进行方法的交换
        class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
    } else {
        // 交换 IMP 指针
        method_exchangeImplementations(fromMethod, toMethod);
    }
}
  • 使用案例:略。
  • class_getInstanceMethod可以得到该类的实例方法,class_addMethod可以为类添加方法,返回true表示被交换的方法的方法没有实现,已经添加成功。返回false表示已经存在添加失败。然后在根据是否添加成功,来class_replaceMethodmethod_exchangeImplementations
  • 但是直接使用runtime进行方法交换会有如下风险:交换需要在+load方法中实现,并且只能交换1次(或者奇数次);被替换的方法只能是当前类的方法,不能是父类的方法,父类的方法只能在调用的时候使用,而不是在交换时;交换的方法如果_cmd则可能会出现一些其他的问题;还可能出现方法名冲突等问题。
2.Aspects
  • Aspects项目
  • Aspects是一个通过runtime消息转发机制实现的的方法交换的库。他将所有的方法调用都指到_objc_msgForward函数调用上,按照自己的方式实现了消息转发,自己处理参数列表、返回值,通过NSInvocation调用方法。
  • 使用案例:
[NXViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionBefore usingBlock:^(id info, BOOL animated){
        NSLog(@"NXViewController-viewDidAppear:");
} error:NULL];
  • AspectOptions有四个枚举值:AspectPositionAfter表示在原始方法之前回调;AspectPositionBefore表示在原始方法之后回调,而AspectPositionInstead表示直接取代原始的方法实现。AspectOptionAutomaticRemoval表示会在首次回调后移除。
  • hook之前会判断是否允许hook,[aspect_isSelectorAllowedAndTrack]:retain, release, autorelease, forwardInvocation:这几个方法是不能hook的;dealloc方法只能hook在原始方法调用之前。不能响应的方法不能hook;同一个方法在父子类继承关系中只能hook一次。
  • 然后再根据交换的事类对象、还是实例对象分别处理,[aspect_prepareClassAndHookSelector]:类对象先修改forwardInvocation,将类的实现转成自己的实现,然后重新生成一个方法来实现交换,最后交换IMP;对于实例对象的方法交换先创建一个新的子类,并将当前实例的isa指向新创建的类,然后再修改类的方法(与KVO的底层实现相似)

你可能感兴趣的:(iOS开发进阶:三方源码解读)