日记

首页 » 归档 » 日记 » Objective-C类之关系-ios学习从入门到精通尽在姬长信

Objective-C类之关系-ios学习从入门到精通尽在姬长信

分享最热门的资讯

  • 本文为CocoaChina网友fiifii999投稿

1、NSObject是所有类的根类

我们知道,Objective-C是面向对象的语言,不论你使用任何类,比如NSString、UIView、 NSWindowController、UIViewController、NSViewController……,也就是不论是基于macOS的 Cocoa类库,还是基于iOS的Cocoa Touch类库,还是Fundation库,它们所有的类都会指向NSObject这个根类(root class),如同道家所说的一生二,二生三,三生万物,这个NSObject就是一,所有类的起源。同时,根类/父类拥有的特性也会由子类继承下去。

比如iOS中的UIButton,类的继承关系如下:

1503035096282432.png

更详细的类继承关系,参看下图。

先来看Fundation库,Fundation是支撑iOS和macOS的基础库,其中蓝色部分的是iOS才支持的,macOS全部支持(本来iOS就是从Mac OSX改过来的,OSX现在更名为macOS)

1503035314150525.png

1503035315313353.png

1503035316869024.png

1503035318931653.png

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

1503035426895982.png

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,我们可以看到如下定义:

再点Class进去看Class的定义,其实就是C结构的别名:

由定义我们知道几点:

  1. 每个Objective-C的类都有一个Class isa的成员变量,这个isa指向其类或它的元类地址;

  2. 如果这个类有父类,则super_class就会记录父类的地址;

  3. 每个类都会在objc_ivar_list中记录其所有成员变量的地址;

  4. 每个类都会在objc_method_list中记录其所有方法的地址;

  5. 每个类都会在objc_protocol_list中记录其所有协议的地址;

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

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

1503035823804025.png

小结:

isa指向顺序是:实例变量isa --> 类--> 类的元类 --> NSObject的元类

任何一个类的元类的isa都会指向根类NSObject的元类。

根类NSObject的isa指向自己。表示到头了。

4、子类、父类、根类、元类

子类、父类、根类、元类之间是什么样的关系呢?再加上一个类的实例变量,貌似这一大家子够热闹的,怎么扯清他们之间的关系呢,我们写个代码例子来看看。

新建3个类,Test类,Test2类,Test3类,其继承关系为:NSObject --> Test类--> Test2类 --> Test3类

引入头文件:

写下如下测试方法:

运行代码后,执行结果如下:

在面向对象的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。

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

1503036266746817.png

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

1503036266480970.png

5、总结

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

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

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

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

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

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

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

ios学习从入门到精通尽在姬长信

(0)

本文由 姬長信 创作,文章地址:https://blog.isoyu.com/archives/20302.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:8月 18, 2017 at 04:00 下午

热评文章