首先娘荡,我們看下幾個(gè)類型:NSObject
、Class
祭钉、objc_object
瞄沙、objc_class
、id
的聯(lián)系與區(qū)別慌核。
- NSObject:OC中的基類距境,絕大多數(shù)類都繼承
NSObject
(NSProxy
也是基類哦~) - Class:
NSObject
的類型,在objc
源碼的NSObject.mm
文件中可以看到
+ (Class)class {
return self;
}
- objc_object:
NSObject
類在C++的底層實(shí)現(xiàn)的結(jié)構(gòu)體名稱,在生成的cpp文件中可以看到,它和NSObject
是同一個(gè)東西咙冗,只是在不同語(yǔ)言環(huán)境的名字不同。objc_object
結(jié)構(gòu)體里面诬滩,只有一個(gè)成員變量isa
指針。
typedef struct objc_object NSObject;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
- objc_class:
Class
在C++底層實(shí)現(xiàn)的結(jié)構(gòu)體名稱灭将,在生成的cpp文件中可以看到疼鸟,它和Class
是同一個(gè)東西,只是在不同語(yǔ)言環(huán)境的名字不同庙曙。
typedef struct objc_class *Class;
- id:OC環(huán)境中可以指向任何類型空镜,并且不用帶*號(hào)
typedef struct objc_object *id;
isa與superClass探究
我們先看下類在內(nèi)存中地址,是不是每創(chuàng)建一對(duì)象捌朴,都會(huì)創(chuàng)建一個(gè)新的與之對(duì)應(yīng)的類吴攒。
void getSJPersonClass(void) {
Class cls1 = SJPerson.class;
Class cls2 = [SJPerson alloc].class;
Class cls3 = object_getClass([SJPerson alloc]);
Class cls4 = [SJPerson alloc].class;
NSLog(@"cls1 : %p \n cls1 : %p \n cls1 : %p \n cls1 : %p \n", cls1, cls2, cls3, cls4);
}
執(zhí)行getSJPersonClass
,打印結(jié)果如下:
也就是同樣的類砂蔽,在內(nèi)存中只會(huì)有1份洼怔。
我們打印實(shí)例變量p
的內(nèi)存,我們知道前8位是isa
指針
isa
地址與ISA_MASK
做與運(yùn)算左驾,可以拿到類對(duì)象的地址镣隶。
我們繼續(xù)拿類的isa
地址與ISA_MASK
做與運(yùn)算,看可以得到什么诡右。
我們看到安岂,最后打印的結(jié)果也是
SJPerson
,但是兩個(gè)SJPerson
的地址不一樣稻爬,一個(gè)0x00000001000085f8嗜闻,一個(gè)0x00000001000085d0蜕依,也就是說(shuō)這兩個(gè)不是一個(gè)類型桅锄。在這就不賣(mài)關(guān)子了琉雳,第二個(gè)SJPerson
就是我們常說(shuō)的元類。為什么是元類呢友瘤,我們可以在machOView中找到相關(guān)內(nèi)容:
看到有一個(gè)
metaclass
也就是元類翠肘。那我們繼續(xù)看下元類的
isa
指向哪里。我們看到SJPerosn
元類的isa
打印出來(lái)是ONObject
辫秧,但是地址和NSObject
類對(duì)象地址不一樣束倍,我們?cè)诖蛴?code>NSObject的元類地址,發(fā)現(xiàn)地址一致了盟戏,也就是說(shuō)SJPerson
的元類的isa
指向NSObject
元類绪妹。SJPerson
繼承自NSObject
,那是不是說(shuō)明直接指向父類的元類呢柿究,我們?cè)賱?chuàng)建一個(gè)SJStudent
繼承SJPerson
邮旷,再試一下。
我們看到
$3
與$5
相同蝇摸,也就是說(shuō)婶肩,無(wú)論對(duì)象繼承誰(shuí),自定義對(duì)象的元類的isa
都指向根元類貌夕。繼續(xù)往下律歼,我們看下
NSObject
元類的isa
指向哪里。isa
指向它自己险毁。isa
指向鏈我們看完了,再看下繼承關(guān)系植旧,元類也有繼承嗎辱揭,我們用以下代碼測(cè)試以下:
NSObject *obj = [NSObject alloc];
Class objCls = object_getClass(obj);
Class objMetaCls = object_getClass(objCls);
Class objRootMetaCls = object_getClass(objMetaCls);
NSLog(@"NSObject實(shí)例對(duì)象: %p \n NSObject類對(duì)象: %p \n NSObject元類對(duì)象: %p \n NSObject根元類對(duì)象: %p \n ", obj, objCls, objMetaCls, objRootMetaCls);
Class pMetaCls = object_getClass([SJPerson class]);
Class pMetaSuperCls = class_getSuperclass(pMetaCls);
NSLog(@"SJPerson元類對(duì)象: %p \n SJPerson元類對(duì)象的父類: %p \n ", pMetaCls, pMetaSuperCls);
Class sMetaCls = object_getClass([SJStudent class]);
Class sMetaSuperCls = class_getSuperclass(sMetaCls);
NSLog(@"SJStudent元類對(duì)象: %p \n SJStudent元類對(duì)象的父類: %p \n ", sMetaCls, sMetaSuperCls);
Class rootMetaSuperCls = class_getSuperclass(objRootMetaCls);
NSLog(@"根元類對(duì)象的父類: %p", rootMetaSuperCls);
打印結(jié)果:
NSObject實(shí)例對(duì)象: 0x101a30050
NSObject類對(duì)象: 0x10036a140
NSObject元類對(duì)象: 0x10036a0f0
NSObject根元類對(duì)象: 0x10036a0f0
SJPerson元類對(duì)象: 0x100008530
SJPerson元類對(duì)象的父類: 0x10036a0f0
SJStudent元類對(duì)象: 0x100008580
SJStudent元類對(duì)象的父類: 0x100008530
根元類對(duì)象的父類: 0x10036a140
根據(jù)結(jié)果,我們看到SJStudent
元類的父類就是SJPerson
元類病附,SJPerson
元類的父類就是NSObject
的元類问窃,NSObject
元類的父類就是NSObject
類對(duì)象。
isa
的流程圖與繼承流程圖如下:
類的內(nèi)存數(shù)據(jù)
OC的類在C++中是objc_class
類型完沪,我們?cè)?code>objc源碼中找objc_class
的結(jié)構(gòu)域庇。
我們找到3個(gè)結(jié)構(gòu)
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
因?yàn)槲覀儺?dāng)前環(huán)境是OBJC2
,所以這個(gè)標(biāo)明OBJC2_UNAVAILABLE
覆积,我們就可以忽略不計(jì)听皿。
還有兩個(gè)分別在objc-runtime-new.h
和objc-runtime-old.h
文件中,我們想都不想直接看new
宽档。里面的結(jié)構(gòu)省略方法后:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
也就是說(shuō)尉姨,類結(jié)構(gòu)里面有isa
指針,superclass
吗冤,cache
又厉,bits
九府。isa
我們已經(jīng)分析過(guò)了,superclass
就是父類覆致,cache
我們以后再說(shuō)侄旬,我們接下來(lái)研究下bits
。
在此之前我們驗(yàn)證下superclass
:
SJPerson
的父類是NSObject
煌妈,沒(méi)有問(wèn)題儡羔。要研究
bits
,首先我們需要知道bits
在內(nèi)存存儲(chǔ)的位置璧诵,isa
占8字節(jié)汰蜘,superclass
占8字節(jié)沒(méi)什么好說(shuō)的,那么cache
占多少字節(jié)呢之宿,我們點(diǎn)進(jìn)去看下cache_t
的結(jié)構(gòu):
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
再看下explicit_atomic
實(shí)現(xiàn)
template <typename T>
struct explicit_atomic : public std::atomic<T> {
explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {}
operator T() const = delete;
T load(std::memory_order order) const noexcept {
return std::atomic<T>::load(order);
}
void store(T desired, std::memory_order order) noexcept {
std::atomic<T>::store(desired, order);
}
// Convert a normal pointer to an atomic pointer. This is a
// somewhat dodgy thing to do, but if the atomic type is lock
// free and the same size as the non-atomic type, we know the
// representations are the same, and the compiler generates good
// code.
static explicit_atomic<T> *from_pointer(T *ptr) {
static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *),
"Size of atomic must match size of original");
explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr;
ASSERT(atomic->is_lock_free());
return atomic;
}
};
T
泛型鉴扫,這個(gè)變量的大小取決于泛型T
的大小。uintptr_t
指針占8字節(jié)澈缺,第二個(gè)變量共用體坪创,mask_t
:typedef uint32_t mask_t;
,占4字節(jié)姐赡,uint16_t
占2字節(jié)莱预,結(jié)構(gòu)體占8字節(jié),_originalPreoptCache
是個(gè)*也就是指針類型也是8字節(jié)项滑,所以依沮,cache_t
一共占16字節(jié)。bits
從32字節(jié)開(kāi)始枪狂。
再看下怎么獲取bits
里面數(shù)據(jù)危喉,在源碼中可以找到data
方法。
class_rw_t *data() const {
return bits.data();
}
里面唯一能根據(jù)單詞猜意思的只有
firstSubclass
州疾,第一個(gè)子類辜限,但是SJPerson
有一個(gè)子類SJStudent
啊,為什么這里面數(shù)據(jù)是nil
呢严蓖?因?yàn)镺C中的類是懶加載的薄嫡,如果沒(méi)有用到,是不會(huì)加載這個(gè)類颗胡,那我們代碼改一下:
SJStudent *s = [SJStudent alloc];
SJPerson *p = [SJPerson alloc];
這時(shí)候我們就看到
firstSubclass
里面有值了毫深,沒(méi)毛病。但是我又想毒姨,如果SJPerson
還有別的子類哑蔫,這個(gè)值又會(huì)是什么呢?新建一個(gè)
SJHandsomeMan
繼承自SJPerson
。兩次結(jié)果不一樣闸迷,也就是說(shuō)firstSubclass
的值是該類子類中最后初始化的那個(gè)類瘩蚪。
類的屬性
目前能看懂看完了,比如我們想看類里面的屬性怎么辦呢稿黍?答:從class_rw_t
結(jié)構(gòu)里面找成員變量或者方法。
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
成員變量沒(méi)找到崩哩,方法倒是看上去有一個(gè)巡球,那我們接著調(diào)試。
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) { }
};
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
}
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
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(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
}
Element& get(uint32_t i) const {
ASSERT(i < count);
return getOrEnd(i);
}
size_t byteSize() const {
return byteSize(entsize(), count);
}
static size_t byteSize(uint32_t entsize, uint32_t count) {
return sizeof(entsize_list_tt) + count*entsize;
}
struct iterator;
}
分析下property_array_t
結(jié)構(gòu):是個(gè)數(shù)組邓嘹,數(shù)組里面每個(gè)元素都是property_list_t
類型酣栈,property_list_t
也是個(gè)數(shù)組,因?yàn)?code>entsize_list_tt里面有迭代器iterator
汹押,property_list_t
數(shù)組里面的元素是property_t
矿筝。大致結(jié)構(gòu):property_array_t = [property_list_t[property_t, property_t], property_list_t[property_t]]
。
再看下
property_t
的結(jié)構(gòu):
struct property_t {
const char *name;
const char *attributes;
};
類的方法
屬性我們看完了棚贾,再看下方法窖维,在SJPerson
里面添加兩個(gè)方法,一個(gè)實(shí)例方法妙痹,一個(gè)類方法铸史。
@interface SJPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;
@end
找方法同樣我們看到class_rw_t
里面有個(gè)methods
方法,不出意外應(yīng)該能拿到方法怯伊。
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
method_array_t
的結(jié)構(gòu)和property_array_t
一樣琳轿,都是二維數(shù)組,只不過(guò)最里面的對(duì)象換成了method_t
耿芹。
我們看到同樣的操作崭篡,怎么最后打印不出來(lái)數(shù)據(jù),是不是我們操作不對(duì)吧秕?這就要說(shuō)下
property_t
里面有兩個(gè)成員琉闪,打印的時(shí)候直接把兩個(gè)成員打印出來(lái),我們?cè)倏聪?code>method_t的結(jié)構(gòu)里面沒(méi)有這種成員砸彬,但是里面有一個(gè)結(jié)構(gòu)體:
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
這里面成員能看到方法所有內(nèi)容塘偎,是不是我們打印這個(gè)big
就行呢?測(cè)試一下:
非常完美方法都打印出來(lái)了拿霉,但是自己觀察吟秩,一共就3個(gè)方法,
say666
沒(méi)有打印出來(lái)绽淘,為什么呢涵防?對(duì)象方法存在類結(jié)構(gòu)中,那猜測(cè)類方法會(huì)不會(huì)放在元類里面呢?我們來(lái)驗(yàn)證下:
我們看到
say666
的確在元類里面壮池,也就是類方法存儲(chǔ)在元類偏瓤。這也就是為什么iOS要設(shè)計(jì)元類的原因,因?yàn)镺C中有實(shí)例方法和類方法椰憋,也就是所謂的減號(hào)方法和加號(hào)方法厅克,但是在OC底層,是沒(méi)有這個(gè)含義的橙依,也就是所有方法都轉(zhuǎn)化成同名函數(shù)证舟,如果沒(méi)有元類,我們寫(xiě)兩個(gè)方法
+(void)say666
和-(void)say666
窗骑,這兩個(gè)方法都存儲(chǔ)在類里面女责,那我們執(zhí)行方法的時(shí)候,根據(jù)函數(shù)名创译,就無(wú)法確定到底是執(zhí)行實(shí)例方法還是類方法了抵知,所以設(shè)計(jì)元類把兩種方法分離開(kāi)。
類的成員變量
class_ro_t演變
在WWDC視頻中软族,我們可以詳細(xì)看到class_ro_t
演變優(yōu)化過(guò)程刷喜,也知道成員變量存在class_ro_t
中。在SJPerson
中添加一個(gè)成員變量hobby
立砸。
@interface SJPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;
@end
class_ro_t
怎么找呢吱肌,同樣在源碼中找方法
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
調(diào)試驗(yàn)證:
TypeEncoding
上面打印方法里面types
一堆符號(hào)就是iOS中TypeEncoding
編碼,每個(gè)符號(hào)的含義可以參照# Type Encodings
仰禽。
在解釋下方法中types
含義:
第一個(gè)字母:返回值類型氮墨。第一個(gè)數(shù)字:這個(gè)方法所有參數(shù)占內(nèi)存大小。
第二個(gè)字母:第一個(gè)參數(shù)的類型吐葵。第二個(gè)數(shù)字:第一個(gè)參數(shù)從第幾號(hào)內(nèi)存開(kāi)始规揪。
第三個(gè)字母:第二個(gè)參數(shù)的類型。第三個(gè)數(shù)字:第二個(gè)參數(shù)從第幾號(hào)內(nèi)存開(kāi)始温峭。
.......以此類推猛铅。
總結(jié):類本質(zhì)就是結(jié)構(gòu)體,里面有isa
凤藏,superclass
奸忽,cache
,bits
揖庄。isa
利用位域存著類相關(guān)信息栗菜,superclass
存著父類,cache
存著方法緩存蹄梢,bits
里面存著屬性列表疙筹、實(shí)例變量列表、方法列表、協(xié)議列表等而咆。