上篇文章提到,OC中對象的本質是結構體,那麼這個結論咱們要如何去驗證呢?這時候咱們須要用到Clang。linux
Clang是一個由Apple主導編寫,基於LVVM的C、C++、OC語言的輕量級編譯器設計模式
@interface LGPerson : NSObject
@end @implementation LGPerson @end int main(int argc, const char * argv[]) { @autoreleasepool { } return NSApplicationMain(argc, argv); } 複製代碼
使用Clang編譯上邊的代碼: clang -rewrite-objc main.m -o main.cpp
,而後打開main.cpp文件,搜到LGPerson能夠看到markdown
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson typedef struct objc_object LGPerson; typedef struct {} _objc_exc_LGPerson; #endif struct LGPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; }; 複製代碼
這裏就能夠看到,Clang編譯後,LGPerson就被編譯成了了LGPerson_IMPL的結構體。並且其中初始化就有一個struct NSObject_IMPL NSObject_IVARS
的數據,它代表了一種繼承關係(雖然嚴格來講結構體沒有繼承,這樣作其實是結構體類型的強制轉換,linux內核源碼中有不少這樣的方法。父結構體變量必須放在子結構體的首位)。咱們再找到LGPerson_IMPL結構體的‘父類’ide
struct NSObject_IMPL {
Class isa; }; 複製代碼
咱們看到NSObject_IMPL
中只有一個isa指針。isa咱們下邊作討論,看到這裏就驗證了,對象在編譯運行後,確實是以結構體的類型存在的。函數
類在實際使用過程當中,確定會包括一些屬性,那麼在LGPerson中添加一個屬性name,再來編譯看下結果。oop
@interface LGPerson : NSObject
@property(nonatomic, copy)NSString *name; @end @implementation LGPerson @end 複製代碼
編譯後咱們再找到LGPerson看有沒有什麼變化:post
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson typedef struct objc_object LGPerson; typedef struct {} _objc_exc_LGPerson; #endif extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name; struct LGPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *_name; }; // @property(nonatomic, copy)NSString *name; /* @end */ // @implementation LGPerson static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); } // @end 複製代碼
這裏看到了兩處變化:優化
結構體LGPerson_IMPL多了一條數據:NSString *_name;
ui
_I_LGPerson_name
和_I_LGPerson_setName_
分別是屬性name的get和set方法atom
主要來探索下set的底層實現過程:
在以前配置的源碼代碼中,查找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) { 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; newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); } 複製代碼
其中咱們能夠看到,屬性的set方法,在底層實現中只作了兩件事舊值release:objc_release(oldValue); 新值retain:newValue = objc_retain(newValue);
objc_setProperty
objc_setProperty方法是採用了適配器設計模式
,這種設計模式主要應用於「但願複用,但接口與複用環境要求不一致的狀況」。在實際使用過程當中,屬性set方法的調用會有不少種狀況(setName,setAge),而它們都會去作相同的事情舊值release,新值retain
,可是屬性set方法的調用也是各有不一樣,若是直接調用底層進行"舊值release,新值retain",會產生很是多中間臨時變量,類庫遷移複雜等問題,因此應該將這一過程抽取剝離出來進行共用,。
objc_setProperty方法做爲了一箇中間層進行包裝處理,本質能夠認爲是個接口
,供上層的set方法調用,經過接口參數:SEL _cmd, id newValue
等,使得不一樣的set方法在下層不會相互不影響,作到上下層接口的隔離。
在OC底層原理01:alloc方法底層探索中,alloc的三個重要步驟,
今天咱們再深刻了解下第三步,開闢空間獲得的isa指針,是如何與類對象進行關聯的?
首先認識一個新的數據類型--union聯合體(共用體)
它的優勢是全部成員共用一段內存,對內存的使用更加精細靈活,同時也節省了內存空間,可是一樣由於內存空間過小,致使它的包容性弱,沒有可拓展的能力。
案例: 咱們定義一個類Car,它有四個屬性表示四個方向
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back; @property (nonatomic, assign) BOOL left; @property (nonatomic, assign) BOOL right; 複製代碼
那麼在建立這個類對象時,會爲每個BOOL類型的屬性分配一個字節的內存空間,可是BOOL類型使用"0或1"就能夠表示清楚,四個字節會存在內存浪費。使用union聯合體,表示這四個數據只須要四位,也就是一個字節足夠了。
union {
char bits; // 位域 struct { // 1表明了屬性佔據的位數,從低位向高位排序 char front : 1; char back : 1; char left : 1; char right : 1; }; } _direction; 複製代碼
經過alloc第三步initInstanceIsa
的源碼咱們能夠看到isa與對象關聯的底層實現。
源碼內容:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(!cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { ASSERT(!isTaggedPointer()); if (!nonpointer) { isa = isa_t((uintptr_t)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; // bits數據的初始化 // 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; } } 複製代碼
initIsa方法中傳入的nonpointer爲true,因此initIsa調用到了else裏。其中能夠看到,isa是由isa_t定義的。
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就是一個聯合體類型的數據,其中位域部分ISA_BITFIELD
:
shiftcls
:存儲類指針的值,對象繼承自類,類也是有本身的isa,這裏shiftcls中,保存的就是類指針的值他們在內存中的存儲爲:
newisa.bits = ISA_MAGIC_VALUE;
這裏咱們也能夠看到註釋裏的提示:
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE 複製代碼
ISA_MAGIC_VALUE 對應的是MacOS中的0x001d800000000001ULL
,下圖是初始化的先後對比
其中由於聯合體中,cls和bits是互斥關係,單獨爲cls賦值時,不會爲bits賦值,可是在對bits賦值中,也會對cls的值進行追加.
上圖分析下cls賦一個默認的初值0x001d800000000001.
因此說,newisa.bits = ISA_MAGIC_VALUE;
初始化的結果:
2.newisa.has_cxx_dtor = hasCxxDtor;
由於咱們沒有對Person類中添加析構函數dealloc
,因此這裏會傳一個flase
3.newisa.shiftcls = (uintptr_t)cls >> 3;
這句代碼,就是將cls與對象關聯起來的關鍵部分,在介紹shiftcls咱們已經提到了,這裏保存的是類的指針值,類一樣也有本身的isa,因此類的信息一樣保存在本身的shiftcls
中,(uintptr_t)cls將cls強轉後的數據,右移3位,摒棄掉類isa中的nonpointer、has_assoc、has_cxx_dtor
前三個數據後,賦值給對象isa的shiftcls.
利用isa_t位域中的ISA_MASK
,& 與上獲得obj結果的isa
.即當shiftcls關聯結束後,會回到obj->initInstanceIsa(cls, hasCxxDtor);
這裏可知,alloc方法的底層實現中,申請由系統開闢的內存空間獲得的isa,與它的類關聯在了一塊兒.