Objective-C 类和对象的声明

Objective-C的Runtime是开源的,在Obj4-532.2之后,NSObject也开源了,可以在这里下载
下面的代码就是节选自最新的代码551.1.

1
2
3
4
5
6
7
8
//objc.h
typedef struct objc_class *Class;//代表类 Objective-C class
typedef struct objc_object *id;//代表对象,也就是类的实例 instance of a class.

//NSObject.h -- NSProxy.h
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
1
2
3
4
5
6
7
//runtime.h
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
};

在objc.h里,声明了两个类型:Classid,分别代表Objective-C(以下简称OC)类和实例对象,可以看作是泛型。在runtime.h中,我们看到OC类和对象其实就是两个结构体,其中第一个变量就是一个指向自己所属类的isa指针。

所以根据上面的声明,在OC中,一个对象的类由它的 isa 指针决定,isa 指针指向这个对象所属的Class。或者可以这么说:凡是首地址是isa指针的结构体,都可看作OC类。

当然后一句是不严谨的,根据下面的代码,我们发现,一个OC对象,还要包含ISA(),initIsa(),getIsa(),changeIsa()等5个方法。理所当然的,在NSObject.h中,NSObject也包含了一个isa指针。

Objective-C 类和对象的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//objc-private.h
struct objc_object {
private:
uintptr_t isa; //isa 指针,如果不是TaggedPointer,指向 Class

public:

// ISA() assumes this is NOT a tagged pointer object
Class ISA()
{
assert(!isTaggedPointer());
return (Class)isa;
}

// getIsa() allows this to be a tagged pointer object
Class getIsa()
{
#if SUPPORT_TAGGED_POINTERS
if (isTaggedPointer()) {
uintptr_t slot =
((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
#endif
return ISA();
}

// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class cls);

// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
void initIsa(Class cls)
{
assert(!isTaggedPointer());
isa = (uintptr_t)cls;
}

bool isTaggedPointer()
{
#if SUPPORT_TAGGED_POINTERS
return ((uintptr_t)this & TAG_MASK);
#else
return false;
#endif
}
};
1
2
3
4
5
6
7
8
//objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
uintptr_t data_NEVER_USE;
/*....省略若干方法...*/
};

OC的对象定义在objc-private.h中,除了isa指针外,还包含了另外5个方法,简单说下其中两个:

  • getIsa()是获取isa指针的方法,如果isa不是TaggedPointer,则将isa转为Class类型返回;如果isa是TaggedPointer,则返回经过计算的指针。关于TaggedPinter的内容,可以参考唐巧的博文

  • changeIsa() 。。可能和isa swizzle 有关,以后再说

OC对象模型分析

OC的类的定义在objc-runtime-new.h中,比较特殊,它是继承自OC对象,所以所有的OC类都可以看作是对象—元类的对象;OC类还增加了包括superClass指针和和方法列表缓存cache_t cache等,superClass指向父类,方法cache是为了提高访问效率而设定的,用来缓存调用过的方法,这样下次再调用的时候就能提高查找效率。所以在运行时,通过isa指针就能找到真正调用的方法,其isa指针和superClass指针的指向关系如下图所示:

!image



根据上图,我们可以看到:

  • 创建一个OC类其实是创建一个类对(ClassPair)-类和元类,类的isa指针指向元类,元类的isa指向根元类,即图中的NSObject元类,根元类的isa指向它自己,形成闭环,保证isa指针永远不会为空 —同时这也会使得一个类(Class)可以
    响应NSObject的所有实例方法

  • isa指针指向就是消息传递的方向,如果通过isa指针没找到此消息,则继续通过superClass指针查找,如下图所示。

  • 类中含有实例方法列表,元类中含有类方法列表;NSObject元类的superClass指向NSObject实例,所以,理论上给元类发消息,能找到NSObject的实例方法。

为什么要有元类呢?一是可以分离类方法和实例方法;二是元类总是会保证 Class 对象会有从基类继承的所有的的实例和类方法。

运行时的消息传递

!image

在运行时动态创建一个类

动态创建一个类,需要三个步骤:

  • 1、为“class pair”-类和元类 创建存储空间(使用 objc_allocateClassPair).
  • 2、为这个类添加所需的 methods 和 ivars(使用 class_addMethod).
  • 3、注册这个类,然后就可以使用了(使用 objc_registerClassPair).

下面的代码在运行时创建了一个 NSError 的子类同时为它添加了一个方法:

1
2
3
4
Class newClass =
objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);

添加的方法使用叫 ReportFunction 的函数作为实现,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ReportFunction(id self, SEL _cmd)
{
NSLog(@"This object is %p.", self);
NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);

Class currentClass = [self class];
for (int i = 1; i < 5; i++)
{
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}

NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}

isa swizzling

KVO(key-value observer)就是通过isa swizzling来实现的。
当我们为一个类的某个属性添加observer时候,框架自动创建这个类的一个子类,并且修改这个类的isa指向这个新的子类。
由于在ios中函数调用都是转化为isa查表形式,所以这次查得时新的子类的表,
也就是说对类的函数调用被子类给拦截了,在拦截的实现中就可以通知observer了。

修改类的isa被称为isa-swizzling技术。isa-swizzling就是类型混合指针机制。KVO主要通过isa-swizzling,来实现其内部查找定位的。

参考资料:

1.Objective-C中的类和对象(instance)

2.Objective-C对象模型及应用

3.what is meta class in objective-c

4.Objective-C 中的 Meta-class 是什么?

5.Object, Class and Meta Class in Objective-C

6.Objective-C Runtime Reference

7.KVO/KVC 实现原理进一步分析
8.KVC/KVO原理详解及编程指南