关闭

使用运行时APIs

107人阅读 评论(0) 收藏 举报
分类:

一.使用可加载的Bundles来扩展程序

1.创建一个可加载的Bundle:

使用Xcode新建一个工程,在OS X下选择Framework & Library,然后选择Bundle,如图:

为工程取个名称,创建:

我们在工程中创建一个类,并编写一个-方法:-(void)greetWithName:(NSString*)name;,代码如下

Greet.h文件代码:

#import <Foundation/Foundation.h>

@interface Greet : NSObject

-(void)greetWithName:(NSString*)name;

@end

Greet.m文件代码:

#import "Greet.h"

@implementation Greet

-(void)greetWithName:(NSString *)name{
    NSLog(@"Hello,%@",name);
}

@end

选中刚才中的.bundle文件,然后编译:

2.在另一个工程中加载这个包

在Xcode中新建一个OS X的命令行工程,在main()函数中添加代码:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        id greete;

        NSString* bundlePath;
            //获取路径字符串
            bundlePath=@"...New.bundle";
            //根据路径寻找bundle
            NSBundle* greeterBundle=[NSBundle bundleWithPath:bundlePath];
            if (greeterBundle==nil) {
                NSLog(@"Bundle not found at path");
            }else{

                NSError* error;
                BOOL isLoaded=[greeterBundle loadAndReturnError:&error];
                if (!isLoaded) {
                    //包加载失败
                    NSLog(@"Error = %@",[error localizedDescription]);
                }else{

                    Class greeteClass=[greeterBundle classNamed:@"Greet"];

                    SEL selector=NSSelectorFromString(@"greetWithName:");
                    NSLog(@"OK");
                    greete=[[greeteClass alloc] init];
                    [greete performSelector:selector withObject:@"WflytoC"];

                    greete=nil;
                    BOOL isUnloaded=[greeterBundle unload];
                    if (!isUnloaded) {
                        NSLog(@"Couldnt unload bundle");
                    }

                }

            }           

        }


    return 0;
}

结果如下:

二.使用运行时APIs

现在,你将创建一个程序,使用运行时API来动态地创建一个类、一个类实例,然后动态地为实例添加一个变量。


#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

