OC学习9——反射机制

摘要:
7.如果程序需要确定一个对象是否可以调用一个方法,可以通过NSObject的以下方法来确定:responseToSelector:这个方法需要传入一个SEL参数——OC将该方法称为选择器,所以OC使用SEL对象来表示该方法。

1、OC提供了3种编程方式与运行环境进行交互:

  • 直接通过OC的源代码:这是最常见的方式,开发人员只是编写OC源代码,而运行环境负责在后台工作。
  • 通过NSObject类中定义的方法进行动态编程:因为绝大部分类都是NSObject的子类(NSProxy例外),所以绝大部分对象都继承了NSObject的方法,因此,所有对象都可以调用NSObject的方法来编程。例如NSObject提供了isKindOfClass:(判断指定类及其子类的实例对象)、isMemberOfClass:(判断指定类的实例对象)方法用于判断该对象所属的类;respondsToSelector:用于判断该实例是否可调用指定方法;methodForSelector:则用于返回指定方法的指针。
  • 直接调用运行时函数进行动态编程:运行时系统是一个动态共享库,有一些列位于usr/include/objc目录的头文件中的函数和数据结构组成,这些工具都是C风格,他们并不是OC必需的,但有些函数在OC编程中也是有用的。

2、OC中同样也提供了与Java中类似的反射机制,这种动态变成机制可以让OC语言更加灵活。说到反射,首先我们要弄清楚什么是反射,反射的定义是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变。通俗的讲就是反射可以在运行时根据指定的类名获得类的信息。

3、为什么要用反射,也就是反射的意义何在?

  • 当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢因为程序是支持插件的(第三方的),在开发的时候并不知道。所以,无法在代码中 New出来,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。
  • 在编码阶段不知道那个类名,要在运行期从配置文件读取类名,这时候就没有办法硬编码,new ClassName(),而必须用到反射才能创建这个对象。

4、每个类都有一个对应的Class,Class对象其实本质上就是一个结构体,这个结构体中的成员变量还是自己,这种设计方式非常像链表的数据结构。然后通过一个类的Class可以实现获取该类的实例变量、方法等信息,从而可以实现创建对象和调用方法的目的。OC中获得Class通常有3种方法:

  • 使用Class NSClassFromString(NSString * aClassName)函数来获取Class,该函数需要传入字符串参数,该字符串的值是某个类的类名。
  • 调用某个类的class方法来获取该类对应的Class,例如 [FKPerson class]; 将会返回FKPerson类对应的Class
  • 调用某个对象的class方法,该方法是NSObject类中的实例方法,所以所有的OC对象都可以调用该方法,该方法将会返回该对象所属类对应的Class。
    1 #import <Foundation/Foundation.h>
    2 
    3 int main(int argc, char *argv[])
    4 {
    5 @autoreleasepool{
    6         //通过字符串来获取Class
    7         Class clazz = NSClassFromString(@"NSDate") ;
    8         NSLog(@"%@", clazz) ;
    9 
    10         //直接使用Class来创建对象
    11         id date =[[clazz alloc] init] ;
    12         NSLog(@"%@", date) ;
    13 
    14         //通过类来获取Class
    15         NSLog(@"%d", clazz == NSDate.class) ;
    16 
    17         //通过对象来获取Class
    18         NSLog(@"%@", [date class]) ;
    19 }
    20 }

    输出结果是NSDate、2017-08-15 13:15:34 +0000、1、_NSDate。其中最后一个返回的是_NSDate而不是NSDate的原因是因为OC中很多设计都是才用的类簇的设计,NSDate只是这个类簇的前端,当程序调用[[NSDatealloc] init] 创建对象时,程序实际返回的只是NSDate的子类(_NSDate)的实例,而不是NSDate的实例。因此直接调用date来获取Class时返回的是_NSDate。

5、程序中在才用反射机制创建类时一般都需要对创建的对象或者待反射的对象进行一个继承或从属关系的检查,即需要判断一个对象是否是某个类的实例或者是否是某个类或者其子类的实例。闫完成这样的工作,我们可以直接调用NSObject提供的如下方法进行判断:

  • isKindOfClass:该方法需要传入一个Class参数,判断是否是指定类及其子类的实例对象
  • isMemberOfClass:该方法也需要传入一个Class参数,判断是否是指定类的实例对象
  • conformsToProtocol:该方法需要传入一个Protocol参数,为了在程序中获取Protocol对象,通常通过两种方法来获取
    • 使用@Protocol(协议名)来实现
    • 可调用Protocol * NSProtocolFromString(NSString *namestr);函数来根据协议名字符串来获取对应的协议

