Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

原IMP丢弃,stackBlock crash , 子类调用不到被修复的父类方法 #6

Closed
zhutc opened this issue Mar 14, 2019 · 1 comment

Comments

@zhutc
Copy link

zhutc commented Mar 14, 2019

首先感谢作者实现了一种全新的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 :
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上测试的。反馈中虽然给出了自己的修改,但是并不是最好的。希望作者能抽空修复下这些问题,以后有新版本发布也方便我们升级。多谢!!

@YPLiang19
Copy link
Owner

@zhutc 最新版本重写了替换Method IMP的策略,现在可以替换父类Method IMP,也可以同时替换父类和子类的Method IMP,调用原有的Method IMP以通过 ORGxxx的方式进行调用。而对于StackBlock 也会自动进行copy。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants