简介
Objective-C是一门古老的语言,但是是一门动态性的语言,因为它的动态性,使其又有了强大的生命力,在苹果生态系统的平台应用广泛,可谓互相成全了对方,Objective-C的动态性随处可见,当子类覆写父类方法的时候,总是在执行前才决定该执行什么,不像C在编译时就已经决定了代码的执行,能让Objective-C有强大动态性的就是Runtime类库。
原理
Runtime的核心就是消息转发,当对象调用方法是,程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
简单的说主要有这几点:
1.消息传递(Messaging)
2.动态方法解析和转发
原理这一块儿我并没有写的很详细,大家可以看这几篇原理博客的详细介绍。
使用
1.在使用delegate的时候我们在判断代理是否遵守协议的时候,会发送一条消息,当消息没有被响应的时候,会抛出异常。
//这里就是发送了一条消息,看代理是否能够响应到,响应到的时候执行方法
if ([self.delegate respondsToSelector:@selector(dosomething:)]) {
[self.delegate dosomething:@""];
}
2.给对象添加category的时候是没有办法给之间添加property方法的,需要使用runtime进行动态绑定,这个可以参考AFNetwortking里面的写法
@interface UIView (category)
//声明
@property (nonatomic,copy) NSString *text;
@end
#import "UIView+category.h"
#import <objc/runtime.h>
//设置key
static char textKey;
@implementation UIView (category)
//设置关联
-(void)setText:(NSString *)text{
objc_setAssociatedObject(self, &textKey, text, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//获取关联的值
-(NSString *)text{
return objc_getAssociatedObject(self, &textKey);
}
@end
3.Method Swizzling
我觉得这才是Runtime运用场景最为广泛的地方,java有反射机制,可以设置动态代理,实现AOP编程,但是Objective-C的Runtime则更为强大。
下面来看一段代码
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
//原始方法指针
originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 给类添加方法指针
didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
//将原始的方法实现赋给swizzledSelector
class_replaceMethod(class, swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
} else {
//交换方法实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
上面是一段比较常见的方法交叉代码,但是我们在开发中用的是Aspects这个第三方的开源库,用这个开源库你可以实现自己的一些埋点统计或者判断UI刷新。
埋点统计
+ (void)load
{
[UIViewController aspect_hookSelector:@selector(viewWillAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
DebugLog(@"%@ class tpo appear",className);
} error:NULL];
[UIViewController aspect_hookSelector:@selector(viewWillDisappear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
DebugLog(@"%@ class tpo disappear",className);
} error:NULL];
}
判断子线程UI刷新
+(void)load{
#if DEBUG
[UIView aspect_hookSelector:@selector(setNeedsLayout)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
className = [NSString stringWithFormat:@"%@ can't use in background",className];
NSAssert([NSThread isMainThread],className);
} error:NULL];
[UIView aspect_hookSelector:@selector(setNeedsDisplay)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
className = [NSString stringWithFormat:@"%@ can't use in background",className];
NSAssert([NSThread isMainThread],className);
} error:NULL];
[UIView aspect_hookSelector:@selector(setNeedsDisplayInRect:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
className = [NSString stringWithFormat:@"%@ can't use in background",className];
NSAssert([NSThread isMainThread],className);
} error:NULL];
#else
//不在release的时候添加是因为和jspatch有冲突
#endif
}
hotfix
其实热修复有很多方案,原理都是Method Swizzling,比较常见的选择方案是JSPatch,但是我觉得JSPatch和Aspects冲突引发崩溃的问题应该有JSPatch的作者bang来解决,不应该由开发者自己解决,虽然bang团队给出过解决方案。
爱生活,爱运动,热爱交流,欢迎讨论!