一、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
的底層實現相似)