OC底層原理03-對象的本質

對象的本質

上篇文章提到,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方法在下層不會相互不影響,作到上下層接口的隔離。


isa與對象的關聯

OC底層原理01:alloc方法底層探索中,alloc的三個重要步驟,

  1. 由系統計算出開闢的內存空間大小
  2. 申請開闢得出的大小內存空間,返回isa指針
  3. 將開闢的內存空間與要建立的對象進行關聯

今天咱們再深刻了解下第三步,開闢空間獲得的isa指針,是如何與類對象進行關聯的?

union聯合體

首先認識一個新的數據類型--union聯合體(共用體)

  • 什麼是聯合體 聯合體也是C語言中的一種數據類型,它與結構體的不一樣在於:
  1. 聯合體中全部的成員共用一塊內存,每次只能使用其中一個成員
  2. 聯合體中各變量是互斥的,對某一個成員賦值,也會覆蓋其餘成員的值
  3. 順序從低地址開始存放

它的優勢是全部成員共用一段內存,對內存的使用更加精細靈活,同時也節省了內存空間,可是一樣由於內存空間過小,致使它的包容性弱,沒有可拓展的能力。

案例: 咱們定義一個類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; 複製代碼

isa指針

經過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

  • nonpointer:表示是否對isa指針開啓指針優化。0:純isa指針;1:isa中包含了類對象地址、類信息、對象的引用計數等
  • has_assoc:關聯對象標誌位,0沒有,1存在
  • has_cxx_dtor:該對象是否有C++或objc的析構器,若是有析構函數,則能夠作析構邏輯,若是沒有,則能夠更快的釋放對象。(objc中析構函數爲dealloc)
  • shiftcls:存儲類指針的值,對象繼承自類,類也是有本身的isa,這裏shiftcls中,保存的就是類指針的值
  • magic:⽤於調試器判斷當前對象是真的對象仍是沒有初始化的空間
  • weakly_referenced:志對象是否被指向或者曾經指向⼀個ARC的弱變量,沒有弱引⽤的對象能夠更快釋放。
  • deallocating:標誌對象是否正在釋放內存
  • has_sidetable_rc:當對象引⽤計數⼤於10時,則須要借⽤該變量存儲進位
  • extra_rc:當表示該對象的引⽤計數值,其實是引⽤計數值減1, 例如,若是對象的引⽤計數爲10,那麼extra_rc爲9。若是引⽤計數⼤於10,則須要使⽤到下⾯的has_sidetable_rc。

他們在內存中的存儲爲:

isa指針與對象關聯的過程

  1. isa位域的初始化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;初始化的結果:

  1. nonpointer賦值爲1
  2. magic賦值爲59

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_MASK驗證

利用isa_t位域中的ISA_MASK,& 與上獲得obj結果的isa.即當shiftcls關聯結束後,會回到obj->initInstanceIsa(cls, hasCxxDtor);

這裏可知,alloc方法的底層實現中,申請由系統開闢的內存空間獲得的isa,與它的類關聯在了一塊兒.

參考推薦

isa與類關聯的原理

相關文章
相關標籤/搜索