static void display(id self,SEL _cmd){
    NSLog(@"Invoking method with selector %@ on %@ instance",NSStringFromSelector(_cmd),[self className]);
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {

        //创建一个类组
        Class widgetClass=objc_allocateClassPair([NSObject class], "Widget", 0);

        //添加一个方法到类中
        const char *types="v@:";
        class_addMethod(widgetClass, @selector(display), (IMP)display, types);

        //添加一个变量到类中
        const char *height="height";
        class_addIvar(widgetClass, height,sizeof(id), rint(log2(sizeof(id))),@encode(id));

        //注册类
        objc_registerClassPair(widgetClass);

        //创建widgetClass实例,并设置ivr的值
        id widget=[[widgetClass alloc] init];
        id value=[NSNumber numberWithInt:15];
        [widget setValue:value forKey:[NSString stringWithUTF8String:height]];
        NSLog(@"widget instance height = %@",[widget valueForKey:[NSString stringWithUTF8String:height]]);

        //发送消息
        objc_msgSend(widget,NSSelectorFromString(@"display"));

        //动态地添加一个变量到对象上
        NSNumber *width=[NSNumber numberWithInt:10];
        objc_setAssociatedObject(widget, @"width", width, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        //获取变量的值,并且展示它
        id result=objc_getAssociatedObject(widget, @"width");
        NSLog(@"widget instance width = %@",result);


        }


    return 0;
}

我们来分析一下上面的代码:

1.定义方法实现

其实,Objective-C的方法就是仅仅是一个C语言的函数,只不过该函数至少要有两个参数:self_cmd。导入必要的运行时头文件后,我们定义了一个函数,稍后将用来添加一个方法到类中。

static void display(id self,SEL _cmd){
    NSLog(@"Invoking method with selector %@ on %@ instance",NSStringFromSelector(_cmd),[self className]);
}

2.创建并注册一个Class

为了使用运行时APIs动态地创建一个类,你必须执行一下几步操作:

  • 创建一个新类和元类
  • 添加方法和实例变量到类中
  • 注册刚刚创建的类

一个功能是通过下面代码实现的:

//创建一个类组
        Class widgetClass=objc_allocateClassPair([NSObject class], "Widget", 0);

        //添加一个方法到类中
        const char *types="v@:";
        class_addMethod(widgetClass, @selector(display), (IMP)display, types);

        //添加一个变量到类中
        const char *height="height";
        class_addIvar(widgetClass, height,sizeof(id), rint(log2(sizeof(id))),@encode(id));

        //注册类
        objc_registerClassPair(widgetClass);

运行时的class_addMethod()函数有四个参数,分别是:方法所要添加到的类、声明了所要添加方法的名字的selector、实现了方法的函数、一个描述参数和返回值类型的字符串(即type encodings字符编码)。其中”v”代表void,”@”代表对象,”:”代表selector,更详细的信息请看:苹果官方的Type Encoding,在本例中,void display(id self,SEL _cmd)函数的返回值为 void,对应”v”,第一个参数为id对象,对应”@”,第二个参数为SEL,对应”:”,所以为”v@:”。

利用BOOL class_addIvar()来为被动态创建的类添加实例属性,这个函数只能在objc_allocateClassPair之后和objc_registerClassPair之前执行,不能用它来为已经存在的类添加实例变量。

3.动态添加变量到类的实例中

Objective-C不支持向对象中添加实例变量,但是运行时的一个特点:associated objects(关联对象),可以用来弥补这个缺憾。关联对象,指的是固定到类实例上的对象,由一个键来引用。当你创建一个关联对象时,你要声明一个键来将与类实例的关联,关联对象的内存管理方案,值映射出去。上面的代码展示了利用运行时APIs来对关联对象的使用:

//动态地添加一个变量到对象上
        NSNumber *width=[NSNumber numberWithInt:10];
        objc_setAssociatedObject(widget, @"width", width, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        //获取变量的值,并且展示它
        id result=objc_getAssociatedObject(widget, @"width");
        NSLog(@"widget instance width = %@",result);

运行时API包含了一个枚举,涵盖了可能的内存管理方案。这个例子中,OBJC_ASSOCIATION_RETAIN_NONATOMIC给关联对象分配了nonatomic、strong的引用。

当你的程序运行时,结果如图:

三.创建一个动态的代理(Proxy)

下面的程序展示了使用Foundation框架的NSInvocationNSProxy类来实现动态的消息转发(message forwarding)。

关于消息转发,简单地说,就是当我们向对象发送消息时,系统查看这个对象能否接收这个消息,如果不能并且只在不能的情况下,就会调用几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,具体细节请看 Objective-C消息转发

Objective-C提供了几种消息转发选项:使用NSObject的forwardingTargetForSelector:方法的快速转发和使用NSObject的forwardInvocation:方法的普通(或完全)转发。

使用普通转发的好处之一就是它能够让你对消息进行额外的处理,它的参数和它的返回值。普通转发与NSProxy一起使用的话,就为在Objective-C中实现切面编程(aspect-oriented programming即AOP)提供了完美的机制。NSProxy是专门为代理设计的Foundation框架类,它作为真正类的接口。

先看示例代码:

1.创建协议Invoker

Invoker.h代码文件

#import <Foundation/Foundation.h>

@protocol Invoker <NSObject>

@required

-(void)preInvoke:(NSInvocation *)inv withTarget:(id)target;

@optional

-(void)postInvoke:(NSInvocation *)inv withTarget:(id)target;

@end

2.创建遵守Invoker协议的类AuditingInvoker

AuditingInvoker.h文件代码

#import <Foundation/Foundation.h>
#import "Invoker.h"

@interface AuditingInvoker : NSObject<Invoker>

@end

AuditingInvoker.m文件代码

#import "AuditingInvoker.h"

@implementation AuditingInvoker

-(void)preInvoke:(NSInvocation *)inv withTarget:(id)target{
    NSLog(@"Creating audit log before sending message with selector %@ to %@ object",NSStringFromSelector([inv selector]),[target className]);

}

-(void)postInvoke:(NSInvocation *)inv withTarget:(id)target{
    NSLog(@"Creating audit log after sending message with selector %@ to %@ object",NSStringFromSelector([inv selector]),[target className]);
}


@end

3.创建NSProxy子类AspectProxy

AspectProxy.h文件代码

#import <Foundation/Foundation.h>
#import "Invoker.h"

@interface AspectProxy : NSProxy

@property(strong)id proxyTarget;
@property(strong)id<Invoker> invoker;
@property(readonly)NSMutableArray *selectors;

-(id)initWithObject:(id)object andInvoker:(id<Invoker>)invoker;
-(id)initWithObject:(id)object selectors:(NSArray*)selectors andInvoker:(id<Invoker>)invoker;

-(void)registerSelector:(SEL)selector;


@end

AspectProxy.m文件代码

#import "AspectProxy.h"

@implementation AspectProxy

-(id)initWithObject:(id)object selectors:(NSArray *)selectors andInvoker:(id<Invoker>)invoker{
    _proxyTarget=object;
    _selectors=[selectors mutableCopy];
    _invoker=invoker;
    return self;
}

-(id)initWithObject:(id)object andInvoker:(id<Invoker>)invoker{
    return [self initWithObject:object selectors:nil andInvoker:invoker];
}

-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.proxyTarget methodSignatureForSelector:sel];
}

