前言
相關文章:
iOS底層探索一(底層探索方法)
iOS底層探索九(方法的本質下objc_msgSend慢速及方法轉發初探)
相關代碼:
前幾篇文章對alloc方法進行了初步探究,
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
/**
fastpath(x)表示x很可能不爲0,希望編譯器進行優化;
slowpath(x)表示x很可能爲0,希望編譯器進行優化——這裏表示cls大概率是有值的,編譯器可以不用每次都讀取 return nil 指令
*/
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
/**
實際意義是hasCustomAllocWithZone——這裏表示有沒有alloc / allocWithZone的實現(只有不是繼承NSObject/NSProxy的類才爲true)
*/
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
/**
內部調用了bits.canAllocFast默認爲false,可以自行點擊進入查看
*/
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
//底層進行開闢控件
/*
其中calloc函數意思是開闢一個內存空間,cls->bits.fastInstanceSize() 意思是開闢一個cls類的內存空間的大小,前面__count意思是倍數,其中cls->bits.fastInstanceSize()大小是遵循內存對齊原則開闢內存的
*/
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
今天我們繼續探索alloc方法中的initInstanceIsa方法進行繼續探索
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
這個裏面的assert方法爲斷言方法(這裏可以不用理會想要了解可以點擊查看)
這裏進入initIsa方法
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#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
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;
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;
}
}
這裏我們查看一下isa的結構
//聯合體
union isa_t {
isa_t() { } //初始化方法
isa_t(uintptr_t value) : bits(value) { }
Class cls; //綁定類
uintptr_t bits; //typedef unsigned long長整形8字節
#if defined(ISA_BITFIELD)
struct {
// 這裏使用宏定義的原因是因爲要根據系統架構進行區分的
ISA_BITFIELD; // defined in isa.h 位域
};
#endif
};
這個結構爲union(聯合體),查看下位域的聲明;
# 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
/**
nonpointer :1
表示是否對isa指針開啓指針優化;0代表純isa指針,1代表不止是類對象指針,還包含了類信息、對象的引用計數等;
has_cxx_dtor:1
該對象是否有C++或者Objc的析構器,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象;
hasCxxDtor:1
該對象是否有C++或者Objc的析構器,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象;
shiftcls:33
存儲類指針的值。開啓指針優化的情況下,在arm64架構中有33位用來存儲類指針;
magic:6
用於調試器判斷當前對象是真的對象還是沒有初始化的空間;
weakly_referenced :1
標誌對象是否被指向或者曾經指向一個ARC的弱變量,沒有弱引用的對象可以更快釋放;
deallocating :1
標誌對象是否正在釋放內存;
has_sidetable_rc :1
當對象引用計數大於10時,則需要借用該變量存儲進位
extra_rc :19
當表示該對象的引用計數值,實際上是引用計數值減1,例如,如果對象的引用計數爲10,那麼extra_rc爲9.如果引用計數大於10,則需要使用上面提到的has_sidetable_rc。
**/
上述中我們可以看到 isa_t
類型是一個 union
聯合體。ISA_BITFIELD
是位域。
聯合體(union)
當多個數據需要共享內存或者多個數據每次只取其一時,可以利用聯合體(union)。在C Programming Language 一書中對於聯合體是這麼描述的:
1)聯合體是一個結構;
2)它的所有成員相對於基地址的偏移量都爲0;
3)此結構空間要大到足夠容納最"寬"的成員;
4)其對齊方式要適合其中所有的成員;
位域
有些信息在存儲時,並不需要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。爲了節省存儲空間,並使處理簡便。
這麼描述可能有點蒼白了,我們可以使用一個demo來進行描述下,這裏新建一個Object-C工程,新建一個類亞瑟(XZArthur)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XZArthur : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@end
@implementation XZArthur
@end
- (void)viewDidLoad {
[super viewDidLoad];
XZArthur *arthur = [[XZArthur alloc]init];
arthur.front = YES;
arthur.back = YES;
arthur.right = YES;
arthur.left = YES;
NSLog(@"聯合體--位域");
}
如果是這樣的話,我們可以在NSLog處打斷點,LLDB調試看下內存情況
01 01 01 01 00 00 00 00 這裏至少會使用4個字節存這個4個屬性
這裏我們就對屬性對空間來說就有些浪費
我們可以使用char類型進行表示
char 0000 0001 (這裏是用二進制表示的)
我們可以使用第一位爲1標示向前,第二位爲1標示向後,第三位爲0標示向左,第四位爲1標示向右,這樣我們就只用了4個位置(4個位置二進制中標示爲8),不到一個字節,這樣就大大的節省了內存空間
由此我們就發現如果直接使用屬性會很浪費空間,所以我們這裏就可以使用聯合體來標示這幾個位置
即爲,我們這裏只用一個char類型,讓其中的屬性共用內存,共用內存大小爲,聯合體中最大的那個內存
我沒在XZArthur類中進行修改
#import "XZArthur.h"
#define XZDirectionFrontMask (1 << 0) //1左移0位爲1
#define XZDirectionBackMask (1 << 1) //1往左移1位爲2
#define XZDirectionLeftMask (1 << 2) //1往左移2位爲4
#define XZDirectionRightMask (1 << 3) //1往左移2位爲8
@interface XZArthur(){
union {
// 聯合體
char bits;
} _direction;
}
@end
- (instancetype)init
{
self = [super init];
if (self) {
// 這裏就標示爲二進制的全部爲0
_direction.bits = 0b0000000000;
}
return self;
}
/**
0000 0000 原始值
0000 0001 傳進來值
| 進行或運算
0000 0001 計算結果值爲1
0000 0000 原始值
1111 1110 XZDirectionFrontMask取非值
& 進行與運算算
0000 0000 計算結 果值爲0
*/
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= XZDirectionFrontMask;
} else {
_direction.bits &= ~XZDirectionFrontMask;
}
}
- (BOOL)isFront{
return !!(_direction.bits & XZDirectionFrontMask);
}
@end
這樣我就就使用聯合體對4個屬性進行了表示,這裏我們就已經使用聯合體對屬性進行替換了,已經節省內存了,那位域是用來幹什麼呢?
我們現在是已經使用聯合體表示了這幾功能,那我們怎麼讓別人理解起來更加方便呢,或者說,這裏面屬性如果增加了呢,就需要使用位域來進行進一步的描述了,我們可以將屬性進行擴增,也可以很清晰的看出來屬性的意義,包括所佔大小;這裏所佔內存大小可直接從位域中可以看出,也可以根據char類型進行看出,char爲1字節
#define XZDirectionFrontMask (1 << 0) //1左移0位爲1
#define XZDirectionBackMask (1 << 1) //1往左移1位爲2
#define XZDirectionLeftMask (1 << 2) //1往左移2位爲4
#define XZDirectionRightMask (1 << 3) //1往左移2位爲8
#define XZDirectionBigRecruitMask (1 << 4) //1往左移7位
@interface XZArthur(){
union {
// 聯合體
char bits;
// 位域
struct {
char front : 1; //佔一位
char back : 1; //佔1位
char left : 1; //佔1位
char right : 1; //佔1位
char bigRecruit : 8; //佔8位
};
} _direction;
}
@end
@implementation XZArthur
- (instancetype)init
{
self = [super init];
if (self) {
// 這裏就標示爲二進制的全部爲0
_direction.bits = 0b0000000000;
}
return self;
}
/**
0000 0000 原始值
0000 0001 傳進來值
| 進行或運算
0000 0001 計算結果值爲1
0000 0000 原始值
1111 1110 XZDirectionFrontMask取非值
& 進行與運算算
0000 0000 計算結 果值爲0
*/
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= XZDirectionFrontMask;
} else {
_direction.bits &= ~XZDirectionFrontMask;
}
}
- (BOOL)isFront{
return !!(_direction.bits & XZDirectionFrontMask);
}
- (void)setBack:(BOOL)isBack {
_direction.back = isBack;
}
- (BOOL)isBack{
return _direction.back;
}
- (void)setRight:(BOOL)right
{
_direction.right = right;
}
- (BOOL)isRight
{
return _direction.right;
}
- (void)setLeft:(BOOL)left
{
_direction.left = left;
}
- (BOOL)isLeft
{
return _direction.left;
}
@end
從上述代碼中我們可以看出,給bit中賦值的時候可以使用2種方式,1.直接對bit進行位運算賦值,2.直接使用位域中的屬性進行賦值都可以;
我們可以在lldb中進行驗證一下:
我們可以看到只是用0f,就表示了4個屬性的值;
這就是我對聯合體和位域的解釋!
然後我們再回到isa的源碼中
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
//isTaggedPointer這個涉及內存管理,後續在進行詳細解釋
assert(!isTaggedPointer());
if (!nonpointer) {
// isa對類的綁定
// 使用位域屬性進行賦值
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
// # define ISA_INDEX_MAGIC_VALUE 0x001C0001 直接使用bit進行賦值
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
// # define ISA_MAGIC_VALUE 0x000001a000000001ULL 直接使用bit進行賦值
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;
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;
}
}
Struct
與 Union
主要有以下區別:
- 1.
struct
和union
都是由多個不同的數據類型成員組成, 但在任何同一時刻,union
中只存放了一個被選中的成員, 而struct
的所有成員都存在。在struct
中,各成員都佔有自己的內存空間,它們是同時存在的。一個struct
變量的總長度等於所有成員長度之和。在Union
中,所有成員不能同時佔用它的內存空間,它們不能同時存在。Union
變量的長度等於最長的成員的長度。 - 2. 對於
union
的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於struct
的不同成員賦值是互不影響的。
總結
以上就是關於alloc探索,到initInstanceIsa的方法內部實現機制,如果有錯誤的地方還請指正,大家一起討論,開發水平一般,還望大家體諒,歡迎大家點贊,關注我的CSDN,我會定期做一些技術分享!
寫給自己:
有人幫你,是你的幸運;無人幫你,是公正的命運。沒有人該爲你做什麼,因爲生命是你自己的,你得爲自己負責,繼續努力,未完待續...