6、如果程序需要动态地调用对象的setter、getter方法,则可通过OC提供的KVC机制来实现。如果程序需要访问对象的实例变量的值,那么不管这个实例变量是否在类的接口部分定义,也不管该变量使用哪种访问控制符修饰,或者是否在类的实现部分定义,程序都可通过KVC机制来设置、访问实例变量的值。(具体KVC机制的原理后面学习了再补充:OC学习篇之---KVC和KVO操作

7、如果程序需要判断某个对象是否可调用方法,则可通过NSObject的如下方法进行判断:

  • respondsToSelector:该方法需要传入一个SEL参数——OC把方法称为选择器,因此,OC使用SEL对象来代表方法。如果该对象可调用该方法,则返回YES,否则返回FALSE。

如果程序需要动态调用对象的普通方法,则可以通过如下两种方式来实现:

  • 通过NSObject提供的系列performSelector:方法来实现,该方法第一个参数需要传入一个SEL对象,如果调用方法需要传入参数,则还可以通过withObject:标签来传入参数
  • 使用objc_msgSend(receiver, selector, ...)函数来调用。该函数第一个参数是方法调用者,第二个参数代表调用的方法,接下来的参数将作为调用方法的参数。

为了在程序中动态获得SEL对象,OC提供了如下两种方法来获得:

  • 使用@selector指令来获取房前类中制定的方法,该指令需要用完整的方法签名关键字作为参数,仅有方法名是不够的
  • 使用SEL NSSelectorFromString(NSString * aSelectorName)函数根据方法签名关键字的字符串获取对应的方法
1 #import <Foundation/Foundation.h>
2 
3 //定义接口部分
4 @interfaceFKCar : NSObject
5 @end
6 
7 
8 #import <objc/message.h>
9 #import "FKCar.h"
10 
11 //实现部分
12 @implementationFKCar
13 - (void) move : (NSString *) count
14 {
15     int num =[count intValue] ;
16     for(int i = 0 ; i < num ; i++)
17 {
18         NSLog(@"%@", [NSString stringWithFormat:@"汽车正在路上走~~~%d", i]);
19 }
20 }
21 
22 - (double) addSpeed: (double) factor
23 {
24     //此处希望能动态调用move:方法
25     //使用performSelector:动态调用move:方法,通过@selector指令来获取SEL对象
26     [self performSelector:@selector(move:) withObject: [NSNumber numberWithInt:2]] ;
27     
28     //使用performSelector:动态调用move:方法,通过NSSelectorFromString函数来获取SEL对象
29     [self performSelector:NSSelectorFromString(@"move:") withObject: [NSNumber numberWithInt:2]] ;
30     
31     //使用objc_msgSend()函数动态调用move:方法,通过@selector指令来获取SEL对象
32    objc_msgSend(self, @selector(move:) , [NSNumber numberWithInt:3]) ;
33 
34     //使用objc_msgSend()函数动态调用move:方法,通过NSSelectorFromString函数来获取SEL对象
35      objc_msgSend(self, NSSelectorFromString(@"move:") , [NSNumber numberWithInt:3]) ;
36 
37     NSLog(@"正在加速。。。%g", factor) ;
38     return 100 *factor ;
39 }
40 
41 @end

8、此外,NSObject还提供了如下一个方法

  • - (IMP)methodForSelector:(SEL) aSelector:该方法根据传入的SEL参数,返回该方法对应的IMP对象。

IMP代表指向OC方法的函数指针,OC方法的本质还是函数,IMP相当于一个指向函数的指针变量,也就说代表了函数的入口,接下来就可以通过IMP来调用该函数——也就是调用OC的方法。对于一个指向OC方法的函数指针变量,它指向的函数签名的第一个参数通常是方法的调用者,第二个参数通常是方法对应的SEL对象,接下来的参数就说调用该方法的参数。因此,通常会使用如下的代码格式来定义指向OC方法的函数指针,第一个id形参表示方法调用者,第二个SEL类型代表方法,接下来可以声明调用该方法所需的参数:

返回值类型 (* 指针变量名) (id,SEL,。。。)
1 #import <Foundation/Foundation.h>
2 #import "FKCar.h"
3 
4 int main(int argc, char *argv[])
5 {
6 @autoreleasepool{
7 
8         //获取FKCar类
9         Class clazz = NSClassFromString(@"FKCar") ;
10 
11         //动态创建对象
12         id car =[[clazz alloc] init] ;
13 
14         //使用performSelector:方法来动态调用方法
15         [car performSelector: @selector(addSpeed:) withObject: [NSNumber numberWithDouble: 3.4]] ;
16 
17         //使用objc_msgSend:方法动态调用方法
18         objc_msgSend(car, @selector(addSpeed:), 3.4) ;
19 
20         //定义函数指针变量
21         double (* addSpeed) (id, SEL, double) ;
22 
23         //获取car对象的addSpeed:方法,并将该方法赋给addSpeed函数指针变量
24         addSpeed = (double (* addSpeed) (id, SEL, double)) [car methodForSelector: NSSectorFromString(@"addSpeed:")] ;
25 
26         //通过addSpeed函数指针变量来调用car对象的方法
27         double speed = addSpeed(car , @selector(addSpeed:), 2.4) ;
28 
29         //输出
30         NSLog(@"加速后的速度为:%g", speed) ; 
31 }
32 }

9、在开发了大量的IOS项目之后,就会发现有大量的代码是类似的,如果能把这些通用代码抽取成更通用的框架,那么程序将会拥有ugenghaode框架——当需要开发出那些具有通用性质的框架时,这些框架代码无法预先知道被调用组件的实现类,以及具有那些方法,这些信息可能是通过配置文件给出的,而这些诶框架必须动态地根据字符串来创建对象,根据字符创来决定调用那个方法,这些功能呢个则必须借助OC的反射、动态机制来实现,这也回到了我们前面讲的为什么要用反射机制的原因。

免责声明:文章转载自《OC学习9——反射机制》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇.NET Core微服务之开源项目CAP的初步使用Fastboot线刷“复活”之刷机心得(二)——线刷刷机下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

11_Python的69个内置函数详解

1.内置函数分类     思维导图: https://www.processon.com/view/link/5dcabc48e4b0bd68d813b24f 2.基础数据类型-和数字相关的函数(14) 数据类型-bool         bool()函数用于将给定参数转换为布尔类型,如果没有参数,返回 False         bool 是 int 的...

Matlab自定义函数的几种方法

1、函数文件+调用函数文件:定义多个M文件 % 调用函数文件:myfile.m clear clc for t=1:10 y=mylfg(t); fprintf('M^(1/3)=%6.4f ',t,y); end %自定义函数文件: mylfg.m function y=mylfg(x) %注意:函数名(mylfg)必须与文件名(mylfg.m)一致 Y...

C#调用WIN32的API(转贴)

小序 Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微软留给我们直接控制Windows的接口。想玩儿吗?呵呵,太难了。C#使用非常简单,写程序就像打拱猪,Sorry -_-! ,搭积木一样简单。想玩儿吗?呵呵,没办法直接控制Windows的核心...

多线程详解,一篇文章彻底搞懂多线程中各个难点

出自https://mp.weixin.qq.com/s/xh-LfrelAgFCRkP5-s-GLw 1.什么是线程? linux内核中是没有线程这个概念的,而是轻量级进程的概念:LWP。一般我们所说的线程概念是C库当中的概念。 1.1线程是怎样描述的? 线程实际上也是一个task_struct,工作线程拷贝主线程的task_struct,然后共用主线程...

在OC项目工程中混编Swift

1.创建一个OC项目工程,然后在Build Settings中找到如下字段,修改。 2.然后在项目中创建swift文件,如果系统提示是否需要创建桥接文件的时候,点击确定。 然后在Build Settings,查找swift, 如图所示,会显示两个文件,一个上桥接文件(在项目工程中可见), 另一个是swift编译的.h文件(在项目工程中不可见), 但是可以...

WSGI详解

WSGI接口 了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是: 浏览器发送一个HTTP请求; 服务器收到请求,生成一个HTML文档; 服务器把HTML文档作为HTTP响应的Body发送给浏览器; 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。 所以,最简单的Web应用就是先把HTML用文件保存好,用...