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)

Objective-C类之关系

Objective-C类之关系

Objective-C类之关系

Objective-C类之关系

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 ,如下:

Objective-C类之关系

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 *` */

由定义我们知道几点:

  1. 每个Objective-C的类都有一个Class isa的成员变量,这个isa指向其类或它的元类地址;
  2. 如果这个类有父类,则super_class就会记录父类的地址;
  3. 每个类都会在objc_ivar_list中记录其所有成员变量的地址;
  4. 每个类都会在objc_method_list中记录其所有方法的地址;
  5. 每个类都会在objc_protocol_list中记录其所有协议的地址;

那么,元类是什么,元类就是每一个我们看得见的类的背后如影随形的一个类,它会记录这个类的所有的类方法(即静态方法)等一些内容。我们平时创建一个类,其实同时也会创建这个元类,只是这个元类是工作于runtime底层的,对上层应用开发者来说,不用管也不用操心。

实例对象、类、元类、根类之间的isa指向关系如下:

Objective-C类之关系

小结:

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。

归纳总结下后,根据这个例子画个图,就更清晰了:

Objective-C类之关系

把上面例子的图简化下,就是如下的图:

Objective-C类之关系

5、总结

不罗嗦,简单概括本文就是:

NSObject是类之根本,全靠它来开枝散叶;

元类如同鬼魅丽影,但却实如一人;

类之关系判断有三板斧:isSubclassOfClass,inKindOfClass,isMemberOfClass;

乾坤挪移、斗转星移,大法还是runtime好。

其实Objective-C类之关系远远不止这些,因为很多内容相关联,目前只是沧海一粟,有时间总结出其他内容再分享出来。

(注:本文原发表于自己的 CSDN博客



随意打赏

objective-cobjective
提交建议
微信扫一扫,分享给好友吧。