本文为CocoaChina网友fiifii999投稿
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,如下:
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,我们可以看到如下定义:
@interface NSObject { Class isa OBJC_ISA_AVAILABILITY; }
再点Class进去看Class的定义,其实就是C结构的别名:
/// 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类
@interface Test : NSObject @interface Test2 : Test @interface Test3 : Test2
引入头文件:
#import #import "Test.h" #import "Test2.h" #import "Test3.h"
写下如下测试方法:
- (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])); }
运行代码后,执行结果如下:
--------- 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博客)