Category、load、initialize、Class、關聯對象、isa

一 、Category的實現原理

Category編譯之後的底層結構是struct category_t,裏面存儲着分類的對象方法、類方法、屬性、協議信息。在程序運行的時候,runtime會將Category的數據,合併到類信息中(類對象、元類對象中)

Category和Class Extension的區別是什麼?
Class Extension在編譯的時候,它的數據就已經包含在類信息中
Category是在運行時,纔會將數據合併到類信息中

Category中有load方法嗎?load方法是什麼時候調用的?load 方法能繼承嗎?
有load方法。load方法在runtime加載類、分類的時候調用
load方法可以繼承,但是一般情況下不會主動去調用load方法,都是讓系統自動調用

Category能否添加成員變量?如果可以,如何給Category添加成員變量?
不能直接給Category添加成員變量,但是可以利用runtime的API間接實現Category有成員變量的效果。

詳細原因:因Calss定義內,class_rw_t結構體內有class_ro_t屬性的結構體,只讀的
class_ro_t結構體內有ivars **成員變量**、instanceSize,是在編譯的時候就確定的,
 而Category是運行時纔將數據合併到類信息中,默認情況下,因爲分類底層結構的
 限制,不能添加成員變量到分類中。但可以通過關聯對象來間接實現。

結構體如下
定義在objc-runtime-new.h中

  • 源碼解讀順序
    objc-os.mm
    _objc_init
    map_images
    map_images_nolock

  • objc-runtime-new.mm
    _read_images
    remethodizeClass
    attachCategories
    attachLists
    realloc、memmove、 memcpy

  • 通過Runtime加載某個類的所有Category數據
    把所有Category的方法、屬性、協議數據,合併到一個大數組中
    後面參與編譯的Category數據,會在數組的前面
    將合併後的分類數據(方法、屬性、協議),插入到類原來數據的前面
    分類中重寫類中原有的某個方法,導致原方法被“覆蓋”。其實不算覆蓋,是因合併到類方法列表的前部,方法查找時,因爲分類方法在前,就找到分類的方法響應,類遠方法就未輪到響應,造成被覆蓋的假象。

二、+load方法

  • +load方法會在runtime加載類、分類時調用
  • 每個類、分類的+load,在程序運行過程中只調用一次
  • 調用順序
  • 先調用類的+load 按照編譯先後順序調用(先編譯,先調用) 調用
    子類的+load之前會先調用父類的+load
  • 再調用分類的+load 按
    照編譯先後順序調用(先編譯,先調用)
  • +load方法是根據方法地址直接調用,並不是經過objc_msgSend函數調用

objc4源碼解讀過程
objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list

call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)

三、+initialize方法

  • +initialize方法會在類第一次接收到消息時調用 調用順序 先調用父類的+initialize,再調用子類的+initialize (先
  • 初始化父類,再初始化子類,每個類只會初始化1次)
  • +initialize和+load的很大區別是,+initialize是通過objc_msgSend進行調用的。
  • 如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次)
  • 如果分類實現了+initialize,就覆蓋類本身的+initialize調用

四、Class

class的結構
在這裏插入圖片描述

class_rw_t裏面的methods、properties、protocols是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容
在這裏插入圖片描述

class_ro_t裏面的baseMethodList、baseProtocols、ivars、baseProperties是一維數組,是隻讀的,包含了類的初始內容
在這裏插入圖片描述

method_t是對方法\函數的封裝
在這裏插入圖片描述

IMP代表函數的具體實現
在這裏插入圖片描述

SEL代表方法\函數名,一般叫做選擇器,底層結構跟char *類似
可以通過@selector()和sel_registerName()獲得
可以通過sel_getName()和NSStringFromSelector()轉成字符串
不同類中相同名字的方法,所對應的方法選擇器是相同的

types包含了函數返回值、參數編碼的字符串
在這裏插入圖片描述

iOS中提供了一個叫做@encode的指令,可以將具體的類型表示成字符串編碼
在這裏插入圖片描述

