iOS開發進階:三方源碼解讀

一、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);如果使用數組則需要將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<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的底層實現相似)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章