Objective-C类之关系
1、NSObject是所有类的根类
我们知道,Objective-C是面向对象的语言,不论你使用任何类,比如NSString、UIView、 NSWindowController、UIViewController、NSViewController……,也就是不论是基于macOS的 Cocoa类库,还是基于iOS的Cocoa Touch类库,还是Fundation库,它们所有的类都会指向NSObject这个根类(root class),如同道家所说的一生二,二生三,三生万物,这个NSObject就是一,所有类的起源。同时,根类/父类拥有的特性也会由子类继承下去。
比如iOS中的UIButton,类的继承关系如下:
更详细的类继承关系,参看下图。
先来看Fundation库,Fundation是支撑iOS和macOS的基础库,其中蓝色部分的是iOS才支持的,macOS全部支持(本来iOS就是从Mac OSX改过来的,OSX现在更名为macOS)
2、NSProxy也是一个根类
当然,OC里还有一个根类,就是NSProxy,有且仅有这两个,但是这个NSProxy很少使用,尤其是在iOS的Cocoa Touch中从未使用过,官方描述:
Note: The Foundation framework defines another root class, NSProxy, but this class is rarely used in Cocoa applications and never in Cocoa Touch applications.
NSProxy不是本文重点,暂不细说,有兴趣的可以看官方文档。
3、判断类的关系
判断一个类的实例是不是某类的子类或就是某类,使用isKindOfClass。
假如类B继承于类A ,如下:
1
2
3
4
5
6
7
8
9
10
|
B *objB = [[B alloc] init];
BOOL rev = [objB inKindOfClass:[A class]];
//判断实例对象objB是否是类A的子类或就是类A
//如果rev=YES则表示实例对象objB是类A的子类或类A
BOOL rev = [objB isMemberOfClass:[A class]];
//判断实例对象objB是否是类A,结果rev=NO
BOOL rev = [objB isMemberOfClass:[B class]];
//判断实例对象objB是否是类B,结果rev=YES
//isSubclassOfClass等同于inKindOfClass,是判断一个类是否是某类的子类或就是某类,但它是类方法,适用于类间判断
BOOL rev = [B isSubclassOfClass:[A class]];
//判断类B是否是类A的子类或就是类A,结果rev=YES
BOOL rev = [B isSubclassOfClass:[B class]];
//判断类B是否是类A的子类或就是类A,结果rev=YES
BOOL rev = [A isSubclassOfClass:[B class]];
//判断类A是否是类B的子类或就是类A,结果rev=NO
|
A(类A) | 结果 | |
B(类B) | [B isSubclassOfClass:[A class]]; | YES |
objB(类B的实例) | [objB inKindOfClass:[A class]]; | YES |
objB(类B的实例) | [objB isMemberOfClass:[A class]]; | NO |
要讲类和元类,就得先说OC的动态性、消息机制、以及类的结构、类的isa指针、方法列表等。
我们知道,Objective-C是一门面向对象的动态语言,动态是最重要也是其区别于其他面向对象语言最有特色的一面,与C /JAVA这些静态编译语言不同的是,OC的函数/方法不是在静态编译阶段就确定地址的,而是在运行时(Runtime)通过消息机制(objc_msgSend)来实时确定并调用的。
比如,平常OC开发中,调用类A实例clsA的一个方法,如[clsA doSomething]; 其实会被runtime转换为objc_msgSend(receiver, selector)进行执行,即objc_msgSend([clsA class], @selector(doSomething))。
如果有参数,则是objc_msgSend(receiver, selector, arg1, arg2, …)。
也就是说,对一个方法的调用,其实就是runtime执行消息发送,那runtime怎么知道发给谁,那个目标方法又在哪呢?这就需要我们了解下Objective-C中一个类是怎么定义的,类结构怎样的。
我们在任意一个类上按cmd键 鼠标左键点击进去,就可以看到这个类的定义,如果我们不断这样溯源这个类的父类一直到根类,也就是前面我们说的NSObject,我们可以看到如下定义:
1
2
3
|
@interface NSObject {
Class isa OBJC_ISA_AVAILABILITY;
}
|
再点Class进去看Class的定义,其实就是C结构的别名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
//指向类或元类地址
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
//指向父类地址
const charchar *name OBJC2_UNAVAILABLE;
//类名
long version OBJC2_UNAVAILABLE;
//类版本
long info OBJC2_UNAVAILABLE;
//信息
long instance_size OBJC2_UNAVAILABLE;
//实例的大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
//成员变量列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
//方法列表
struct objc_cache *cache OBJC2_UNAVAILABLE;
//缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
//协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
|
由定义我们知道几点:
- 每个Objective-C的类都有一个Class isa的成员变量,这个isa指向其类或它的元类地址;
- 如果这个类有父类,则super_class就会记录父类的地址;
- 每个类都会在objc_ivar_list中记录其所有成员变量的地址;
- 每个类都会在objc_method_list中记录其所有方法的地址;
- 每个类都会在objc_protocol_list中记录其所有协议的地址;
那么,元类是什么,元类就是每一个我们看得见的类的背后如影随形的一个类,它会记录这个类的所有的类方法(即静态方法)等一些内容。我们平时创建一个类,其实同时也会创建这个元类,只是这个元类是工作于runtime底层的,对上层应用开发者来说,不用管也不用操心。
实例对象、类、元类、根类之间的isa指向关系如下:
小结:
isa指向顺序是:实例变量isa –> 类–> 类的元类 –> NSObject的元类
任何一个类的元类的isa都会指向根类NSObject的元类。
根类NSObject的isa指向自己。表示到头了。
4、子类、父类、根类、元类
子类、父类、根类、元类之间是什么样的关系呢?再加上一个类的实例变量,貌似这一大家子够热闹的,怎么扯清他们之间的关系呢,我们写个代码例子来看看。
新建3个类,Test类,Test2类,Test3类,其继承关系为:NSObject –> Test类–> Test2类 –> Test3类
1
2
3
|
@interface Test : NSObject
@interface Test2 : Test
@interface Test3 : Test2
|
引入头文件:
1
2
3
|
#import #import "Test.h"
#import "Test2.h"
#import "Test3.h"
|
写下如下测试方法:
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
|
- (void)testClassRelation {
// Test
NSLog(@
"--------- Test -----------"
);
Test *intanceT = [[Test alloc] init];
Class a_currentClass = object_getClass(intanceT);
for
(int i=1; i<=4; i ) {
BOOL isMetaClass = class_isMetaClass(a_currentClass);
NSLog(@
"Test类isa指针第%d次是%p, 类名是%@, 是否是元类=%d, 父类指针是%p"
, i, a_currentClass, NSStringFromClass(a_currentClass), isMetaClass, [a_currentClass superclass]);
a_currentClass = object_getClass(a_currentClass);
}
// Test2
NSLog(@
"--------- Test2 -----------"
);
Test2 *intanceT2 = [[Test2 alloc] init];
Class b_currentClass = object_getClass(intanceT2);
for
(int i=1; i<=4; i ) {
BOOL isMetaClass = class_isMetaClass(b_currentClass);
NSLog(@
"Test2类isa指针第%d次是%p, 类名是%@, 是否是元类=%d, 父类指针是%p"
, i, b_currentClass, NSStringFromClass(b_currentClass), isMetaClass, [b_currentClass superclass]);
b_currentClass = object_getClass(b_currentClass);
}
// Test3
NSLog(@
"--------- Test3 -----------"
);
Test3 *intanceT3 = [[Test3 alloc] init];
Class c_currentClass = object_getClass(intanceT3);
for
(int i=1; i<=4; i ) {
BOOL isMetaClass = class_isMetaClass(c_currentClass);
NSLog(@
"Test3类isa指针第%d次是%p, 类名是%@, 是否是元类=%d, 父类指针是%p"
, i, c_currentClass, NSStringFromClass(c_currentClass), isMetaClass, [c_currentClass superclass]);
c_currentClass = object_getClass(c_currentClass);
}
// NSObject
NSLog(@
"--------- NSObject -----------"
);
NSLog(@
"NSObject类的指针是%p"
, [NSObject class]);
NSLog(@
"NSObject元类的指针是%p"
, object_getClass([NSObject class]));
}
|
运行代码后,执行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
--------- Test -----------
Test类isa指针第1次是0x10477d6e0, 类名是Test, 是否是元类=0, 父类指针是0x104fd9170
Test类isa指针第2次是0x10477d6b8, 类名是Test, 是否是元类=1, 父类指针是0x104fd9198
Test类isa指针第3次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
Test类isa指针第4次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
--------- Test2 -----------
Test2类isa指针第1次是0x10477d7d0, 类名是Test2, 是否是元类=0, 父类指针是0x10477d6e0
Test2类isa指针第2次是0x10477d7a8, 类名是Test2, 是否是元类=1, 父类指针是0x10477d6b8
Test2类isa指针第3次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
Test2类isa指针第4次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
--------- Test3 -----------
Test3类isa指针第1次是0x10477d780, 类名是Test3, 是否是元类=0, 父类指针是0x10477d7d0
Test3类isa指针第2次是0x10477d758, 类名是Test3, 是否是元类=1, 父类指针是0x10477d7a8
Test3类isa指针第3次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
Test3类isa指针第4次是0x104fd9198, 类名是NSObject, 是否是元类=1, 父类指针是0x104fd9170
--------- NSObject -----------
NSObject类的指针是0x104fd9170
NSObject元类的指针是0x104fd9198
|
在面向对象的Objective-C语言中,类的实例变量是对象(实例对象);类也是对象(类对象);类的元类也是对象(元类对象)。
从打印结果和比对指针地址来看,我们可以看到:
- Test1 / Test2 / Test3 各自的实例变量的isa指针第1次都指向各自的类对象的地址,即intanceT指向了Test类对象的地址0x10477d6e0;intanceT2指向了Test2类对象的地址0x10477d7d0;intanceT3指向了Test3类对象的地址0x10477d780。
- Test1 / Test2 / Test3 各自的实例变量的isa指针第2次都指向各自的类对象的元类地址,即intanceT指向了Test类的元类对象地址0x10477d6b8;intanceT2指向了Test2类的元类对象的地址0x10477d7a8;intanceT3指向了Test3类元类对象的地址0x10477d758。
- Test1 / Test2 / Test3 各自的实例变量的isa指针第3次都指向了NSObject的元类对象地址,即intanceT、intanceT2、intanceT3第3次都指向了NSObject元类对象的地址0x104fd9198。
- Test1 / Test2 / Test3 各自的实例变量的isa指针第4次重复了第3次的结果,如果循环7次、8次、100次,从第4次开始都是和第3次结果是一样的,说明isa指针从第3次开始,即到了NSObject元类后就指向了自己,进入闭环。
也就是说不管一个类继承了多少次,和根类NSObject隔了多少层,纵是千秋万代,但最后一个X代孙子的isa到祖爷爷(根类NSObject的)的距离始终只有3层,也就是:
X类的实例对象 | –> | X类 | –> | X类的元类 | –> | NSObject类的元类 |
第1层 | 第2层 | 第3层 |
元类isa的关系只有3层,但是正常我们看见的类与父类的关系,继承了多少层那还是多少层,祖宗十八代族谱上还是一个接一个的有数的,从上面打印结 果上也可以看到Test3父类指针指向的是Test2,Test2父类指针指向的是Test,Test父类指针指向的是NSObject。
归纳总结下后,根据这个例子画个图,就更清晰了:
把上面例子的图简化下,就是如下的图:
5、总结
不罗嗦,简单概括本文就是:
NSObject是类之根本,全靠它来开枝散叶;
元类如同鬼魅丽影,但却实如一人;
类之关系判断有三板斧:isSubclassOfClass,inKindOfClass,isMemberOfClass;
乾坤挪移、斗转星移,大法还是runtime好。
其实Objective-C类之关系远远不止这些,因为很多内容相关联,目前只是沧海一粟,有时间总结出其他内容再分享出来。
(注:本文原发表于自己的 CSDN博客 )