平时觉得runtime都是大神用的,但是通过慢慢的学习,在实际项目中运用runtime,某些时候确实感觉挺牛逼的.
我就简单介绍下平时在我的项目中runtime的运用.
1.平时对于app账号信息的存储的时候,运用runtime实现归档和解档,也可以理解为字典转模型.
//解档
-(id)initWithCoder:(NSCoder *)decoder{
self=[super init];
if (self) {
unsigned int count = 0;
//获取类中所有成员变量名
Ivar *ivar = class_copyIvarList([self class], &count);
for (int i=0 ; i<count; i++) {
Ivar iva=ivar[i];
const char * name = ivar_getName(iva);
NSString *strName = [NSString stringWithUTF8String:name];
//进行解档取值
id value = [decoder decodeObjectForKey:strName];
//利用KVC对属性赋值
[self setValue:value forKey:strName];
}
free(ivar);
}
return self;
}
//归档
-(void)encodeWithCoder:(NSCoder *)encoder{
unsigned int count;
Ivar *ivar=class_copyIvarList([self class], &count);
for (int i=0; i<count; i++) {
Ivar iva=ivar[i];
const char *name = ivar_getName(iva);
NSString *strName=[NSString stringWithUTF8String:name];
//利用KVC取值
id value =[self valueForKey:strName];
[encoder encodeObject:value forKey:strName];
}
free(ivar);
}
继承与他的子类可以直接归档和解档
@interface LMAccount : LMArchiveModel
//账号信息
@property(nonatomic, copy)NSString *name;
@property(nonatomic, copy)NSString *mobile;
@property(nonatomic, copy)NSString *address;
@property(nonatomic, copy)NSString *email;
@property(nonatomic, copy)NSString *age;
@property(nonatomic, copy)NSString *userIcon;
@property(nonatomic, copy)NSString *signature;
@property(nonatomic, copy)NSString *userId;
@property(nonatomic, copy)NSString *state;
/**
存储
*/
-(void)saveAccount;
/**
取出账号信息
@return 返回账号
*/
+(id)getAccount;
@implementation LMAccount
//存储
-(void)saveAccount{
NSString *filePath=[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingString:@"/LMAccount.data"];
[NSKeyedArchiver archiveRootObject:self toFile:filePath];
}
//取出
+(id)getAccount{
NSString *filePath=[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingString:@"/LMAccount.data"];
id account =[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
return account;
}
2.在友盟统计页面停留时长的时候,我们可以runtime实现页面的统计,不用每个控制器都去写代码,解决了当页面较多时,需要修改的代码较多的问题
@implementation UIViewController (LMStatistics)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod1=class_getInstanceMethod([self class], @selector(viewWillAppear:));
Method swizzledMethod1=class_getInstanceMethod([self class], @selector(lm_viewWillAppear:));
Method originalMethod2=class_getInstanceMethod([self class], @selector(viewDidDisappear:));
Method swizzledMethod2=class_getInstanceMethod([self class], @selector(lm_viewDidDisappear:));
//交换实现
method_exchangeImplementations(originalMethod1, swizzledMethod1);
method_exchangeImplementations(originalMethod2, swizzledMethod2);
});
}
-(void)lm_viewWillAppear:(BOOL)animated{
;
NSLog(@"进入%@界面",NSStringFromClass([self class]));
[self lm_viewWillAppear:animated];
}
-(void)lm_viewDidDisappear:(BOOL)animated{
NSLog(@"离开%@界面",NSStringFromClass([self class]));
[self lm_viewDidDisappear:animated];
}
+load vs. +initialize
Swizzling应该只在load方法中使用
oc会在运行时自动调用每个类的两个方法,+load 会在类初始化加载的时候调用;+initialize方法会在程序调用类的第一个实例或者类方法的时候调用。这两个方法都是可选的,只会在实现的时候才去调用。由于method swizzling会影响到全局的状态,因此最小化竞争条件的出现变得很重要,+load方法能够确保在类的初始化时候调用,这能够保证改变应用行为的一致性,而+initialize在执行时并不提供这种保证,实际上,如果没有直接给这个类发送消息,该方法可能都不会调用到。
dispatch_once
Swizzling应该只在dispatch_once中完成
如上,由于swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是其中的一种预防措施,因为它能保证不管有多少个线程,代码只会执行一次。GCD的dispatch_once 能够满足这种需求,因此在method swizzling应该将其作为最佳的实践方式。
3.在项目中有很多的button是不可以重复点击的,用runtime可以很好的解决这个问题
@implementation UIButton (LMRetouch)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method method1= class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method method2= class_getInstanceMethod([self class], @selector(customSendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
});
}
-(NSTimeInterval)timeInterval{
return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
-(void)setTimeInterval:(NSTimeInterval)timeInterval{
objc_setAssociatedObject(self, @selector(timeInterval), @(timeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(void)customSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
if (self.isIgnore) {//设为YES,执行系统的方法
[self customSendAction:action to:target forEvent:event];
return;
}
if ([NSStringFromClass(self.class) isEqualToString:@"UIButton"]) {
self.timeInterval=self.timeInterval==0? defaultTimeInterval:self.timeInterval;
if (self.isIgnoreEvent) {//是否忽视此次事件
return;
}else if(self.timeInterval>0){
[self performSelector:@selector(resetState) withObject:nil afterDelay:self.timeInterval];
}
self.isIgnoreEvent=YES;
[self customSendAction:action to:target forEvent:event];
}
}
//重置
-(void)resetState{
self.isIgnoreEvent=NO;
}
#pragma mark - 动态添加属性
-(void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{
objc_setAssociatedObject(self, @selector(isIgnoreEvent), @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)isIgnoreEvent{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-(void)setIsIgnore:(BOOL)isIgnore{
objc_setAssociatedObject(self, @selector(isIgnore), @(isIgnore), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)isIgnore{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
4.项目中有的时候一个buttion的响应区域太小,交互不是很好,这个时候就需要增加button的响应区域
@interface UIButton (LMEnlargeTouchArea)
/**
设置响应边距
@param size 增加的边距
*/
-(void)setEnlargeEdge:(CGFloat)size;
/**
设置响应边距(增加)
@param top 上边距
@param right 右边距
@param bottom 下边距
@param left 左边距
*/
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left;
@implementation UIButton (LMEnlargeTouchArea)
static char topNameKey;
static char rightNameKey;
static char bottomNameKey;
static char leftNameKey;
//设置边距
#pragma mark - setter
-(void)setEnlargeEdge:(CGFloat)size{
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
#pragma mark - setter
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left{
// 需要添加关联的对象
// 添加的唯一标识符
// 关联的对象
// 关联的策略,是个枚举
objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//getter
//获取新的rect
-(CGRect)enlargedRect{
// 添加过关联的对象
// 添加的唯一标识符
NSNumber *topEdge =objc_getAssociatedObject(self, &topNameKey);
NSNumber *bottomEdge =objc_getAssociatedObject(self, &bottomNameKey);
NSNumber *leftEdge =objc_getAssociatedObject(self, &leftNameKey);
NSNumber *rightEdge =objc_getAssociatedObject(self, &rightNameKey);
if (topEdge && bottomEdge &&leftEdge && rightEdge) {
return CGRectMake(self.bounds.origin.x-leftEdge.floatValue,
self.bounds.origin.y-topEdge.floatValue,
self.bounds.size.width+leftEdge.floatValue+rightEdge.floatValue,
self.bounds.size.height+topEdge.floatValue+bottomEdge.floatValue);
}else{
return self.bounds;
}
}
#pragma mark - override
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
CGRect rect=[self enlargedRect];
if (CGRectEqualToRect(rect, self.bounds)) {
return [super hitTest:point withEvent:event];
}
return CGRectContainsPoint(rect, point)? self:nil;
}