-(void)forwardInvocation:(NSInvocation *)invocation{

    if ([self.invoker respondsToSelector:@selector(preInvoke:withTarget:)]) {
        if (self.selectors!=nil) {
            SEL methodSel=[invocation selector];
            for(NSValue *selValue in self.selectors){
                if (methodSel==[selValue pointerValue]) {
                    [[self invoker] preInvoke:invocation withTarget:self.proxyTarget];
                }
            }
        }else{
            [[self invoker] preInvoke:invocation withTarget:self.proxyTarget];
        }
    }

    [invocation invokeWithTarget:self.proxyTarget];


    if ([self.invoker respondsToSelector:@selector(postInvoke:withTarget:)]) {
        if (self.selectors!=nil) {
            SEL methodSel=[invocation selector];
            for(NSValue *selValue in self.selectors){
                if (methodSel==[selValue pointerValue]) {
                    [[self invoker] preInvoke:invocation withTarget:self.proxyTarget];
                }
            }
        }else{
            [[self invoker] postInvoke:invocation withTarget:self.proxyTarget];
        }
    }


}

-(void)registerSelector:(SEL)selector{
    NSValue* selValue=[NSValue valueWithPointer:selector];
    [self.selectors addObject:selValue];
}

@end

methodSignatureForSelector:的实现为目标对象中即将被调用的方法返回了方法签名实例,而运行时系统要求:在执行普通的消息转发时,这个方法必须要实现。forwardInvocation:的实现部分调用了目标对象上的方法,并且有选择性地调用了切面编程功能,条件就是目标对象上被激活的方法的selector是否与在AspectProxy对象上注册的selectors搭配。

4.创建测试AspectProxy类的Calculator类。

Calculator.h文件代码

#import <Foundation/Foundation.h>

@interface Calculator : NSObject

-(NSNumber*) sumAddend1:(NSNumber*)adder1 addend2:(NSNumber*)adder2;
-(NSNumber*) sumAddend1:(NSNumber*)adder1 :(NSNumber*)adder2;

@end

Calculator.m文件代码

#import "Calculator.h"

@implementation Calculator

-(NSNumber *)sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2{
    NSLog(@"Invoking method on %@ object with selector %@",[self class],NSStringFromSelector(_cmd));
    return [NSNumber numberWithInteger:([adder1 integerValue]+[adder2 integerValue])];
}

-(NSNumber *)sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2{
    NSLog(@"Invoking method on %@ object with selector %@",[self class],NSStringFromSelector(_cmd));
    return [NSNumber numberWithInteger:([adder1 integerValue]+[adder2 integerValue])];
}

@end

5.main()函数

main.m文件代码

#import <Foundation/Foundation.h>
#import "AspectProxy.h"
#import "AuditingInvoker.h"
#import "Calculator.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        id calculator=[[Calculator alloc] init];
        NSNumber* addend1=[NSNumber numberWithInteger:-25];
        NSNumber* addend2=[NSNumber numberWithInteger:10];
        NSNumber* addend3=[NSNumber numberWithInteger:15];

        NSValue* selValue1=[NSValue valueWithPointer:@selector(sumAddend1:addend2:)];
        NSArray* selValues=@[selValue1];
        AuditingInvoker *invoker=[[AuditingInvoker alloc] init];
        id calculatorProxy=[[AspectProxy alloc] initWithObject:calculator selectors:selValues andInvoker:invoker];
        //使用一个给定的selector发送消息给proxy代理
        [calculatorProxy sumAddend1:addend1 addend2:addend2];

        [calculatorProxy sumAddend1:addend2 :addend3];

        [calculatorProxy registerSelector:@selector(sumAddend1::)];

        [calculatorProxy sumAddend1:addend1 :addend3];

    }
    return 0;
}

运行结果如图:

对这个程序总体解释下:在方法initWithObject:(id)object selectors:(NSArray *)selectors andInvoker:(id<Invoker>)invoker中,object(即proxyTarget属性)就是指Calculator对象,invoker(即invoker属性)就是指AuditingInvoker

main()函数给AspectProxy类型的calculatorProxy变量发送消息,但是AspectProxy类类中并没有定义,所以便会执行methodSignatureForSelector:方法和methodSignatureForSelector:方法。在这两个方法中,将消息转发给了Calculator对象来执行([invocation invokeWithTarget:self.proxyTarget];)

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15571次
    • 积分:600
    • 等级:
    • 排名:千里之外
    • 原创:31篇
    • 转载:6篇
    • 译文:15篇
    • 评论:1条
    最新评论