问答:
什么是多态?
不同类之间共享相同方法名的能力。
或不同类以自己的方式响应相同消息的能力称为多态。
object-c如何实现多态?
多态是利用动态类型、动态绑定、选择器机制和类方法列表(dispatch table),isa指针,父类指针等数据结构来实现的。
如何实现动态类型识别?
概括的说是:将对象存入Id这个弱数据类型,直到运行时才确定对象所属的类。
具体来说:每个对象均包含一个isa指针,指向对象所属的类。依靠这个指针就能在运行时动态确定对象所属的类
isa指针怎么来的?:几乎所有的类都继承自NSObject,而NSObject中有一个实例变量isa,也就是说每个类都有一个isa变量
所有对象在实例化的时候都会将自己的isa变量指向自己所属的类
如何实现方法的动态绑定?
概括的说:在运行时,根据消息名称找到响应消息的方法入口地址的过程或者说将某些消息对应的响应方法绑定到实例上的过程叫动态绑定(也可以添加新的方法,在消息转发之前)
在运行时,发送消息以后,首先根据消息接收对象的isa指针找到对象所属的类,在类的dispatch table(方法列表)中查找对应的函数入口地址,如果没有找到则沿着继承关系往父类寻找,一直到NSObject。找到以后利用消息接收对象的self指针对函数进行调用响应消息。没有找到则会报错。
找到函数地址后,利用传递过去的receiver 和SEL(即self 和_cmd)(id和SEL)可以建立起一个优化的绑定关系
在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。
相关概念:通过接收者(receiver)、选择器(SEL)和类消息映射表(方法列表)来实现。
动态绑定的核心就在于绑定的是方法的声明,而不是方法的实现。
什么是SEL?
SEL是针对一个方法名的唯一的标识符,所有同名方法(包括参数列表)具有相同的标识。
发送消息的时候,依靠这个标识在类的方法列表中查找对应函数具体的入口地址
什么是ISA指针?IMP?
所有继承自NSObject的对象都会拥有一个isa指针实例变量,指向对象所属的类。用于识别动态对象
IMP是函数的实际入口地址,它和SEL之间有一个对应关系表,每个类都拥有这么一个表。动态绑定的目的就是找到SEL对应的IMP
消息和函数的区别?
传统的函数一般在编译的时候就将参数等函数信息编译到了目标文件中,运行时直接调用,是一个静态的过程。
消息是OC中的概念,响应消息是一个动态查找响应消息的方法的过程,甚至可以添加新的方法来响应消息。
称为往对象发送消息,对象在收到消息之后,在自身实例中查找响应这条消息的方法的过程。
多态的优点:
1.可以方便的为不同类编写不同的响应相同消息的代码
多态使得可以开发一组类,每个类都可以使用响应相同的方法名,使用相同的调用方式,却可以拥有自己独立不同的方法定义,并且在添加新类的时候也可以方法同名
比如各种图形(圆,正方形等)都可以响应一个draw消息。
2.增加了程序灵活性。因为动态绑定中SEL类型选择器机制的使用,可以根据方法名(字符串)动态执行一个方法,这个方法可以从用户输入或者配置文件读取
多态的缺点,或者说静态类型的优点:
1.程序拥有更强的可读性
2.预防错误,在编译时更好的检查出来错误。
定义:
1.多态定义:使不同类以自己的方式响应相同消息名称的能力称为多态
多态包含两部分:在运行时,由对象识别出类,由类绑定方法
分别成为动态类型识别和动态绑定,识别指对象识别(isa),绑定指方法绑定(类名称空间中SEL映射表:方法名和方法内存地址的映射)。
2.动态类型:在运行时才确定对象类型(对象所属的类),利用id类型来实现,通过反射机制来查找
id,可以存储任何类型数据,在运行时能确定对象所属的类
弱类型:id是弱类型,因此再编译期间不进行类型检查,推迟到运行时
id obj=someInstance;
-isKindOfClass:和isMemberOfClass是常用的反射方法,是NSObject的方法。
3.动态绑定:在运行时,确定对象所属类之后,对象对应的方法列表也就确定了(确定方法究竟属于哪个类空间)。
动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在调用的消息转发给实例之前,变有机会为类加入一些新的方法。
4.类(类对象):类是一个数据结构(类似c里的结构体),作为一个独立的名称空间而存在拥有自己的数据(static变量)和类方法。
它存储类的基本信息如类名称,类大小,版本,类包含的方法映射表(消息映射表)(方法名和方法内存地址的映射表)等
类所保存的信息在编译时确定,在运行时一直存在于内存中。
5.对象类型(实例对象):对象分为静态类型和动态类型。实例对象是一个类的具体化,实例对象拥有属于自己的实例变量,以及和别的属于同一个类的实例对象共享的实例方法
静态类型在编译时就已经确定了自己所属的类,以及要调用的方法。动态类型则在运行时确定
6.isa指针(类和对象的关系):运行时,所有类的实例都由类对象生成,类对象会把实例的isa的值修改成自己的地址,每个实例的isa都指向该实例的类对象,从类对象里可以知道父类信息、可以响应的方法等。利用[对象 class]方法(继承自NSObject)可以得到对象所属的类。
。
7.类方法:类方法属于类对象,只能通过类来调用,类对象只能访问类静态变量,不能访问实例变量,实例变量属于每个实例独立拥有。
8.SEL类型:所有同名方法(参数列表和返回值也相同)都对应一个相同的标识(字符串),SEL类型就是这个标识的类型。
SEL类型用于方法的动态绑定。每个类的消息(方法)映射表可能如下:
方法名称(char*) - 方法标识(SEL) - 方法参数类型列表(char*) -方法所在内存地址(IMP)
getsometing() 00001 (int,double,id) 0x0031
9.(方法)动态绑定的过程:([对象 SEL方法名标识])
找到对象所属的类(类空间,利用isa),查找类的消息映射表中是否有对应的SEL.
如果有,则根据对象的self指针,找到对应的IMP,加上参数列表,就可以直接调用方法了
如果没有,则找到本类的父类(isa->父类isa),如果一直到NSObject仍然没有找到对应的SEL,则报错。
(静态类型绑定的过程错误是编译型错误,动态类型就会crash)
缩略语解释:
receiver->isa->isa->class SEL 查找SEL是否存在,不存在则报错
存在则:receiver->self->SEL to IMP 调用
10.方法的类型结构为:
struct objc_method{
SEL method_name;//方法标识
char *method_types; //方法参数列表
IMP method_imp; //方法地址(IMP)
};
如下方法提供了方法名称和SEL变量之间的转换
SEL mydraw =@select(draw);
NSSelectorFromString(NSString*);根据方法名得到方法标识
(NSString*)NSStringFromSelector(SEL);得到SEL类型的方法名
11.IMP就是方法的实际内存地址(相当于函数指针)12.动态类型参数和返回值注意事项
动态类型在编译时总是通过假设的方法来处理参数和返回值,进行编译。
那么,两个同名方法将会编译生成一样的代码,如果两个方法的参数都是对象类型,则不会出错,因为对象类型的实质都是指针
如果一个方法参数是对象类型,另外一个方法参数是内置对象,那么就会编译出错误的代码
所以在要求方法名一样的情况下,还要求参数列表也是相同的(都是对象类型或者相同的内置类型)
13.多态相关的方法
类/对象是否属于classObj类或其子类,
-(BOOL)isKindOfClass:classObj //类或者子类
-(BOOL)isMemberOfClass:classObj //类
-(BOOL)respondsToSelector:Selector //类/对象是否能响应方法,广泛应用于委托
+(BOOL)isSubclassOfClass:classObj //类是否子类
-(id)performSelector:selector -(id)performSelector:selector withObject:object -(id)performSelector:selector withObject:object1 withObject:object2
//执行方法
14.动态加载:根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。