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与类关联的原理

相关文章
相关标签/搜索