简介
OC是C语言的超集,是扩充C的面向对象编程语言。OC的语法基本上是照搬C语言的,对象所占内存总是分配在“堆空间”中,而绝不会分配在“栈”上。
每个OC对象实例都是指向某块内存数据的指针,所以声明变量时,类型后面要跟一个 * 字符,有时会遇到定义里不含 * 的变量它们可能会使用栈空间,这些变量保存的不是OC对象,比如CGRect是c的结构体。
例如: NSString * pointerVar = @“aa”;
pointerVar存放内存地址的变量,NSString自身的数据就存于那个地址中。可以说,变量指向NSString实例。
特殊类型id,能代指任意OC对象类型。id本身已经是指针。可以写为 id pointerVar = @“aa”; 区别在于,如果声明指定了具体类型,那么在该类实例上调用其所没有的方法时,编译器会探知此情况,并发出警告信息。
OC对象底层实现
了解了OC对象之后,来看看OC的对象是怎样通过C语言来进行实现的。由于水平有限,只能简单梳理一下,有兴趣的可查看苹果源码https://opensource.apple.com/source/objc4,进行深入了解。
OC 对象都是 C 语言结构体
描述OC对象所用的数据结构定义在运行期程序库的头文件里,每个对象结构体的首个成员是Class类的变量,该变量定义了对象所属的类,通常称为 is a 指针。
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
typedef struct objc_class *Class;
typedef struct objc_object *id;
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
...
};
struct objc_object {
isa_t isa;
...
};
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
...
};
1、结构体objc_class 中存放类的元数据。objc_class 结构体是继承自 objc_object 的,首个变量也是isa指针,说明Class本身也是OC对象。
2、superclass定义了本类的超类。类所属的类型是另外一个类,叫做“元类” (metaclass),用来表述类对象本身所具备的元数据。类方法就是定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,每个类对象仅有一个与之相关的元类。如下图:
3、superclass确立了继承关系,isa指针描述了实例所属的类。通过图上的关系可一步步查询出对象是否能响应某个方法,是否遵循某个协议等。当实例方法被调用时,它要通过自己持有的 isa 来查找对应的类,然后在结构体 class_data_bits_t 中查找对应方法的实现。
4、cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。
5、结构体 isa_t,这个结构体中包含了当前对象指向的类的信息。
OC对象实例
举个例子,当我们定义一个类,带有两个成员变量:
@interface SomeObject : NSObject {
NSString * var1;
NSString * var2;
}
这个对象的底层的数据结构,相当于是:
struct SomeObject {
struct NSObject {
struct objc_class {
Class isa;
};
};
NSString * var1;
NSString * var2;
};
对象在内存中的情况:
Class isa;
NSString * var1;
NSString * var2;
所以说,OC中的对象是一个指向ClassObject地址的变量:
id obj = &ClassObject 。
对象的实例变量则是,ClassObject地址加上变量对应的偏移量:
void *ivar = &obj + offset(N)
OC中构成一个对象有三个部分:
创建好一个对象后,有一块首地址指向Class的内存,就是Class类型的 isa 指针,指向结构体 objc_class,其中类的成员变量,对象方法就存放在这。 objc_class 有superclass ,指向元类(metaclass),类方法就保存在其中。如下图:
让每一个类的 isa 指向对应的元类,这样就达到了使类方法和实例方法的调用机制相同的目的:
实例方法调用时,通过对象的 isa 在类中获取方法的实现
类方法调用时,通过类的 isa 在元类中获取方法的实现
在类继承体系中查询类型信息
可用类型信息查询方法来检视类继承体系:
isMemberOfClass: 能够判断出对象是否为某个特定类的实例
[[NSMutableArray array] isMemberOfClass:[NSArray class]] = NOisKindOfClass: 则能判断出是否为某类或派生类的实例
[[NSMutableArray array] isKindOfClass:[NSArray class]] = YES
使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走。由于对象是动态的,此特性显得极为重要。必须查询类型信息,方能完全了解对象的真实类型。
- 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Calss对象则构成类的继承体系
- 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
- 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象
[obj1 class] == [NSString class], 因为某些对象可能实现了消息转发功能。
OC对象创建
下面简单来看一下,+ alloc 和 - init,这两个方法,都做了什么。
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
1、allco方法,最主要的几行代码就是:
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
obj.isa = (uintptr_t)cls;
在使用 calloc 为对象分配一块内存空间之前,我们要先获取对象在内存的大小,在获取对象大小之后,调用 calloc 函数就可以为对象分配内存空间了,接着就是写入isa指针,初始化引用计数器,以及重置所有实例变量。
有关初始化isa的内容,可查看:从 NSObject 的初始化了解 isa
2、init 方法:
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
从此可知 init 方法只是调用了 _objc_rootInit 并返回了当前对象
alloc 方法会返回一个合法的没有初始化的实例对象。每一个发送到实例的消息会被翻译为objc_msgSend() 函数的调用,它的参数是指向 alloc 返回的对象的、名为 self 的指针的。这样之后 self 已经可以执行所有方法了。
为了完成两步创建,第一个发送给新创建的实例的方法是约定俗成的 init 方法。
References
http://www.jianshu.com/p/fedbc5c2f189
http://www.jianshu.com/p/f725d2828a2f
http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html