上篇文章提到,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,与它的类关联在了一块儿.