runtime运行机制方法学习
runtime这玩意第一次听说时都不知道是什么,经过了解后才知道它就是oc动态语言的机制,没有它那oc就不能称为动态语言。在之前可能大家对runtime了解都不深,随着编程技能的日益加深和需要,大家开始更加关心底层的实现,并用自己更需要的方式实现。这时runtime开始慢慢火起来了,作为一个iOS程序员,如果出去说自己不知道runtime无疑是一件很丢分的事情。由于runtime的底层实现笔者也没搞得太明白,在这里就跟大家提提几个关于runtime的方法。
1.runtime之动态添加属性
在最开始学习oc之时,我相信大家都听过,在类目中是无法添加属性的。而我在这里将要实现给UIButton增加一个block属性,方便实现其点击方法。首先创建一个UIButton的类目(UIButton+Block),接着我们来实现
1 UIButton+Block.h文件 2 //定义一个block 3 typedef void(^ActionBlock)(UIButton *sender); 4 5 @interface UIButton (Block) 6 //定义方法当button通过某点击状态使用一个block 7 - (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block; 8 @end
.h文件较为简单一看明白不多做解释,接下来是.m文件
1 //定义添加属性所对应的关键字 2 static char *overViewKey; 3 @implementation UIButton (Block) 4 5 - (void)handleClickEvent:(UIControlEvents)aEvent UsingBlock:(ActionBlock)block{ 6 //设置关联对象 (被关联者,关键字,关联者,属性状态) 7 objc_setAssociatedObject(self, &overViewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC); 8 //给button增加点击事件 9 [self addTarget:self action:@selector(buttonClicked:) forControlEvents:aEvent]; 10 } 11 12 - (void)buttonClicked:(UIButton *)sender{ 13 //获取关联对象 (被关联者,关键字) 14 ActionBlock clickedBlock = objc_getAssociatedObject(self, &overViewKey); 15 //如果block存在则调用block 16 if (clickedBlock != nil) { 17 clickedBlock(sender); 18 } 19 }
在这里 objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
便为runtime方法。此方法有4个参数,第一个为类别:self(给谁增加属性),第二个参数为属性关键字:&overViewKe(作为属性的标示符来查找属性),第三个参数为属性:block(要添加的属性),第四个参数为属性修饰符:OBJC_ASSOCIATION_COPY_NONATOMIC(作为属性的修饰符,nonatomic,copy)。通过此方法我们便可以动态的给一个类增加属性了。
属性增加后我们便需要获取此属性 objc_getAssociatedObject(<#id object#>, <#const void *key#>),此方法就较为简单了。在这里需要注意的是属性关键字,由于要保证属性关键字的作用域,所以在这里使用static作为其修饰符,另外由于runtime机制属于c语言实现,因此这里的关键字需要使用char类型!
这里定义好后的使用就简单了直接一句代码使用
1 //定义一个uibutton 并使用其方法 2 [button handleClickEvent:UIControlEventTouchUpInside UsingBlock:^(UIButton *sender) { 3 NSLog(@"你点了%@按钮",[sender currentTitle]); 4 }];
这样通过runtime改写的是UIButton的点击事件就更方便理解,阅读了。
2.runtime之归档使用方法
在自定类归档时,我们需要将属性一个一个的写入,在属性较少时可能不觉得有什么问题,但若是属性较多时,此种写法就显得笨拙了,此处我们便利用runtime的方法来动态的获取其属性,并写入。
首先自己新建一个类,这里笔者创建一个BQPerson类
1 BQPerson.h文件 2 @interface BQPerson : NSObject <NSCoding> 3 //假定这里有好几十个属性 4 @property (nonatomic, copy) NSString *name; 5 @property (nonatomic, copy) NSString *crad; 6 @property (nonatomic, copy) NSString *birthday; 7 @property (nonatomic, assign) NSInteger age; 8 @property (nonatomic, copy) NSString *iphone; 9 @property (nonatomic, copy) NSString *adress; 10 @property (nonatomic, assign) CGFloat height; 11 @property (nonatomic, assign) CGFloat weight; 12 13 @end
接下来我们便在.m文件中通过runtime来实现其归档
1 - (id)initWithCoder:(NSCoder *)aDecoder{ 2 if (self = [super init]) { 3 //定义长度 4 unsigned int index = 0; 5 //动态获取成员变量数组 6 Ivar *ivars = class_copyIvarList([self class], &index); 7 for (int i = 0 ; i < index; i++) { 8 //取出变量 9 Ivar ivar = ivars[i]; 10 //获取变量名 11 const char *name = ivar_getName(ivar); 12 //输出属性名字及其在内存偏移量 13 NSLog(@"%s %td",name,ivar_getOffset(ivar)); 14 //解档:解档调用方法decodeObjectForKey 15 NSString *key = [NSString stringWithUTF8String:name]; 16 id value = [aDecoder decodeObjectForKey:key]; 17 //赋值 18 [self setValue:value forKey:key]; 19 } 20 //由于这里是使用的C语言,需要自己手动管理内存 21 free(ivars); 22 } 23 return self; 24 } 25 26 - (void)encodeWithCoder:(NSCoder *)aCoder{ 27 //定义长度 28 unsigned int index = 0; 29 //动态获取类成员变量数组 30 Ivar *ivars = class_copyIvarList([self class], &index); 31 for (int i = 0; i < index; i++) { 32 //取出对应的变量 33 Ivar ivar = ivars[i]; 34 //获取取出的变量名 35 const char *name = ivar_getName(ivar); 36 //归档:归档调用方法encodeObject: forKey: 37 NSString *key = [NSString stringWithUTF8String:name]; 38 id value = [self valueForKey:key]; 39 [aCoder encodeObject:value forKey:key]; 40 } 41 //释放获取的变量数组 42 free(ivars); 43 }
在这里我们首先使用 class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 方法来获取一个类的成员变量数组,其中第一个参数为一个类:[self class](获取哪个类的成员变量),第二个参数为一个无符号整型指针:index(用以记录数组长度)。接下来我们通过ivar_getName(<#Ivar v#>)来返回一个变量的名字,接着将此名字转化为NSString类型。便可以进行解档和归档了。需要注意的是这里由于使用的是C语言,所以数组需要自己手动释放!
3.runtime之方法调换
在工程项目变得较为庞大后,由于功能需要我们要实现2个方法的交换,若是在代码里一个一个修改相对来说较为困难,在这里我们便可以使用runtime通过几句代码来直接交换我们需要交换的2个方法。示例如下:
1 + (void)load{ 2 static dispatch_once_t onceToken; 3 dispatch_once(&onceToken, ^{ 4 Method testfirst = class_getInstanceMethod(self, @selector(testfirst)); 5 Method secondtest = class_getInstanceMethod(self, @selector(secondtest)); 6 if (testfirst != nil && secondtest != nil) { 7 method_exchangeImplementations(testfirst, secondtest); 8 }else{ 9 NSLog(@"方法获取失败"); 10 } 11 }); 12 } 13 14 - (void)viewDidLoad { 15 [super viewDidLoad]; 16 17 [self testfirst]; 18 [self secondtest]; 19 } 20 21 - (void)testfirst{ 22 NSLog(@"1===%s",__func__); 23 } 24 - (void)secondtest{ 25 NSLog(@"2===%s",__func__); 26 }
按照正常程序来讲在viewDidLoad中应该是先走testfirst方法,接着再走secondtest方法,但这里我们在load(类第一次加载进内存的时候调用)方法中使用
class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)返回一个方法,其中第一个参数:self(类实例对象),第二个参数:@selector(testfirst)(方法选择器,选择一个方法)。最后我们使用method_exchangeImplementations(<#Method m1#>, <#Method m2#>) 将获取的2个方法进行动态的交换,这样在交换后的方法实现中,此2种方法都会互换,需要注意的是此处交换后,交换的是2个方法的实现地址,如调用[self testfirst]方法时会走secondtest的方法!
4.runtime之使assgin修饰符具有weak修饰符的特性(对象不存在后置为nil特性)
由于此方法实用性不强,只作为理论补充所以简单描述即可,首先给NSObject对象增加一个属性,若对象被销毁前都销毁其属性,那么在属性被销毁的时候就将对象置为nil。具体其做法如下
4.1.重写assgin的set方法
- (void)setTestView:(UIView *)testView{ _testView = testView; [testView psp_rundealloc:^{ _testView = nil; }]; }
4.2.写一个NSObject类目,动态增加一个属性
- (void)psp_rundealloc:(void (^)())block{ if (block) { PSPObjectBlock *pspobjc = [[PSPObjectBlock alloc] initWithBlock:block]; objc_setAssociatedObject(self, "pspobjc", pspobjc, OBJC_ASSOCIATION_RETAIN); } }
4.3.pspobjc属性内部实现如下
@interface PSPObjectBlock(){ BlockTest _block; } @end @implementation PSPObjectBlock - (instancetype)initWithBlock:(BlockTest)block{ self = [super init]; if (self) { _block = [block copy]; } return self; } //销毁时调用block使非weak属性值变为nil - (void)dealloc{ if (_block) { _block(); } }
有兴趣的朋友可以试一试实现此方法,关于runtime知识的记录分享就到这里了,如果其中有什么错误,望大家指出,谢谢!