方法緩存

  • Class內部結構中有個方法緩存(cache_t),用散列表(哈希表)來緩存曾經調用過的方法,可以提高方法的查找速度
    在這裏插入圖片描述

  • 緩存查找
    objc-cache.mm
    bucket_t * cache_t::find(cache_key_t k, id receiver)

在這裏插入圖片描述

五、關聯對象

  • 添加關聯對象
    void objc_setAssociatedObject(id object, const void * key,
    id value, objc_AssociationPolicy policy)

  • 獲得關聯對象
    id objc_getAssociatedObject(id object, const void * key)

  • 移除所有的關聯對象
    void objc_removeAssociatedObjects(id object)

key的常見用法
1.靜態方法變量 static void *MyKey = &MyKey;

objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

2.靜態變量 static char MyKey;

objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

3.使用屬性名作爲key

objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

4.使用get方法的@selecor作爲key

objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

關聯策略 objc_AssociationPolicy

objc_AssociationPolicy 對應的修飾符
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic
OBJC_ASSOCIATION_COPY copy, atomic

關聯對象的原理

  • 實現關聯對象技術的核心對象有
    AssociationsManager
    AssociationsHashMap
    ObjectAssociationMap
    ObjcAssociation
  • objc4源碼解讀:objc-references.mm

在這裏插入圖片描述

  • 關聯對象並不是存儲在被關聯對象本身內存中關聯對象存儲在全局的統一的一個AssociationsManager中,設置關聯對象爲nil,就相當於是移除關聯對象

詳見下圖:
在這裏插入圖片描述

六、isa、superClass

在這裏插入圖片描述
對象的isa指針指向哪裏?

  • instance對象的isa指向class對象
  • class對象的isa指向meta-class對象
  • meta-class對象的isa指向基類的meta-class對象

superClass

  • class的superclass指向父類的class,如果沒有父類,superclass指針爲nil

  • meta-class的superclass指向父類的meta-class,基類的meta-class的superclass指向基類的class

instance調用對象方法的軌跡

  • isa找到class,方法不存在,就通過superclass找父類

class調用類方法的軌跡

  • isa找meta-class,方法不存在,就通過superclass找父類

據以上Class結構體可知有個isa屬性,在arm64架構之前,isa就是一個普通的指針,存儲着Class、Meta-Class對象的內存地址從arm64架構開始,對isa進行了優化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息,見下圖:
在這裏插入圖片描述

  • nonpointer
    0,代表普通的指針,存儲着Class、Meta-Class對象的內存地址
    1,代表優化過,使用位域存儲更多的信息

  • has_assoc
    是否有設置過關聯對象,如果沒有,釋放時會更快

  • has_cxx_dtor
    是否有C++的析構函數(.cxx_destruct),如果沒有,釋放時會更快

  • shiftcls
    存儲着Class、Meta-Class對象的內存地址信息

  • magic
    用於在調試時分辨對象是否未完成初始化

  • weakly_referenced
    是否有被弱引用指向過,如果沒有,釋放時會更快

  • deallocating
    對象是否正在釋放

  • extra_rc
    裏面存儲的值是引用計數器減1

  • has_sidetable_rc
    引用計數器是否過大無法存儲在isa中
    如果爲1,那麼引用計數會存儲在一個叫SideTable的類的屬性中

七、objc_msgSend執行流程

OC 的消息發送機制

OC中的方法調用其實都是轉成了objc_msgSend函數的調用,給receiver(方法調用者)發送了一條消息(selector方法名)
objc_msgSend底層有3大階段

  • 消息發送(當前類、父類中查找)、
  • 動態方法解析
  • 消息轉發

objc_msgSend執行流程 – 源碼跟讀
objc-msg-arm64.s

ENTRY _objc_msgSend
b.le	LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
__class_lookupMethodAndLoadCache3

objc-runtime-new.mm

_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache

objc-msg-arm64.s

STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward

