【IOS 开发学习总结-OC-23】★objective-c的反射机制 - Go语言中文社区

【IOS 开发学习总结-OC-23】★objective-c的反射机制


【IOS 开发学习总结-OC-23】★objective-c的反射机制

objective-c 提供的与运行环境交互的方式

objective-c 提供了3种编程方式与运行环境交互。

  1. 直接通过 oc 源代码。——最常见的方式。其中,数据结构负责保存类,类别中定义的数据,而函数负责处理方法调用。
  2. 通过 NSObject 类中定义的方法进行动态编程。
    大部分对象都可以直接调用NSObject 的方法进行编程——绝大部分类都是NSObject 类的子类(NSProxy例外)。少数情况下,NSObject只提供了方法模板,并没有给方法提供实现代码。
  3. 直接调用运行时函数进行动态编程。

获得 class

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,...)函数来调用。——第一个参数是方法调用者,第二个参数是调用的方法,接下来的参数将作为调用方法的参数。
Alt text
示例代码:
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);
    }
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/senwin2009/article/details/48809407
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-19 12:48:28
  • 阅读 ( 1404 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