一、YYMemoryCache的源码解读
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)
;如果使用数组则需要将1
到N-1
之间的数据都向后移动一位,需要执行N-2
次,时间复杂度为O(n)
。
2.使用双向链表可以快速增删元素:假如删除指定的节点,使用爽链表则直接将删除节点的上一节点的next
指针指向当前节点的下一个节点,将当前节点的next
节点的prev
指针指向当前节点的上一个节点,一次操作完成任务,时间复杂度O(1)
;如果使用单向链表,在做删除时,无法直接拿到当前节点的上一个节点,只能从头开始遍历,找到当前节点的前一个节点,时间复杂度为O(n)
。
-
_YYLinkedMapNode
的prev
和next
指针,为什么使用__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-C
的runtime
-
Objective-C
的runtime
可以允许我们在运行时进行方法交换,或者在原方法之前插入自定义方法:
+ (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_replaceMethod
或method_exchangeImplementations
。 - 但是直接使用
runtime
进行方法交换会有如下风险:交换需要在+load方法中实现,并且只能交换1次(或者奇数次);被替换的方法只能是当前类的方法,不能是父类的方法,父类的方法只能在调用的时候使用,而不是在交换时;交换的方法如果_cmd则可能会出现一些其他的问题;还可能出现方法名冲突等问题。
2.Aspects
- Aspects项目
-
Aspects
是一个通过runtime
消息转发机制实现的的方法交换的库。他将所有的方法调用都指到_objc_msgForward
函数调用上,按照自己的方式实现了消息转发,自己处理参数列表、返回值,通过NSInvocation
调用方法。 - 使用案例:
[NXViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> 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
的底层实现相似)