首先感谢作者实现了一种全新的hotfix方式,点赞!
我在使用过程中发现了几个问题,希望作者能优化下,让mango越来越棒!
问题一:被修复的类会丢弃原来的IMP
在execute.m 原来的处理会丢弃旧的IMP,处理有点粗暴。我将旧IMP绑定到ORIGxxx。
static void replace_method(MANInterpreter *interpreter,Class clazz, MANMethodDefinition *method){
// 省略其他代码
//保留原来的IMP
Class c2 = method.classMethod ? objc_getMetaClass(class_getName(clazz)) : clazz;
class_replaceMethod(c2, @selector(forwardInvocation:), (IMP)mango_forward_invocation,"v@:@");
IMP originalIMP =class_replaceMethod(c2, sel, _objc_msgForward, typeEncoding);
class_addMethod(c2, NSSelectorFromString([NSString stringWithFormat:@"ORIG%@",func.name]), originalIMP, typeEncoding);
if (needFreeTypeEncoding) {
free((void *)typeEncoding);
}
}
之后就可以在DSL中使用self.ORIGxxxx带调用到原来的方法。
问题二:父类修复某一方法,子类使用super调用不到
MANMethodMapTable.h
当子类使用[super xxx] ,传递class实际是子类的class,getMethodMapTableItemWith:sel: 中返回nil,类似调用了空函数。
-(MANMethodMapTableItem *)getMethodMapTableItemWith:(Class)clazz classMethod:(BOOL)classMethod sel:(SEL)sel{
//需要递归找到所有父类的方法,同时产生新的问题:子类好父类hotfix同一个方法,方法内部使用super时会造成死循环。JSPatch取最顶层IMP调用,虽然没有死循环,但是丢掉了继承调用链。
Class fixClass = clazz;
do {
NSString *index = [NSString stringWithFormat:@"%d_%@_%@,",classMethod,NSStringFromClass(fixClass),NSStringFromSelector(sel)];
MANMethodMapTableItem* item = _dic[index];
if (item || [NSStringFromClass(fixClass) isEqualToString:@"NSObject"]) {
return item;
}
fixClass = class_getSuperclass(fixClass);
} while (fixClass);
return nil;
}
问题三:当修复的方法有参数是stack block时,会造成crash
ARC之后有种写法还是会产生StackBlock:
SubModel* model = [[SubModel alloc] initWithTitle:@"Title" image:@"image"];
[model log];
int a = 1;
void(^block)(void) = ^{ //此时block是 Malloc
NSLog(@"run block %d" , a);
};
[model testBlock:^{ // 此时block是 Stack
NSLog(@"run block %d" , a);
}];
DSL中的fix代码
class Model : NSObject {
-(void)log{
NSLog(@"Fix Model Log");
self.ORIGlog();
}
-(void)testBlock:(Block)block
{
NSLog(@"Fix testBlock");
self.ORIGtestBlock:(block);
}
}
Crash :
在ARC模式下,会自动插入Stack转Malloc的操作,DSL会丢失这个特性,就出现了上述问题。
我在代码中打了一个补丁,暂时满足需求:
MANValue.h
- (instancetype)initWithCValuePointer:(void *)cValuePointer typeEncoding:(const char *)typeEncoding bridgeTransfer:(BOOL)bridgeTransfer {
typeEncoding = removeTypeEncodingPrefix((char *)typeEncoding);
MANValue *retValue = [[MANValue alloc] init];
switch (*typeEncoding) {
//省略部分代码
case '@':{
retValue.type = man_create_type_specifier(MAN_TYPE_OBJECT);
if (bridgeTransfer) {
retValue.objectValue = (__bridge_transfer id)(*(void **)cValuePointer);
} else if (0 == strcmp(typeEncoding, "@?")) { // **如果是block,将stack变为malloc**
id block = (__bridge id)(*(void **)cValuePointer);
block = [block copy];
retValue.objectValue = block;
}else{
retValue.objectValue = (__bridge id)(*(void **)cValuePointer);
}
break;
}
//省略部分代码
}
return retValue;
}
问题四:内存偶尔有点高
MANInterpreter.h
NSString *currentThread = [[NSThread currentThread] description];
修改为
NSString *currentThread = [NSString stringWithFormat:@"%p",[NSThread currentThread]];
完整代码:
- (MANStack *)stack{
NSString *currentThread = [NSString stringWithFormat:@"%p",[NSThread currentThread]];
[_lock lock];
if (!_stacksDic[currentThread]) {
_stacksDic[(id)currentThread] = [[MANStack alloc] init];
}
MANStack* value = _stacksDic[currentThread];
[_lock unlock];
return value;
}
这个修改只能解决一点点问题,还需要作者从全局出发找到优化方法。
测试代码
完整DSL:
class Model : NSObject {
-(void)log{
NSLog(@"Fix Model Log");
self.ORIGlog();
}
-(void)testBlock:(Block)block
{
NSLog(@"Fix testBlock");
self.ORIGtestBlock:(block);
}
}
Model.h
@interface Model : NSObject
@property(nonatomic,copy) NSString* title;
@property(nonatomic,copy) NSString* image;
-(instancetype)initWithTitle:(NSString*)t image:(NSString *)i;
-(void)log;
-(void)testBlock:(void(^)(void))block;
@end
Model.m
#import "Model.h"
@implementation Model
-(instancetype)initWithTitle:(NSString*)t image:(NSString *)i
{
self = [super init];
if (self) {
self.title = t;
self.image = i;
}
return self;
}
-(void)log
{
NSLog(@"title = %@ image = %@" , self.title , self.image);
}
-(void)testBlock:(void(^)(void))block
{
if(block){
block();
}
}
@end
我的测试代码是在tag1.0.1上测试的。反馈中虽然给出了自己的修改,但是并不是最好的。希望作者能抽空修复下这些问题,以后有新版本发布也方便我们升级。多谢!!