1、OC提供了3种编程方式与运行环境进行交互:
2、OC中同样也提供了与Java中类似的反射机制,这种动态变成机制可以让OC语言更加灵活。说到反射,首先我们要弄清楚什么是反射,反射的定义是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变。通俗的讲就是反射可以在运行时根据指定的类名获得类的信息。
3、为什么要用反射,也就是反射的意义何在?
4、每个类都有一个对应的Class,Class
对象其实本质上就是一个结构体,这个结构体中的成员变量还是自己,这种设计方式非常像链表的数据结构。然后通过一个类的Class可以实现获取该类的实例变量、方法等信息,从而可以实现创建对象和调用方法的目的。OC中获得Class通常有3种方法:
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只是这个类簇的前端,当程序调用[[NSDate alloc] init] 创建对象时,程序实际返回的只是NSDate的子类(_NSDate)的实例,而不是NSDate的实例。因此直接调用date来获取Class时返回的是_NSDate。
5、程序中在才用反射机制创建类时一般都需要对创建的对象或者待反射的对象进行一个继承或从属关系的检查,即需要判断一个对象是否是某个类的实例或者是否是某个类或者其子类的实例。闫完成这样的工作,我们可以直接调用NSObject提供的如下方法进行判断:
6、如果程序需要动态地调用对象的setter、getter方法,则可通过OC提供的KVC机制来实现。如果程序需要访问对象的实例变量的值,那么不管这个实例变量是否在类的接口部分定义,也不管该变量使用哪种访问控制符修饰,或者是否在类的实现部分定义,程序都可通过KVC机制来设置、访问实例变量的值。(具体KVC机制的原理后面学习了再补充:OC学习篇之---KVC和KVO操作)
7、如果程序需要判断某个对象是否可调用方法,则可通过NSObject的如下方法进行判断:
如果程序需要动态调用对象的普通方法,则可以通过如下两种方式来实现:
为了在程序中动态获得SEL对象,OC提供了如下两种方法来获得:
1 #import <Foundation/Foundation.h>
2
3 //定义接口部分
4 @interface FKCar : NSObject
5 @end
6
7
8 #import <objc/message.h>
9 #import "FKCar.h"
10
11 //实现部分
12 @implementation FKCar
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代表指向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的反射、动态机制来实现,这也回到了我们前面讲的为什么要用反射机制的原因。