IOS学习 - 方法选择器Selector的相关使用
2015-08-28 01:18 HermitCarb 阅读(1381) 评论(0) 编辑 收藏 举报问题的由来
Objective-C是一门动态的语言,只要有可能,Objective-C总会使用动态的方式来解决问题,比如它尽可能的将编译和连接要做的事推迟到运行时,所以它需要一个强大的运行时系统(runtime)来执行编译好的代码。runtime的角色类似于Objective-C语言的操作系统,是Objective-C的灵魂。Objective-C的很多特性都是基于这个强大的运行时的,是十分值得去学习理解的。
动态语言Objective-C很常见的一个消息发送语句(比较准确的说是消息发送,在不混淆的情况下我还是喜欢说方法调用):
1 [receiver message];
在编译时会转化成:
1 objc_msgSend(receiver, selector);
然后再进行编译的后续工作。
事实上还有几个类似的方法:
1 objc_msgSend_stret(返回值是结构体) 2 objc_msgSend_fpret(返回值是浮点型) 3 objc_msgSendSuper(调用父类方法) 4 objc_msgSendSuper_stret(调用父类方法,返回值是结构体)
编译时只是确定了要向确定的实例发特定的消息,并没有去验证消息的实现(IMP),IDE Xcode会做一些基本的验证,但是涉及到@selector()得来的方法,仅仅只是警告,这里也经常发生崩溃。
编译时不验证消息实现,消息也只有在运行时才会具体发送。
注:个人理解是,消息的实现叫方法,方法的传递过程叫消息。
Selector/SEL是什么
Selector/SEL又叫方法选择器,SEL在objc.h中是这样声明的,而“@selector()”是取得一个SEL指针。说白了,方法选择器仅仅是一个char *指针,表示它所代表的是方法的名字:
1 typedef struct objc_selector *SEL; //声明 2 3 //使用测试 4 SEL selector = @selector(message); //@selector不是函数调用,只是给编译器的一个提示 5 NSLog (@"%s", (char *)selector); //print message
Objective-C在编译的时候,会根据方法的名字,生成一个唯一的用来区分这个方法的ID,这个ID就是SEL类型的。需要注意的是,只要方法的名字相同,那么它们的ID都是相同的,所以Objective-C,没有重载这个概念,所以你会在Objective-C中看到一个很奇怪的方法命名现象:
1 //-(void)setWidth:(int)width; 这个函数被改写为下面的函数 2 -(void)setWidthIntValue:(int)width; 3 4 //-(void)setWidth:(double)width; 这个函数被改写为下面的函数 5 -(void)setWidthDoubleValue:(double)width
这里先提一下,获取了SEL后,若想向某一对象发送消息,可以使用performSelecor:
1 [receiver performSelecor:@selector(message)];
但一般建议先用respondsToSelector检查对象是否可以响应这个消息。
写到这里,你可能感觉这个SEL和C/C++中的函数指针很像,那么你可能要失望了,因为IMP和函数指针更像。
IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它。
IMP也是一个指针,它指向的是方法的地址,而SEL只是一个ID,用于runtime从方法表中找到对应的IMP。SEL是方法的ID,IMP是方法的实现。IMP和C/C++中函数指针的不同是,函数指针的值,在编译时是确定的,而SEL对应的IMP是在运行时才确定,换句话说你可以在运行时动态的替换方法的实现(其实只是替换IMP指向的位置)。关于IMP的部分后几天总结一下再发上来,有兴趣的也可以自己先搜一下。
selector主要用于两个对象之间进行松耦合的通讯。这种方法很多开发环境用到,比如GTK,Delphi。基本上整个Cocoa库之间对象,控制之间通讯都是在这个基础构建的。Selector是Objective-C动态性实现的十分重要的一部分。
Selector/SEL的存储、查找
刚说到,编译器会在编译时根据方法名为方法生成唯一的ID(SEL),而这些SEL构成了若一个集合,由runtime(运行时)维护。runtime为所有正在使用的类和实例维护着这些集合,并且为了减少动态消息带来的时间成本,runtime还为他们维护着一个SEL的cache。方法集合和cache是通过优化过的Hash实现的,通过散列可以可以加快查询速度。
iOS中的obj都继承于NSObject,在NSObjcet中存在一个Class的isa指针。
1 @interface NSObject { 2 Class isa OBJC_ISA_AVAILABILITY; 3 }
再来看Class:
1 typedef struct objc_class *Class; 2 struct objc_class { 3 Class isa; //指向元类metaclass 4 5 Class super_class ; //指向其父类superclass 6 const char *name ; //类名 7 long version ; //类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取 8 long info; //一些标识信息,如CLS_CLASS(0x1L)表示该类为普通class,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为元类metaclass,其中包含类方法; 9 long instance_size ; //该类的实例变量大小(包括从父类继承下来的实例变量); 10 struct objc_ivar_list *ivars; //用于存储每个成员变量的地址 11 struct objc_method_list **methodLists ; //与 info 的一些标志位有关,如CLS_CLASS(0x1L),则存储对象方法,如CLS_META(0x2L),则存储类方法; 12 struct objc_cache *cache; //指向最近使用的方法的指针,用于提升效率; 13 struct objc_protocol_list *protocols; //存储该类遵守的协议 14 }
可以看到,任意的类的isa成员(从NSObject继承下来的)中存储了该类的很多信息。
Class isa:指向metaclass,也就是静态的Class。
Class super_class:指向父类,如果这个类是根类,则为NULL。
下面是isa和superclass的指向关系(抓大牛的图):
isa指针:
一个Obj实例中的isa会指向普通的Class,这个Class中存储普通成员变量和对 象方法(“-”开头的方法)。
普通Class中的isa指针指向静态Class,静态Class中存储static类型成员变量和类方法(“+”开头的方 法)。
所有元类metaclass中isa指针均指向根元类root metaclass。根元类root metaclass的isa指针也指向自身。
super_class:
普通的Class的super_class指向其父类普通的Class。
元类metaclass的super_class指向其父类元类metaclass。
根元类root metaclass的super_class指向根类普通的Class,根类普通Class的super_class为nil。(顺便提一下,Objective-C中,nil、Nil、NULL的值相同)
下面我们就来看看具体消息发送之后是怎么来动态查找对应的方法的。
首先,编译器将代码[receiver message];转化为objc_msgSend(receiver , @selector (message));,在objc_msgSend函数中。首先通过receiver的isa指针找到receiver对应的class。在Class中先去cache中通过SEL查找对应函数method,若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过查找到的IMP跳转到对应的函数中去执行。