目錄
- 對象的分類
- object_getClass和class方法
- isa流程和繼承鏈分析
- isa流程實例驗證
- 類的繼承鏈實例驗證
- 類的結構
- cache_t結構
- bits分析
- 實例驗證
- 屬性properties
- 方法methods
- 協議protocols
- ro
- 類方法
- 類結構流程圖解
對象的分類
OC中的對象主要可以分為3種:實例對象(instance)、類對象(class)和元類對象(meta-class)
實例對象
通過類alloc出來的對象,每次調用alloc都會產生新的instance對象
NSObject* obj1 = [[NSObject alloc] init];
NSObject* obj2 = [[NSObject alloc] init];
NSLog(@"%p %p", obj1, obj2);
// 打印結果:0x600000180040 0x600000180050
從運行結果可看出以上是不同的兩個實例對象,分別占據著兩塊不同的內存
實例對象在內存中存儲的信息包括:isa
指針、其他成員變量
類對象
#import <objc/runtime.h>
Class objectClass1 = [obj1 class];
Class objectClass2 = [obj2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(obj1); //Runtime API
Class objectClass5 = object_getClass(obj2); //Runtime API
// 打印結果:0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070
以上都是NSObject
的類對象,從運行結果可看出它們都是同一個對象,即這些指針指向的是同一塊內存,每個類在內存中有且只有一個class對象
類對象在內存中存儲的信息主要包括:isa
指針、superclass
指針、類的屬性信息(@property
)、類的對象方法信息(instance method
)、類的協議信息(protocol
)、類的成員變量(ivar
,類型、名稱等描述信息而不是具體的值)
元類對象
看下面如何獲取元類對象(元類對象類型仍是一個類對象,底層都是struct objc_class* Class
,只是包含的信息不一樣)
Class objectMetaClass = object_getClass(object_getClass(obj1));
將類對象作為參數傳入,再次調用object_getClass
函數
那如果調用兩次class
方法呢?
Class objectMetaClass2 = [[NSObject class] class];
NSLog(@"%p %p %d", objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
// 打印結果:0x1d6fc6020 0x1d6fc6070 1 0
從打印結果可以看出,class
不管調多少次返回的一直是類對象,不會是元類對象
每個類只有一個元類對象,元類對象在內存中存儲的信息主要包括:isa
指針、superclass指針以及類方法信息
object_getClass和class方法
查看objc4源碼
object_getClass
方法中傳入各種對象,通過訪問isa
,返回不同的類對象:
Class object_getClass(id obj)
{if (obj) return obj->getIsa();else return Nil;
}// 傳入類名字符串,返回對象的類對象
Class objc_getClass(const char *aClassName)
{if (!aClassName) return Nil;// NO unconnected, YES class handlerreturn look_up_class(aClassName, NO, YES);
}
class
方法直接返回類對象:
//+ (id)self {
// return (id)self;
//}
//- (id)self {
// return self;
//}+ (Class)class {return self;
}- (Class)class {return object_getClass(self);
}//+ (Class)superclass {
// return self->getSuperclass();
//}
//- (Class)superclass {
// return [self class]->getSuperclass();
//}
isa流程和繼承鏈分析
上面我們了解了對象的分類,認識到不同類型對象的差別,那么是什么讓這些不同類型的對象聯系起來從而構成OC對象體系的呢?
上經典老圖:
isa指向鏈
實際上就是isa
指針將它們聯系起來形成 isa
指向鏈:
- 實例對象
instance
的isa
指向類class
- 類對象
class
也有isa
指向的是元類meta
- 元類
meta
中也有isa
指向的是根元類root meta
當調用對象方法時,通過實例對象的isa
找到class
,最后找到對象方法的實現進行調用
當調用類方法時,通過類對象的isa
找到meta-class
,最后找到類方法的實現進行調用
類繼承鏈
根據superclass
的指向,也可總結出OC類的繼承鏈:
- 子類繼承于父類,父類繼承于根類,根類指向的是
nil
- 在元類中也存在繼承,子類的元類繼承于父類的元類,父類的元類繼承于根元類,根元類又繼承與根類
當Student的實例對象要調用Person的對象方法時,會先通過isa
找到Student的class
,然后通過superclass
找到Person的class
,最后找到對象方法的實現進行調用
類似地,當Student的類對象要調用Person的類方法時,會先通過isa
找到Student的meta-class
,然后通過superclass
找到Person的meta-class
,最后找到類方法的實現進行調用
isa流程實例驗證
Person類繼承于NSObject,Student類繼承于Person
@interface Person : NSObject {@publicint _age;
}- (void)personInstanceMethod;
+ (void)personClassMethod;@end@interface Student : Person {@publicint _no;
}- (void)studentInstanceMethod;
+ (void)studentClassMethod;@end
打斷點,通過LLDB查看isa關聯類的地址:
// 打印出實例的地址
Person* person = [Person alloc];
NSLog(@"%@", person);
Student* student = [Student alloc];
NSLog(@"%@", student);
類對象的地址和實例對象
isa
所指向的地址有所出入,isa
需要進行一次位運算,才能計算出類對象的真實地址
在獲取到對象的isa
值后,可以通過&
(按位與)一個掩碼ISA_MASK 0x007ffffffffffff8ULL
來獲取到對象關聯的類地址:
根據student
實例的isa
地址找到關聯類Student的地址0x00000001000082d8
同樣地,根據Student類對象的isa
找到Student元類的地址0x00000001000082b0
根據Student元類對象的isa
找到關聯類的地址0x00000001d6fc6020
找到NSObject類對象的isa
關聯類地址0x00000001d6fc6020
,與Student元類對象的isa
關聯類地址一致,可以驗證元類的isa
指向根元類,且根元類的isa指向自己
類的繼承鏈實例驗證
Class tClass = [Student class];
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);
可看出類對象的繼承鏈:Student->Person->NSObject->nil
Student * student = [Student alloc];
Class tClass = object_getClass(student);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n student %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類", student, tClass, mtClass, mtSuperClass);
Person * person = [Person alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類", person, pClass, mpClass, mpSuperClass);
NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類 == %p NSObject類對象", obj, objClass, mobjClass, mobjSuperClass,
[NSObject class]);
可看出元類的繼承鏈:Student Meta-class -> Person Meta-class -> NSObject Meta-class -> NSObject class -> nil
類的結構
前面我們了解到了Class
的類型是struct objc_class*
結構體指針類型,下面就來分析一下這個結構體的定義
struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags// ...其他代碼,objc_class定義共計531行代碼...
};
繼承于objc_object
說明:
- 還有一個繼承過來的Class類型變量
isa
superclass
:指向父類的指針cache
:緩存相關bits
:用于獲取具體的類信息
cache_t結構
cache_t
是一個結構體
struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字節union {struct {explicit_atomic<mask_t> _maybeMask; // uint32_t 4字節
#if __LP64__uint16_t _flags; // 2字節
#endifuint16_t _occupied; // 2字節};explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字節};};
// 此段為部分代碼,cache_t定義總共有290行
分析整個cache_t的結構,發現cache_t的內存總共為16字節,后面會對其底層進行學習
bits分析
在objc_class
里有一段源碼是data
操作
class_rw_t *data() const {return bits.data();
}
void setData(class_rw_t *newData) {bits.setData(newData);
}
data
為class_rw_t
類型,下面是其部分源碼:
ro
:成員變量、methods
:方法、properties
:屬性、protocols
協議
我們在類中定義的方法、屬性等就是通過調取class_rw_t
結構體中的方法獲取的
實例驗證
下面通過實例來驗證一下類的結構是否如上面一致
創建Person類繼承于NSObject,定義一些屬性、方法以及協議:
@protocol PersonDelegate<NSObject>- (void)personDelegateMethod;
// 讓Person類遵守并實現此協議方法
@end@interface Person : NSObject<PersonDelegate> {NSString* hobby;
}@property (nonatomic, strong)NSString* name;
@property (nonatomic, assign)NSInteger age;- (void)sayHello;
+ (void)sayWorld;@end
LLDB
調試輸出
第一個地址0x0000000100008470
是類的第一個成員isa
,第二個地址0x00000001d6fc6070
是類的第二個成員superclass
isa
和superclass
都是結構體指針類型,占用8字節,cache
結構體占用16字節,XYPerson的地址加上8 + 8 + 16 = 32
就可以得到bits
的地址
相加并強轉為class_data_bits_t *
類型得到bits
的地址0x0000000100008270
,再調用data()
方法就得到類型為class_rw_t
的地址
屬性properties
調用class_rw_t
的properties()
方法,得到property_array_t
類型的數組,繼承于list_array_tt
,找到list
下的ptr
class property_array_t :public list_array_tt<property_t, property_list_t, RawPtr>
{typedef list_array_tt<property_t, property_list_t, RawPtr> Super;public:property_array_t() : Super() { }property_array_t(property_list_t *l) : Super(l) { }
};
ptr
為property_list_t
類型,繼承于entsize_list_tt
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
entsize_list_tt
部分源碼:
struct entsize_list_tt {uint32_t entsizeAndFlags;uint32_t count; // 數量uint32_t entsize() const {return entsizeAndFlags & ~FlagMask;}uint32_t flags() const {return entsizeAndFlags & FlagMask;}Element& getOrEnd(uint32_t i) const {ASSERT(i <= count);return *PointerModifier::modify(*(List *)this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));}Element& get(uint32_t i) const { // 獲取元素方法ASSERT(i < count);return getOrEnd(i);}// ...其他代碼...
};
通過調用get()
方法,獲取元素,下面的結果就是Person類的name
、age
在properties()
里,而實例變量hobby
不在這里
方法methods
調用class_rw_t
的methods()
方法,得到method_array_t
類型的數組,繼承于list_array_tt
,同樣找到list
下的ptr
這里看到ptr
是method_list_t
類型,同樣繼承于entsize_list_tt
,其中有count
為6,調用get()
方法查看輸出
這里的元素為method_t
類型,method_t
為結構體類型,其中的一個成員變量為big
的結構體,里面是方法名稱等信息:
struct method_t {method_t(const method_t &other) = delete;// The representation of a "big" method. This is the traditional// representation of three pointers storing the selector, types// and implementation.struct big {SEL name;const char *types;MethodListIMP imp;};
// ...其他代碼
};
調用big
方法查看輸出
這6個方法分別是:
- 實例方法:
sayHello
- 屬性
name
、age
的set
/get
方法 C++
析構函數:.cxx_destruct
且都是實例方法,并沒有類方法sayWorld
協議protocols
調用class_rw_t
的protocols()
方法,得到protocol_array_t
類型的數組,繼承于list_array_tt
,同樣找到list
下的ptr
這里protocol_list_t
并沒有繼承于entsize_list_tt
:
struct protocol_list_t {// count is pointer-sized by accident.uintptr_t count;protocol_ref_t list[0]; // variable-sizesize_t byteSize() const {return sizeof(*this) + count*sizeof(list[0]);}protocol_list_t *duplicate() const {return (protocol_list_t *)memdup(this, this->byteSize());}typedef protocol_ref_t* iterator;typedef const protocol_ref_t* const_iterator;const_iterator begin() const {return list;}iterator begin() {return list;}const_iterator end() const {return list + count;}iterator end() {return list + count;}
};
看到protocol_list_t
的定義,我們知道count
值為1,說明是有值,但是其成員是protocol_ref_t
為uintptr_t
類型,那怎么輸出查看這個count
中的1到底是什么呢
查看protocol_ref_t
的定義,通過注釋信息,我們可以看到protocol_ref_t
未映射到protocol_t
類型,那我們就找protocol_t
的定義
這里看到protocol_t
中有mangledName
以及instanceMethods
等,只要得到protocol_t
就可以輸出我們想要的名稱方法等信息,怎么才能從protocol_ref_t
映射到protocol_t
呢,全局找一下吧
這里我們看到,protocol_ref_t
是可以強轉protocol_t
的,那我們就試試:
強轉成功,調用demangledName
方法,我們就得到了LGPersonDelegate
,那我們再找一下協議方法
按照method
查看輸出的步驟,成功找到協議方法personDelegateMethod
ro
調用class_rw_t
的ro
方法,得到class_ro_t
的結構體
查看ivars
,也是繼承于entsize_list_tt
的ivar_list_t
類型的結構體,調用get
方法查看:
這6個實例變量分別是自定義hobby
以及系統自動幫我們自動生成的帶有_
的實例變量
類方法
methods
中的方法全部都存在類中,都是實例方法,那么類方法應該去在元類中找
通過類的isa
指針找到元類,再根據上面的步驟找到并輸出這個元類的methods
這里我們不由地想,OC的底層是
C/C++
實現的,不存在對象方法和類方法的區分,有的都是函數實現,在OC的設計中,一個類可以new出無數個對象,因此把方法存在類中,而不是動態創建的對象中,是合理的。
因為OC的對象方法和類方法的定義是-
和+
的區分,那么方法名稱就會有重名的存在,因此才會引入元類的概念,元類的存在就是解決類方法重名的問題
類結構流程圖解
類的結構流程圖解析: