從探索isa
的初始化開始
在OC底層原理(一).alloc實際調(diào)用流程分析內(nèi)容最后的流程圖中,_class_createInstanceFromZone
,我們分為三步:
- 1、
size = cls->instanceSize(extraBytes);
獲取對象需要分配的內(nèi)存大小 - 2、
obj = (id)calloc(1, size);
如何申請內(nèi)存 - 3、
obj->initInstanceIsa(cls, hasCxxDtor);
初始化isa
在上一篇中我們分析了獲取需要分配的大小,以及具體如何分配內(nèi)存,也就是第1步和第2步。本篇我們繼續(xù)對第三步進行探索,也就是isa的創(chuàng)建以及初始化。
分析initInstanceIsa
方法的實現(xiàn)
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
initInstanceIsa
內(nèi)部調(diào)用了initIsa
,并且傳入第一個參數(shù)為cls
(類的地址),第二個參數(shù)為true
,第三個為hasCxxDtor
,我們看看initIsa
實現(xiàn)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
//如果是純指針的isa,那么isa僅僅用于存放類地址
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
//分配一個isa_t變量newisa,并把所有位清零
isa_t newisa(0);
/*
是否支持索引,默認蘋果平臺都不支持的
SUPPORT_INDEXED_ISA為1表示類表的索引
*/
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
/*
0x001d 8000 0000 0001ULL
給isa一個初始值0x001d800000000001ULL
在這個初始值中,已經(jīng)設(shè)置了nonpointer和magic的值
*/
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
/*
將類的地址信息右移三位,放入預(yù)設(shè)好的類的存儲位置
*/
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在initIsa
中,做了isa
位域中四個初始化,分別是給magic
、nonpointer
、has_cxx_dtor
,shiftcls
賦了值。
大致流程如下:
在OC底層原理(一).alloc實際調(diào)用流程分析內(nèi)容中,我們知道initIsa
來自于最初的alloc調(diào)用
。也就是說,一個類調(diào)用alloc
,除了分配內(nèi)存以及內(nèi)存對齊外,它還對內(nèi)部的成員變量isa
作了初始化,每個對象一定有一個叫isa
的成員變量,那么,isa
到底是什么呢?
isa
是什么
使用clang重寫分析
我們在main.m
中創(chuàng)建一個繼承自NSObject
的類LWPerson
,代碼如下:
#import <Foundation/Foundation.h>
@interface LWPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) short age;
@property (nonatomic,assign) BOOL isMan;
@end
@implementation LWPerson
+ (void)LWPersonClassMethod{
NSLog(@"%s",__func__);
}
- (int)LWPersonInstanceMethod{
NSLog(@"%s",__func__);
NSArray *array = @[@1,@2,@3];
return [array[1] intValue];
}
@end
int main(int argc, const char * argv[]) {
LWPerson *person = [LWPerson alloc];
person.name = @"Jobs";
person.age = 5;
person.isMan = YES;
[person LWPersonInstanceMethod];
[LWPerson LWPersonClassMethod];
return 0;
}
我們在終端使用如下clang
命令將main.m
重寫為C、C++實現(xiàn)
clang -rewrite-objc main.m -o main.cpp
執(zhí)行命令后,在目錄中生成一個main.cpp
文件,我們打開它,搜索LWPerson
,我們刪除和main
中無關(guān)的數(shù)據(jù)后,得到最后的代碼如下:
#ifndef _REWRITER_typedef_LWPerson
#define _REWRITER_typedef_LWPerson
typedef struct objc_object LWPerson;
typedef struct {} _objc_exc_LWPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_isMan;
struct LWPerson_IMPL {
//這就是isa
struct NSObject_IMPL NSObject_IVARS;
BOOL _isMan;
short _age;
NSString *_name;
};
// @property (nonatomic,copy) NSString *name;
// @property (nonatomic,assign) short age;
// @property (nonatomic,assign) BOOL isMan;
/* @end */
// @implementation LWPerson
//類方法重寫
static void _C_LWPerson_LWPersonClassMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_0,__func__);
}
//實例方法重寫
static int _I_LWPerson_LWPersonInstanceMethod(LWPerson * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_1,__func__);
NSArray *array = ((NSArray *(*)(Class, SEL, ObjectType _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);
return ((int (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("objectAtIndexedSubscript:"), (NSUInteger)1), sel_registerName("intValue"));
}
//name的getter方法
static NSString * _I_LWPerson_name(LWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LWPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//name的setter方法
static void _I_LWPerson_setName_(LWPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LWPerson, _name), (id)name, 0, 1); }
//age的getter方法
static short _I_LWPerson_age(LWPerson * self, SEL _cmd) { return (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)); }
//age的setter方法
static void _I_LWPerson_setAge_(LWPerson * self, SEL _cmd, short age) { (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)) = age; }
//isMan的setter方法
static BOOL _I_LWPerson_isMan(LWPerson * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)); }
//isMan的setter方法
static void _I_LWPerson_setIsMan_(LWPerson * self, SEL _cmd, BOOL isMan) { (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)) = isMan; }
// @end
int main(int argc, const char * argv[]) {
//alloc方法調(diào)用
LWPerson *person = ((LWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("alloc"));
//調(diào)用setName:
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_2);
//調(diào)用setAge:
((void (*)(id, SEL, short))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), (short)5);
//調(diào)用setIsMan:
((void (*)(id, SEL, BOOL))(void *)objc_msgSend)((id)person, sel_registerName("setIsMan:"), ((bool)1));
//調(diào)用實例方法LWPersonInstanceMethod
((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("LWPersonInstanceMethod"));
//調(diào)用類方法LWPersonClassMethod
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("LWPersonClassMethod"));
return 0;
}
從上面的代碼可以看出以下幾點:
- 1.OC類和對象底層都是結(jié)構(gòu)體
- 2.每個對象都有一個默認的
isa
變量,它由NSObject
繼承而來 - 3.屬性的本質(zhì),就是
實例變量+setter+getter
- 4.實例變量值的獲取,就是對象首地址+地址偏移
- 5.屬性值的設(shè)置,對于簡單類型值,直接通過地址偏移設(shè)置,其他底層都是調(diào)用了
objc_setProperty
函數(shù)。 - 6.方法的調(diào)用,都是使用
objc_msgSend
發(fā)送消息
OC類和對象底層都是結(jié)構(gòu)體
我們打開objc781
源碼,可以在objc-private.h
中找到struct objc_object
的定義,如下所示:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
......
}
我們看到struct objc_object
中,有一個成員變量isa
,另外有三個獲取isa
的成員方法:
-
Class ISA()
方法用于非taggedPointer對象
獲取isa
-
Class rawISA();
方法用于非taggedPointer對象
與非nonpointer對象
獲取isa
-
Class getIsa();
方法用于taggedPointer對象
獲取isa
另外,它還提供了直接獲取isa
中bits
成員的方法uintptr_t isaBits() const
,用const
在函數(shù)末尾修飾,代表一個常成員函數(shù),僅能讀取。
此外,我們可以看到我們常用于指向?qū)ο箢愋偷?code>id類型,它是struct objc_object *
的別名。
/// A pointer to an instance of a class.
typedef struct objc_object *id;
在objc-runtime-new.h
文件中,我們可以找到struct objc_class
的定義,如下所示:
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
class_rw_t *data() const {
return bits.data();
}
}
從上面可以看出,struct objc_class
繼承于objc_object
,它有四個成員:
-
isa
,來自于它的父類,isa_t
類型 -
superclass
,它是一個指向父類的指針 -
cache
,cache_t
類型,存儲緩存的結(jié)構(gòu)體 -
bits
,class_data_bits_t
類型的結(jié)構(gòu)體,存儲了類中方法、協(xié)議、成員列表等的信息。
同樣,我們可以找到關(guān)于Class
的定義,它是struct objc_class *
的別名
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
對象和類結(jié)構(gòu)如下圖
isa結(jié)構(gòu)
我們可以找到isa
的定義,如下圖所示:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
從代碼我們可以看出,isa_t
是一個聯(lián)合體,它有兩個成員,分別為Class cls
和uintptr_t bits
。
我們知道,所謂聯(lián)合體,也叫共用體,它的所有成員變量共用一段內(nèi)存,它的大小等于最大的成員變量的大小,所以,我們可以得出,isa_t
內(nèi)存所占大小為8字節(jié),64位
。
我們再看定義中更重要的一段定義ISA_BITFIELD;
,它在isa.h
中,如下所示
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
我們先不討論內(nèi)部的執(zhí)行結(jié)果,我們先談?wù)勱P(guān)于聯(lián)合體中定義struct
,而struct
中定義一連串的成員,這個是什么呢,在C語言中,我們將它叫做位域。
位域
位域,也叫位段,它是一種特殊的結(jié)構(gòu)體類型,其所有成員的長度
均是以二進制位
為單位定義的,結(jié)構(gòu)體的成員被稱為位段
,位段定義的一般形式如下:
struct 結(jié)構(gòu)名
{
類型 變量名1:長度
類型 變量名2:長度
……
類型 變量名n:長度
}
位段定義類型必須是int
、unsigned
或signed
中的一種,也就是必須為整形
位段的特性如下:
- 從低位到高位排列
- 一個位段必須存在一個存儲單元中,不能跨兩個存儲單元,如果本單元不夠容納某位段,則從下一個單元開始存儲該位段
- 可以用
%d
、%x
、%u
、%o
等格式字符,以整數(shù)形式輸出位段 - 在數(shù)值表達式中引用位段時,系統(tǒng)自動將位段轉(zhuǎn)換為整形數(shù)。
isa中的位域意義
isa
位段定義的意義如下圖所示
在不同平臺下,isa
的內(nèi)部存儲區(qū)域如下圖
使用lldb
對驗證isa
存儲類地址
在之前,我們提到struct objc_obejct
的成員函數(shù)ISA()
是獲取nonpointer isa
的類地址的方法,我們來看看它的實現(xiàn)
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
核心代碼為(Class)(isa.bits & ISA_MASK);
,這一步將isa
的數(shù)據(jù)與一個ISA_MASK
進行按位與,就得到了類的地址信息。
define ISA_MASK 0x0000000ffffffff8ULL
我們使用一開始例子,打斷點如下
然后斷點執(zhí)行到這一步時候,我們使用如下命令得到結(jié)果
(lldb) x/4gx person
0x10054aa30: 0x001d8001000032f5 0x0000000000050001
0x10054aa40: 0x0000000100002040 0x0000000000000000
(lldb) po 0x001d8001000032f5 & 0x0000000ffffffff8ULL
LWPerson
這樣就驗證了isa
是存儲類信息的。
objc_setProperty
源碼分析
objc_setProperty
源碼如下:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
//如果偏移量為0,就是設(shè)置isa,也就是設(shè)置類地址信息
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
//新值retain
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
//進行賦值
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
//進行賦值
*slot = newValue;
slotlock.unlock();
}
//舊值release
objc_release(oldValue);
}
從源碼中我們可以看到,retain
、release
的操作在底層已經(jīng)幫我們完成了,所有涉及引用計數(shù)的屬性
都要調(diào)用到objc_setProperty
方法,這是一種適配器模式的思想,它大大的節(jié)約了我們的代碼量。