社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
【IOS 开发学习总结-OC-23】★objective-c的反射机制
objective-c 提供了3种编程方式与运行环境交互。
objective-c程序中获得 class 通常有3种方式:
1. 调用某个类的 class 方法来获取该类对应的 class。推荐这种方式———代码更安全(编译时检查 class 对象是否存在),程序性能更高(无需调用方法)。
2. 使用 class NSClassFromString(NSStrin* aClassName) 函数来获得 class——该函数需要传入字符串参数,该参数 是某个类的类名。
3. 调用某个对象的 class 方法——该方法为 NSObject 的一个方法,所有的 oc 对象都可以调用该方法。
示例程序:
#import <Foundation/Foundation.h>
int main(int argc , char * argv[])
{
@autoreleasepool{
// 通过字符串来获取Class
Class clazz = NSClassFromString(@"NSDate");
NSLog(@"%@" , clazz);
// 直接使用Class来创建对象
id date = [[clazz alloc] init];
NSLog(@"%@" , date);
// 通过对象来获取Class
NSLog(@"%@" , [date class]);
// 通过类来获取class
NSLog(@"%d" , clazz == NSDate.class);
}
}
编译运行结果:
2015-09-29 18:21:09.590 923[5960:414788] NSDate
2015-09-29 18:21:09.600 923[5960:414788] 2015-09-29 10:21:09 +0000
2015-09-29 18:21:09.600 923[5960:414788] __NSTaggedDate
2015-09-29 18:21:09.600 923[5960:414788] 1
由上面的代码可以看出,调用 class 的 alloc 方法创建的并不是 class 的实例,而是该 class 对应的类的实例。
确认一个类的继承关系可以调用 NSObject 提供的如下方法进行判断:
⭐️-isKindOfClass:
需要传入一个 Class 参数,判断该对象是否为该类及其子类的实例;
⭐️-isMemberOfClass:
需要传入一个 Class 参数,判断该对象是否为该类的实例———比-isKindOfClass:
更为严格;
⭐️-conformsToProtocol:
需要传入一个 Protocol参数,判断该对象是否为该类及其子类的实例;——为了获取Protocol参数,可以使用如下2个方法:
1. objective-c 提供的@protocol 指令来实现;
2. 可以调用例如Protocol* abd=[NSProtocolFromString(<#NSString * _Nonnull namestr#>)];
的方法根据协议名字符串获取对应的协议。
示例代码:
FKEatable.h
#import <Foundation/Foundation.h>
// 定义协议
@protocol FKEatable
@optional
- (void) taste;
@end
FKApple.h
#import <Foundation/Foundation.h>
#import "FKEatable.h"
// 定义类的接口部分,实现FKEatable协议
@interface FKApple : NSObject <FKEatable>
@end
FKApple.m
#import "FKApple.h"
// 为FKApple提供实现部分
@implementation FKApple
@end
checkObject.m
#import <Foundation/Foundation.h>
#import "FKApple.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKApple* app = [[FKApple alloc] init];
// 通过对象来判断该对象的Class
NSLog(@"%@" , [app class]);
// 判断对象是否为某个类的实例
NSLog(@"app是否为FKApple的实例:%d",
[app isMemberOfClass: FKApple.class]);
NSLog(@"app是否为NSObject的实例:%d",
[app isMemberOfClass: NSObject.class]);
// 判断对象是否为某个类及其子类的实例
NSLog(@"app是否为FKApple及其子类的实例:%d",
[app isKindOfClass: FKApple.class]);
NSLog(@"app是否为NSObject及其子类的实例:%d",
[app isKindOfClass: NSObject.class]);
// 判断对象是否实现了指定协议
NSLog(@"app是否实现FKEatable协议:%d",
[app conformsToProtocol: @protocol(FKEatable)]);
}
}
编译运行结果;
2015-09-29 18:51:20.621 923[6083:426035] FKApple
2015-09-29 18:51:20.623 923[6083:426035] app是否为FKApple的实例:1
2015-09-29 18:51:20.623 923[6083:426035] app是否为NSObject的实例:0
2015-09-29 18:51:20.624 923[6083:426035] app是否为FKApple及其子类的实例:1
2015-09-29 18:51:20.624 923[6083:426035] app是否为NSObject及其子类的实例:1
2015-09-29 18:51:20.624 923[6083:426035] app是否实现FKEatable协议:1
如果程序需要访问对象实例变量的值,程序都可以通过 KVC 机制来设置,访问实例变量的值——不管该实例变量是在类的接口部分定义,还是在类的实现部分定义,也不管该变量使用哪种访问控制符修饰,
使用方法:对象 respondsToSelector:<#(SEL)#>
——需要传入一个 SEL参数(objective-c 使用 SEL对象来代表方法),如果该对象可以调用该方法,返回YES,否则返回 NO。
那么如何在程序中动态获取 SEL 对象呢?
可用方法如下:
1. 使用@selector 指令来获取当前类中指定的方法。——需要用完整的方法签名关键字作为参数,仅有方法名不够。
2. 使用 SEL NSSelectorFromString(<#NSString * _Nonnull aSelectorName#>)
函数根据方法签名关键字的字符串获取对应的方法。
那如何动态调用对象的普通方法呢?有2种方法:
1. 通过 performSelector:<#(SEL)#>
方法实现,如果调用方法需要传入参数,还可以通过withObject:<#(id)#>
标签来实现,
2. 使用objc_msgSend(receiver,selector,...)
函数来调用。——第一个参数是方法调用者,第二个参数是调用的方法,接下来的参数将作为调用方法的参数。
示例代码:
FKCar.h
#import <Foundation/Foundation.h>
// 定义类的接口部分
@interface FKCar : NSObject
@end
FKCar.m
#import <objc/message.h>
#import "FKCar.h"
// 为FKCar提供实现部分
@implementation FKCar
- (void) move:(NSNumber*) count
{
int num = [count intValue];
for (int i = 0 ; i < num ; i++)
{
NSLog(@"%@", [NSString stringWithFormat
:@"汽车正在路上走~~%d" , i]);
}
}
- (double) addSpeed:(double) factor
{
// 此处希望能动态调用move方法
// 使用performSelector:动态调用move:方法
[self performSelector:@selector(move:)
withObject: [NSNumber numberWithInt: 2]];
[self performSelector:NSSelectorFromString(@"move:")
withObject: [NSNumber numberWithInt: 2]];
// 使用objc_msgSend()函数动态调用move:方法
objc_msgSend(self,@selector(move:),[NSNumber numberWithInt: 3]);
objc_msgSend(self , NSSelectorFromString(@"move:")
, [NSNumber numberWithInt: 3]);
NSLog(@"正在加速。。。%g" , factor);
return 100 * factor;
}
@end
这个示例也说明了 objective-c 中并没有真正的私有的方法。
NSObject 提供的方法-(IMP)methodForSelector:<#(SEL)#>
——该方法根据传入的 SEL 参数,返回该方法对应的 IMP 对象。
IMP ——代表指向 objective-c 方法的函数指针。相当于一个指向函数的指针变量,也就代表了函数的入口。接下来就可以通过 IMP 来调用该函数——也就是调用了 OC 的方法。
对于一个指向 objective-c 方法的函数指针变量,它指向的函数签名的第一参数,通常是方法的调用者,第二个参数通常是方法对应的 SEL对象,接下来是调用方法的参数。因此,通常会用如下代码格式定义指向 OC 方法的函数变量。
返回值类型 (*指针变量名)(id,SEL,....);
一旦获取了指向 OC方法 的函数指针变量,就可以通过它来访问函数——执行 OC 的方法
示例程序:
#import <Foundation/Foundation.h>
#import <objc/message.h>
#import "FKCar.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 获取FKCar类
Class clazz = NSClassFromString(@"FKCar");
// 动态创建FKCar对象
id car = [[clazz alloc] init];
// 使用performSelector:方法来动态调用方法
[car performSelector:@selector(addSpeed:)
withObject: [NSNumber numberWithDouble:3.4]];
// 使用objc_msgSend()函数动态调用方法
objc_msgSend(car , @selector(addSpeed:) , 3.4);
// 定义函数指针变量
double (addSpeed*)(id , SEL , double) ;
// 获取car对象的addSpeed:方法,并将该方法赋给addSpeed函数指针变量
addSpeed = (double(*)(id ,SEL , double))[car
methodForSelector:NSSelectorFromString(@"addSpeed:")];
// 通过addSpeed函数指针变量来调用car对象的方法
double speed = addSpeed(car , @selector(addSpeed:) , 2.4);
// 输出调用方法的返回值
NSLog(@"加速后的速度为:%g" , speed);
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!