Core Foundation
__forwarding__(不開源)

objc_msgSend執行流程01-消息發送
在這裏插入圖片描述

  • 如果是從class_rw_t中查找方法
    已經排序的,二分查找
    沒有排序的,遍歷查找
  • receiver通過isa指針找到receiverClass
  • receiverClass通過superclass指針找到superClass

objc_msgSend執行流程02-動態方法解析
在這裏插入圖片描述

  • 開發者可以實現以下方法,來動態添加方法實現
    +resolveInstanceMethod:
    +resolveClassMethod:
  • 動態解析過後,會重新走“消息發送”的流程
    “從receiverClass的cache中查找方法”這一步開始執行

動態添加方法
在這裏插入圖片描述

  • Method可以理解爲等價於struct method_t *
  • @dynamic是告訴編譯器不用自動生成getter和setter的實現,等到運行時再添加方法實現

objc_msgSend的執行流程03-消息轉發
在這裏插入圖片描述

  • 開發者可以在forwardInvocation:方法中自定義任何邏輯
  • 以上方法都有對象方法、類方法2個版本(前面可以是加號+,也可以是減號-)

生成NSMethodSignature
在這裏插入圖片描述

八、super的本質

在這裏插入圖片描述

  • super調用,底層會轉換爲objc_msgSendSuper2函數的調用,接收2個參數
    struct objc_super2
    SEL
  • receiver是消息接收者
  • current_class是receiver的Class對象

九、Runtime

OC是一門動態性比較強的編程語言,允許很多操作推遲到程序運行時再進行。
OC的動態性就是由Runtime來支撐和實現的,Runtime是一套C語言的API,封裝了很多動態性相關的函數,編寫的OC代碼,底層都是轉換成了Runtime API進行調用

Runtime的應用

  • 查看私有成員變量
    如:設置UITextField佔位文字的顏色
    在這裏插入圖片描述

  • 字典轉模型
    1.利用Runtime遍歷所有的屬性或者成員變量
    2.利用KVC設值

  • 替換方法實現
    1.class_replaceMethod
    2.method_exchangeImplementations

**Runtime API 1 – 類 **

  • 動態創建一個類(參數:父類,類名,額外的內存空間)
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

  • 註冊一個類(要在類註冊之前添加成員變量)
    void objc_registerClassPair(Class cls)

  • 銷燬一個類
    void objc_disposeClassPair(Class cls)

  • 獲取isa指向的Class
    Class object_getClass(id obj)

  • 設置isa指向的Class
    Class object_setClass(id obj, Class cls)

  • 判斷一個OC對象是否爲Class
    BOOL object_isClass(id obj)

  • 判斷一個Class是否爲元類
    BOOL class_isMetaClass(Class cls)

  • 獲取父類
    Class class_getSuperclass(Class cls)

Runtime API 2 – 成員變量

  • 獲取一個實例變量信息
    Ivar class_getInstanceVariable(Class cls, const char *name)

  • 拷貝實例變量列表(最後需要調用free釋放)
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

  • 設置和獲取成員變量的值
    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)

  • 動態添加成員變量(已經註冊的類是不能動態添加成員變量的)
    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

  • 獲取成員變量的相關信息
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)

Runtime API 3 – 屬性

  • 獲取一個屬性
    objc_property_t class_getProperty(Class cls, const char *name)

  • 拷貝屬性列表(最後需要調用free釋放)
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

  • 動態添加屬性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

  • 動態替換屬性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

  • 獲取屬性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)

Runtime API 4 – 方法

  • 獲得一個實例方法、類方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)

  • 方法實現相關操作
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2)

  • 拷貝方法列表(最後需要調用free釋放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)

  • 動態添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

  • 動態替換方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

  • 獲取方法的相關信息(帶有copy的需要調用free去釋放)
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)

  • 選擇器相關
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)

  • 用block作爲方法實現
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)

以上僅做個人知識回顧使用,有誤之處,希望指正。不全之處,還請移步查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章