面向对象的编程语言中有经典的话:万物皆对象。objective-c
就是一门面向对象的语言,那么在oc
的编程中就离不开对象的建立,下面分析oc对象的内存结构及其建立的过程c++
首先分析对象的内存结构,其实咱们知道oc
的对象指针其实就是结构体指针,也就是说oc的对象转成c++代码后其实就是一个结构体。定义一个简单的类代码以下:objective-c
@interface Person : NSObject
@property (nonatomic,assign) NSUInteger age;
@property (nonatomic,copy) NSString *name;
-(void)say;
@end
@implementation Person
-(void)say{
NSLog(@"person say");
}
@end
复制代码
利用clang
编译器吧这个类转成c++
代码后能够发现对应的Person
类其实就是一个结构体,代码以下:编程
struct Person_IMPL {
Class isa;
NSUInteger _age;
NSString * _Nonnull _name;
};
复制代码
先忽略say
方法的存在(方法跟结构体的isa
指针相关,稍后再分析),能够看出来 struct Person_IMPL
的结构体定义跟Person
类的属性定义是吻合的。因此,其实咱们日常 建立对象其实就是给类的对应的结构体在堆上开辟一块合适的空间,并返回这块空间的指针给用户,这个指针就是咱们平时操做对象(包括对象方法的调用,对象属相的更改)的指针,只是oc把这个结构体指针包装成一个oc类型的指针(Person *
)而已。bash
接下来利用指针强转把oc对象相似指针转换为c语言结构体指针来验证一下oc类其实底层就是c语言的结构体。数据结构
运行代码:架构
Person *p = [[Person alloc] init];
p.age = 15;
p.name = @"Mike";
//指针强转
struct Person_IMPL *sp = (__bridge struct Person_IMPL *)(p);
NSLog(@"经过oc对象类型指针转为结构体指针后访问的结构体Person_IMPL值 : _age = %zd , _name = %@" , sp->_age , sp->_name);
复制代码
打印结果:编程语言
经过oc对象类型指针转为结构体指针后访问的结构体Person_IMPL值 : _age = 15 , _name = Mike
复制代码
验证结果符合预期。ide
既然oc对象的底层数据结构是c语言的结构体,那么对象的属性或成员的存取其实跟c语言结构体的成员变量的存取原理实际上是同样的:经过指针的偏移操做内存的数据:用一个通俗一点的公式能够表达为 propertyValue(对象的成员变量值) = objcPointer(对象指针) + offset(偏移量)。 对象内存及其指针的关系用下图表示: 函数
咱们能够用代码验证经过指针的操做可否访问到oc对象的内存数据 代码以下:源码分析
Person *p = [[Person alloc] init];
p.age = 15;
p.name = @"Mike";
struct Person_IMPL *sp = (__bridge struct Person_IMPL *)(p);
//经过指针的偏移操做 , 或的结构体内部的成员地址,也是oc对象指针(Person * p)的对象成员地址。
long long ageAdress = (long long)((char *)sp+8);
long long nameAdress = (long long)((char *)sp+16);
//打断点经过lldb指令调试验证
NSLog(@"===");
复制代码
在NSLog处打断点后,经过lldb打印相关指令查看到p或sp指针向上偏移8字节能够获取到时成员变量age的值,如图所示
p或sp指针向上偏移8字节能够获取到时成员变量name
研究类的初始化过程确定是经过objc
官方源码分析 , 本人用的是objc4-750
的版本进行分析。
经过咱们建立对象都是调用+alloc
方法进行建立的,此方法调用到了下面两个方法,我把该方法的简化以下
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
//此函数调用到下面的 _class_createInstanceFromZone
id obj = class_createInstance(cls, 0);
return obj;
}
//class_createInstance 调用到此方法
static __attribute__((always_inline)) id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
bool hasCxxDtor = cls->hasCxxDtor();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
复制代码
上面函数的做用有两个
struct objc_object *
就是咱们的id
指针,这也反映了NSObjcet *
对应struct objc_object *
,下面会分析)在分析初始化isa指针前先弄清楚 oc对象指针(NSObjcet * , id
)对应在objc
源码中那些结构体的关系可能会容易理解一点
// NSObject 定义
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
//struct objc_object 定义
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
//struct objc_class 定义
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
//id 指针的定义
typedef struct objc_object *id;
//Class 定义
typedef struct objc_class *Class;
复制代码
经过上面的代码能够发现,咱们日常使用的 NSObject *
或者 id
指针其实底层是 struct objc_object
指针,而日常使用的Class类型其实底层就是struct objc_class
指针,并且还能够发现Class
类型(objc_class *
)实际上是继承自objc_class
,就是说咱们Class
类型其实也是一个对象。
在oc中对象(object),对象的父类(SuperClass),对象的类(Class),对象的元类(MetaClass)都是经过指针来进行关联的。 SuperClass
对应的是objc_class
的superclass
指针 , Class
对应的是objc_class
的isa
指针(OBJC2中的isa指针已经不是直接指向Class的地址了,而是用来位域的技术存储了Class的地址外还有其余一些额外的信息)。
首先咱们开看下isa结构的定义(objc-private.h + isa.h
) 这里紧列举__x86__64__
架构的状况进行分析
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//表明是否有开启指针isa指针优化(位域技术存储更多信息)
uintptr_t has_assoc : 1;//是否有设置关联对象
uintptr_t has_cxx_dtor : 1;//是否有c++析构函数
uintptr_t shiftcls : 44;//存储Class或MetaClass的内存地址信息
uintptr_t magic : 6;//验证对象是否初始化完成
uintptr_t weakly_referenced : 1;//是否有被弱引用指针指向
uintptr_t deallocating : 1;//对象是否正在释放
uintptr_t has_sidetable_rc : 1;//extra_rc没法存储过大的数值时,次标志位为1,把extra_rc部分的值存储到一个全局的SideTable中
uintptr_t extra_rc : 8//存储引用计数存储 (引用值 = 存储值 - 1)
};
};
复制代码
能够看isa_t
实际上是一个共用体union : 一个8字节指针(64位) = cls = bits = 使用位域的struct
了解了isa_t
的结构后咱们看下struct objc_object
初始化isa的方法实现
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) { // Taggedpointer
isa.cls = cls;
} else { // 非Taggedpointer , 平时咱们常用的对象
isa_t newisa(0);
//对isa的 index 、magic 初始化
newisa.bits = ISA_MAGIC_VALUE;
//对isa的 has_cxx_dtor 初始化
newisa.has_cxx_dtor = hasCxxDtor;
//把传进来的Class指针值右移3位赋值给shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;
//更新objc_object的isa指针
isa = newisa;
}
}
复制代码
在复制Class
的指针值是为何要右移三位在赋值,其实缘由能够在从上面获取内存大小时进行的对齐规则能够看出Class的地址转成64位二进制时指针的后三位都是0,右移3位后再存进isa
的47位的shiftcls,这样节省可内存的空间。经过打印Class的地址值能够看出47位的内存是能够存放的下一个右移3位的Class的地址值的,并不必定要64d的的存储空间。
一个对象调用它的实例方法,实际上是先经过isa指针找到类对象的内存地址,经过访问其成员 class_data_bits_t bits
获取到实例方法。类对象调用的类方法其实与实例方法的原理是同样的经过isa找到元类(MetaClass
)的内存地址,经过访问MetaClass
的class_data_bits_t bits
获取类方法进行调用。二者的方法查找都是在当前类中若是找不到对象的方法就会沿着superClass
指针往父类的方法里面查找,直到找到位置,若是找不到就会进行方法的动态解析或者消息的转发,还没解决就会抛出找不到方法的错误。下面的图片很好的展现了这实例对象(objc
)与其 类对象(Class
)、元类对象(MetaClass
)、父类(SuperClass
)之间的关系。
细心观察上面的图片,其实能够发现几个注意点
那么分析清楚了这几个对象间的关系后,接下来开始分析对象实例方法到底是如何初始化的。
源码定义
struct class_data_bits_t {
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}
复制代码
其实 class_data_bits_t bit
就是一个指针而已。真正的方法存储在data()
返回的指针指向的那块内存中。该内存实际上是一个class_rw_t
的类型值。继续分析返回的class_rw_t *
类型值
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//经过编译的类信息(只读不能写入)
method_array_t methods; //方法列表
property_array_t properties;//属性列表
protocol_array_t protocols; //协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //类名
method_list_t * baseMethodList; //编译其已经肯定的方法(没有分类方法)
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//属性列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
复制代码
经过查阅资料及源码,加上实践验证能够知道,咱们平是定义的类属性或者方法,通过编译器的处理转成C或C++代码其实底层由多种结构体和函数共同协做生成包含只读方法和属性的struct class_ro_t
类型变量。就用上面的Person类做为例子。经过clang编译器指令转成c++
代码后我摘取一些重要片断
//包含对象属性信息的变量 , 用于初始化 _class_ro_t 变量
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"age","TQ,N,V_age"},
{"name","T@\"NSString\",C,N,V_name"}}
};
//包含对象成员变量信息的变量 , 用于初始化 _class_ro_t 变量
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_Person$_age, "_age", "Q", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_Person$_name, "_name", "@\"NSString\"", 3, 8}}
};
//包含对象方法信息的变量 , 用于初始化 _class_ro_t 变量
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
5,
{{(struct objc_selector *)"say", "v16@0:8", (void *)_I_Person_say/**方法对应的函数指针*/},
{(struct objc_selector *)"age", "Q16@0:8", (void *)_I_Person_age},
{(struct objc_selector *)"setAge:", "v24@0:8Q16", (void *)_I_Person_setAge_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_}}
};
// _class_ro_t 类型变量
tatic struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct Person, _age), sizeof(struct Person_IMPL),
0,
"Person",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
};
//下面几个方法都是为初始化 Person 类作准备工做
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_Person,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Person,
};
static void OBJC_CLASS_SETUP_$_Person(void ) {
OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
}
static void OBJC_CLASS_SETUP_$_Person(void ) {
OBJC_METACLASS_$_Person.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Person.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_Person.cache = &_objc_empty_cache;
OBJC_CLASS_$_Person.isa = &OBJC_METACLASS_$_Person;
OBJC_CLASS_$_Person.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_Person.cache = &_objc_empty_cache;
}
复制代码
从上面的源码能够看出在程序编译完成后类的信息已经被编译器处理完了大部分的工做,剩下小部分工做是经过runtime
机制来处理的。
objc源码中有一个函数realizeClass
,负责处理编译信息及运行时信息的转接返回类的真实结构体。我简化下函数留下处理 _class_ro_t
与 class_rw_t
关系的源码
static Class realizeClass(Class cls){
ro = (const class_ro_t *)cls->data();
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
return cls;
}
复制代码
从上面的源码能够看出,类在未通过调用函数realizeClass(Class cls)
前,objc_class
结构体方法调用的class_rw_t *data()
方法返回的实际上是class_ro_t
类型的指针,在通过realizeClass
处理后才把class_rw_t
类型变量建立好,并把原来的class_ro_t
指针赋值给class_rw_t
变量的ro
成员变量,并赋值给cls。
接下来咱们经过objc
源码验证一下
在调试是先获取[Person Class]
的地址,接着realizeClass
开始前打断条件断点(cls == Person地址值)配合lldb指令,经过指针的偏移得到class_data_bits_t
的值,再经过其调用data()方法得到对应的指针,经过把改指针强转为class_ro_t
类型打印出来的值符合以前定义Person类的信息。调试过程以下图