iOS编程基础-OC(九)-专家级技巧:使用运行时系统API(续)
来源:互联网 发布:js全局数组变量 编辑:程序博客网 时间:2024/06/03 20:41
该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
第九章 专家级技巧:使用运行时系统API
9.2 使用运行时系统API
接下来仍然是编写一段程序:
该程序会使用运行时系统API以动态的方式创建一个类和一个类实例;
然后以动态方式向该实例添加一个变量;
创建类RunningWidget用于说明;
在类中添加相应代码;(部分内容可参考第7章)
(Code_RunningWidget.m)
#import "C9RunningWidget.h"#import <objc/runtime.h>#import <objc/message.h>@implementation C9RunningWidget//用于显示选择器的方法实现函数static void display(id self,SEL _cmd){ NSLog(@"Invoking method with selector %@ on %@ instance",NSStringFromSelector(_cmd),NSStringFromClass([self class]));}-(void)test9_2{ //创建一个类对 Class widgetClass = objc_allocateClassPair([NSObject class], "C9Widget", 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); //创建一个widget实例并设置实例变量的值 id widget = [[widgetClass alloc] init]; id value = [NSNumber numberWithInteger:15]; [widget setValue:value forKey:[NSString stringWithUTF8String:height]]; NSLog(@"W height = %@",[widget valueForKey:[NSString stringWithUTF8String:height]]); //向widget实例发送一个消息 objc_msgSend(widget, NSSelectorFromString(@"display")); //Build Setting--> Apple LLVM 6.0 - Preprocessing--> Enable Strict Checking of objc_msgSend Calls 改为 NO //以动态方式向widget实例添加一个变量(关联对象) NSNumber * width = [NSNumber numberWithInteger:10]; objc_setAssociatedObject(widget, @"width", width, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取该变量的值并显示它 id result = objc_getAssociatedObject(widget, @"width"); NSLog(@"W width = %@",result); }@end
C9RunningWidget * test = [[C9RunningWidget alloc] init]; [test test9_2];
log:
2017-12-18 17:07:40.425307+0800 精通Objective-C[53341:5496447] W height = 15
2017-12-18 17:07:40.425491+0800 精通Objective-C[53341:5496447] Invoking method with selector display on C9Widget instance
2017-12-18 17:07:40.425634+0800 精通Objective-C[53341:5496447] W width = 10
从功能上讲,这段代码执行了如下操作:
1)定义了一个方法的实现参数;
2)创建并注册了一个类;
3)创建了一个类实例;
4)以动态方式向该实例添加一个变量;
我来逐个分析下;
9.2.1 定义方法的实现函数
OC的方法就是一个至少接收两个陈述(self和_cmd)的C语言参数;(第8章)
9.2.2 创建并注册类
要使用运行时系统API以动态的方式创建类,必须执行下列步骤:
1)新建一个类及其元类;
2)向这个类添加方法和实例变量(如果有的话);
3)注册新建的类;
运行时系统桉树class_addMethod参数:
应该添加方法的类;
设置被添加方法名称的选择器;
实现该方法的函数;
类型编码(描述方法的参数类型和返回值类型的字符串);
类型编码:
字符v代表void;
字符@代表SEL类型(https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100);
这里附上一张截图Objective-C type encodings:
(Pic9_1)
class_addMethod函数的类型参数中的编码必须以固定顺序排列:
第一个编码用于设置返回值类型;
第二个编码用于设置方法的隐式参数self(类型为id);
第三个编码用于设置方法的隐式参数_cmd(类型为SEL);
其他编码用于设置方法的显式参数的类型;
对应上边的这个表,就好理解各个编码的含义了;
9.2.3 创建类实例
创建一个widget实例并设置实例变量的值,然后调用了实例中的方法;
9.2.4 以动态的方式向类实例添加变量
OC没有提供向对象添加实例变量的功能;
然而,使用运行时系统的关联对象特性,可以高效地模拟这个功能;
关联对象:
指通过关键字引用的 附加到类实例中的对象;
我们可以在,不允许使用是实例变量的OC分类中使用关联对象;
在创建关联对象时,需要设置关联对象的关键字、关联对象的内存管理策略和它的值;
NSNumber * width = [NSNumber numberWithInteger:10];
objc_setAssociatedObject(widget, @"width", width, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
id result = objc_getAssociatedObject(widget, @"width");
运行时系统API含有一个枚举型值,其中含有内存管理策略的所有可能值;
OBJC_ASSOCIATION_RETAIN_NONATOMIC:向关联对象分配非原子性的强引用;(和nonatomic和strong特性的属性类似)
本示例展示了使用运行时系统API以动态的方式创建类、类实例和关联对象的方式;
参阅Apple objective-c runtime programming guide,可以学习更多;
9.3 创建动态代理
本节将实现本章的最后一个示例程序:
如何使用Foundation框架中的NSInvocation和NSProxy类,实现动态消息转发功能;
OC提供了多个消息转发选项(第三章):
使用NSObject类的forwardingTargetForSelector:方法实现的快速转发;
以及使用NSObject类的forwardInvocation:方法实现标准(完全)转发;
标准转发的一大优点是使程序能够对对象的消息、参数和返回值执行额外的处理操作;
将该功能与NSProxy类一起使用,可以在OC中获得不错的面向切面编程(AOP)机制;
AOP是一种编程范式,其目的是通过横切(cross-cutting)将(依赖或影响程序的其他部分)功能与程序中的其他组成部分分隔开;提高程序的模块化程度;
NSProxy是Foundation框架中的一个类,专门用于创建代理,其作用就像真正类的接口;
下面我们通过创建一个NSProxy类的子类并实现forwardInvocation:方法:
从而使消息能够发送到应用了横切功能的真正类;
程序的结构可以是这样的:
(Pic9_2)
接下来分步理解下,先从Invoker协议开始;
9.3.1 创建Invoker协议
我们新建了一个文件夹9.3AspectProxy,用于存放这里的类;
首先创建一个协议和一个遵守该协议的类;
选择OC的协议模板,命名为Invoker;添加必要的代码;
(Code_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
临时对代理/协议有了点理解;
在一个类M中导入一个协议P,然后在这个类的.m中实现该协议方法;
M.h中的接口就被协议P扩展了,但是用来扩展接口的协议P只是提供了接口,其实现则需要由被扩展了的M在其.m中进行实现;
这样通过这种接口扩展的方式,我们可以给不同继承体系的类提供统一的行为;
我们来看一下这个协议中定义的两个接口;
-(void)preInvoke:(NSInvocation *)inv withTarget:(id)target;
这是一个必要方法,所有遵循该协议的类都必须实现这个方法;
能够在调用对象中的方法前执行对功能的横切;
-(void)postInvoke:(NSInvovation *)inv withTarget:(id)target;
是一个可选方法;
能够在调用对象中的方法后执行对功能的横切;
接着我们编写一个遵循该协议的类AuditingInvoker,实现横切功能;
(Code_AuditingInvoker.h,AuditingInvoker.m)
#import <Foundation/Foundation.h>#import "Invoker.h"@interface AuditingInvoker : NSObject<Invoker>@end
#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]),NSStringFromClass([target class]));}-(void)postInvoke:(id)inv withTarget:(id)target{ NSLog(@"Creating audit log after sending message with selector %@ to %@ object",NSStringFromSelector([inv selector]),NSStringFromClass([target class]));}@end
实现了横切功能之后,我们再来处理下NSproxy子类;
9.3.2 编写代理类
下面创建的代理类,它是NSProxy的子类;
它会实现消息转发方法forwardInvocation:和methodSignatureForSelector:;
我们来新建这个类AspectProxy;继承自NSProxy;
(Code_AspectProxy.h,AspectProxy.m)
#import <Foundation/Foundation.h>#import "Invoker.h"@interface AspectProxy : NSProxy@property (nonatomic , strong) id proxyTarget;//该属性是通过NSProxy实例转发消息的真正对象;@property (nonatomic , strong) id<Invoker> invoker;//该属性是一个能够实现横切功能的类(遵循Invoker协议)的实例;@property (nonatomic , strong , 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
#import "AspectProxy.h"@implementation AspectProxy-(id)initWithObject:(id)object andInvoker:(id<Invoker>)invoker{ return [self initWithObject:object selectors:@[] andInvoker:invoker];}-(id)initWithObject:(id)object selectors:(NSArray *)selectors andInvoker:(id<Invoker>)invoker{ _proxyTarget = object; _invoker = invoker; _selectors = [selectors mutableCopy]; return self;}-(void)registerSelector:(SEL)selector{ NSValue * selValue = [NSValue valueWithPointer:selector]; [self.selectors addObject:selValue];}//该方法会为目标对象中被调用的方法返回一个NSMethodSignature实例//运行时系统要求在执行标准转发时实现这个方法-(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]; break; } } }else{// [[self invoker] preInvoke:invocation withTarget:self.proxyTarget]; } } //调用目标方法 [invocation invokeWithTarget:self.proxyTarget];//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] postInvoke:invocation withTarget:self.proxyTarget]; break; } } }else{// [[self invoker] postInvoke:invocation withTarget:self.proxyTarget]; } } }@end
来看一下这段代码:
初始化方法初始化了相应的AspectProxy对象的实例;
注意,因为NSProxy是一个基类,所以这些方法的开头没有调用[super init];
当调用目标方法的选择器与在AspectProxy对象中注册的选择器匹配时,forwardInvocation:方法会调用目标对象中的方法,并根据条件语句的判断结果调用AOP功能;
现在再回看之前的Pic9_2,是不是就清晰了;
最终向AspectProxy代理对象发送原对象可接收的消息,而不是直接向原对象发送消息;
我们来测试下;
9.3.3 测试AspectProsy程序
我们是用第七章的C7Calculator类(还记得动态方法决议不?)来进行测试;
该类声明了下列方法:
-(NSNumber *)sumAddend1:(NSNumber *)adder1 addend2:(NSNumber *)adder2;
-(NSNumber *)sumAddend1:(NSNumber *)adder1 :(NSNumber *)adder2;//这个方法的第二个参数使用空参数
//创建对象 id calculator = [[C7Calculator alloc] init]; NSNumber * addend1 = [NSNumber numberWithInteger:-1]; NSNumber * addend2 = [NSNumber numberWithInteger:2]; NSNumber * addend3 = [NSNumber numberWithInteger:5]; //为该对象创建代理 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]; //使用指定的选择器向该代理发送消息 [calculatorProxy sumAddend1:addend1 addend2:addend2]; //使用没有特殊处理的其他选择器向该代理发送消息 [calculatorProxy sumAddend1:addend2 :addend3]; //为这个代理注册另一个选择器并再次向其发送消息 [calculatorProxy registerSelector:@selector(sumAddend1::)]; [calculatorProxy sumAddend1:addend2 :addend3];
log:
2017-12-21 11:42:53.211276+0800 精通Objective-C[83628:6659416] Creating audit log before sending message with selector sumAddend1:addend2: to C7Calculator object
2017-12-21 11:42:53.211419+0800 精通Objective-C[83628:6659416] Invoking method on C7Calculator object with selector sumAddend1:addend2:
2017-12-21 11:42:53.257332+0800 精通Objective-C[83628:6659416] Creating audit log after sending message with selector sumAddend1:addend2: to C7Calculator object
2017-12-21 11:42:53.257634+0800 精通Objective-C[83628:6659416] Invoking method on C7Calculator object with selector sumAddend1::
2017-12-21 11:42:53.257853+0800 精通Objective-C[83628:6659416] Creating audit log before sending message with selector sumAddend1:: to C7Calculator object
2017-12-21 11:42:53.258002+0800 精通Objective-C[83628:6659416] Invoking method on C7Calculator object with selector sumAddend1::
2017-12-21 11:42:53.258146+0800 精通Objective-C[83628:6659416] Creating audit log after sending message with selector sumAddend1:: to C7Calculator object
分析:
调用C7Calculator对象的代理中的sumAddend1:addend2:方法时;
会调用AuditingInvoker对象中的preInvoker:,postInvoker:方法;
还会调用真正目标(C7Calculator对象)中的sumAddend1:addend2:方法;
方法通过AspectProxy代理进行注册,才会调用AuditingInvoker对象中的AOP方法;
不注册,就不会调用;
9.4 小结
本章介绍了3个程序,我们使用OC运行时及其API完成了这些程序;
还有创建可选包和使用Xcode将代码导入工程的方法;
好好复习一下第二部分的内容,接下来将开始第三部分Foundation框架的介绍。
- iOS编程基础-OC(九)-专家级技巧:使用运行时系统API(续)
- iOS编程基础-OC(九)-专家级技巧:使用运行时系统API
- iOS编程基础-OC(六)-专家级技巧:使用ARC
- iOS编程基础-OC(七)-运行时系统
- iOS编程基础-OC(七)-运行时系统(续)
- iOS编程基础-OC(八)-运行时系统的结构(续)
- iOS编程基础-OC(八)-运行时系统的结构
- iOS编程基础-OC(四)-内存管理(续)
- [精通Objective-C]进阶技巧:使用运行时系统API
- iOS编程基础-OC(十一)-Foundation框架中的系统服务:网络、应用及文件系统服务
- iOS编程基础-OC(十一)-Foundation框架中的系统服务:并发机制和线程
- 黑马程序员—(iOS开发)OC开发技巧及核心语法---(九)
- OC基础回顾(九)对象初始化
- iOS编程基础-OC(三)-对象和消息传递
- iOS编程基础-OC(四)-内存管理
- iOS编程基础-OC(五)-预处理器
- OC 运行时系统
- 黑马程序员-iOS基础-Objective-C基础(四)OC开发技巧及核心语法(上)
- cocos layer触摸事件不响应的问题
- mysql 日期函数to_days注意事项
- python与自然语言处理之贝叶斯实战
- 用Swift写一个响应式编程库
- Ubuntu16.04 下安装单机版 hadoop
- iOS编程基础-OC(九)-专家级技巧:使用运行时系统API(续)
- ListView适配器
- mac 升级到php7
- ASP 基本画图
- Qt Designer功能
- 工信部最新印发AI三年行动计划,包含了这些重点(附全文)
- 随手画个圆,你是怎么画的?我们分析了10万个圆,得到了这样的结论
- pycharm下如何查看python的变量类型和变量内容
- git时光机(查看Git版本历史)