一.使用可加载的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框架的NSInvocation
和NSProxy
类来实现动态的消息转发(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];
)