Stinger--实践实现特定实例对象的AOP

栏目: Java · 发布时间: 6年前

内容简介:在但上述方式因为替换了类的方法实现,意味着类的所有实例对象的方法调用都发生了改变。在实际开发中,我们往往有这样的需求,在某个类的特定实例对象的方法里执行aop代码,其他实例对象仍旧执行原方法。实现这类aop的方式有KVO, RACObserve, rac_signalForSelector等。本文介绍了一个类可以有很多实例,要对特定实例对象hook方法,免不了有更换方法实现这一步,既然要达到不影响其他实例的方法,这里能想到的是像KVO的实现细节一样,新建一个所属类的子类,然后更改特定实例的isa指针为新建子

     在 iOS完整实践: 使用Libffi实现AOP 一文中,我们介绍了实现AOP的一种方式,通过解析目标方法的签名,使用 ffi_prep_cifffi_prep_closure_loc 构造壳函数替换原函数实现,以感知原方法调用时机及捕获参数,最后通过 ffi_call 利用预生成的模板动态调用原实现和block的函数指针。在 Stinger 中编写了具体实现。

     但上述方式因为替换了类的方法实现,意味着类的所有实例对象的方法调用都发生了改变。在实际开发中,我们往往有这样的需求,在某个类的特定实例对象的方法里执行aop代码,其他实例对象仍旧执行原方法。实现这类aop的方式有KVO, RACObserve, rac_signalForSelector等。本文介绍了 Stinger 的新特性实现细节,可构造与原方法参数相近的block,作为切面代码插入到特定实例对象的方法实现中。使用如下:

@implementation ASViewController
- (void)print3:(NSString *)s {
  NSLog(@"---original print4: %@", s);
}
- (void)viewDidLoad {
  [super viewDidLoad];
  // hook for specific instance
  [self st_hookInstanceMethod:@selector(print3:) option:STOptionAfter usingIdentifier:@"hook_print3_after1" withBlock:^(id<StingerParams> params, NSString *s) {
    NSLog(@"---specific instance-self after print3: %@", s);
  }];
}
@end
复制代码

实现

     一个类可以有很多实例,要对特定实例对象hook方法,免不了有更换方法实现这一步,既然要达到不影响其他实例的方法,这里能想到的是像KVO的实现细节一样,新建一个所属类的子类,然后更改特定实例的isa指针为新建子类,然后对该子类进行单独hook。另外需要设计一种结构,保存hook信息关联到实例对象,执行方法的时候,既执行该对象所属类原有的aop代码,又执行自己的aop代码。

Hook class

     下边是hook实例对象的代码,首先为实例对象的类新建了子类 ST_##Class , 并关联到了对象上,避免重复生成,同一个类的多个实例对象可以使用同一个子类作为isa指针指向。 此外,与KVO类似,hook了子类的class方法,以返回原有的类。

add hook info

     在 iOS完整实践: 使用Libffi实现AOP 中, hookInfoPool 关联到了类上, hook info 也添加到了该 hookInfoPool 中。hook实例对象的方法时,新建的子类有一个 hookInfoPool ,仅仅是为了存放壳函数,实例对象也有一个 hookInfoPool ,针对实例对象的 hook info 就存于此。这也就意味着,同一个类下的多个实例对象可以共用新建子类的 hookInfoPool 的壳函数,而真正的 hook info 存在对象本身关联的的 hookInfoPool 里。

#pragma mark - For specific instance

- (STHookResult)st_hookInstanceMethod:(SEL)sel option:(STOption)option usingIdentifier:(STIdentifier)identifier withBlock:(id)block {
  @synchronized(self) {
    Class stSubClass = getSTSubClass(self);
    if (!stSubClass) return STHookResultOther;
    
    STHookResult hookMethodResult = hookMethod(stSubClass, sel, option, identifier, block);
    if (hookMethodResult != STHookResultSuccuss) return hookMethodResult;
    if (!objc_getAssociatedObject(self, STSubClassKey)) {
      object_setClass(self, stSubClass);
      objc_setAssociatedObject(self, STSubClassKey, stSubClass, OBJC_ASSOCIATION_ASSIGN);
    }
    
    id<STHookInfoPool> instanceHookInfoPool = st_getHookInfoPool(self, sel);
    if (!instanceHookInfoPool) {
      instanceHookInfoPool = [STHookInfoPool poolWithTypeEncoding:nil originalIMP:NULL selector:sel];
      st_setHookInfoPool(self, sel, instanceHookInfoPool);
    }
    
    STHookInfo *instanceHookInfo = [STHookInfo infoWithOption:option withIdentifier:identifier withBlock:block];
    return [instanceHookInfoPool addInfo:instanceHookInfo] ? STHookResultErrorIDExisted : STHookResultSuccuss;
  }
}

NS_INLINE Class getSTSubClass(id object) {
  NSCParameterAssert(object);
  Class stSubClass = objc_getAssociatedObject(object, STSubClassKey);
  if (stSubClass) return stSubClass;
    
  Class isaClass = object_getClass(object);
  NSString *isaClassName = NSStringFromClass(isaClass);
  const char *subclassName = [STClassPrefix stringByAppendingString:isaClassName].UTF8String;
  stSubClass = objc_getClass(subclassName);
  if (!stSubClass) {
    stSubClass = objc_allocateClassPair(isaClass, subclassName, 0);
    NSCAssert(stSubClass, @"Class %s allocate failed!", subclassName);
    if (!stSubClass) return nil;
    
  objc_registerClassPair(stSubClass);
  Class realClass = [object class];
  hookGetClassMessage(stSubClass, realClass);
  hookGetClassMessage(object_getClass(stSubClass), realClass);
}
  return stSubClass;
}

复制代码

invoke hook info

     在当对象isa指向的类的方法被调用时,壳函数被执行, hookInfoPool 会作为 userData 传进来,这个hookInfoPool时关联在类上的。如果关联的类的类名带有st_class_前缀, 表明这是Hook实例对象新建的子类,那么 hookInfoPool 里的 hook Info 是空的,需要尝试取到预存的真实的类 statedCls 里的hook信息,即针对类的所有实例对象的hook。

     在 args 里,我们可以在第一位拿到 self , 即此时调用方法的实例对象,如果有关联的 hookInfoPool ,表明有针对此对象的Hook。执行切面block代码时,先执行针对的类的 hook info ,再执行针对实例对象的 hook Info ,如果是替换,则优先以实例的为准。

#define ffi_call_infos(infos) \
for (id<STHookInfo> info in infos) { \
  id block = info.block; \
  innerArgs[0] = &block; \
  ffi_call(&(hookedClassInfoPool->_blockCif), impForBlock(block), NULL, innerArgs); \
}  \

static void ffi_function(ffi_cif *cif, void *ret, void **args, void *userdata) {
  STHookInfoPool *hookedClassInfoPool = (__bridge STHookInfoPool *)userdata;
  STHookInfoPool *statedClassInfoPool = nil;
  STHookInfoPool *instanceInfoPool = nil;
  SEL sel = hookedClassInfoPool->_sel;
  if ([NSStringFromClass(hookedClassInfoPool->_hookedCls) hasPrefix:STClassPrefix]) {
    statedClassInfoPool = st_getHookInfoPool(hookedClassInfoPool->_statedCls, sel);
  } else {
    statedClassInfoPool = hookedClassInfoPool;
  }
  NSUInteger count = hookedClassInfoPool->_signature.argumentTypes.count;
  void **innerArgs = malloc(count * sizeof(*innerArgs));
  StingerParams *params = [[StingerParams alloc] init];
  void **slf = args[0];
  instanceInfoPool = st_getHookInfoPool((__bridge id)(*slf), sel);
  params.slf = (__bridge id)(*slf);
  params.sel = sel;
  [params addOriginalIMP:hookedClassInfoPool->_originalIMP];
  NSInvocation *originalInvocation = [NSInvocation invocationWithMethodSignature:hookedClassInfoPool->_ns_signature];
  
  for (int i = 0; i < count; i ++) {
    [originalInvocation setArgument:args[i] atIndex:i];
  }
  [params addOriginalInvocation:originalInvocation];
  
  innerArgs[1] = &params;
  memcpy(innerArgs + 2, args + 2, (count - 2) * sizeof(*args));
  
  // before hooks
  ffi_call_infos(statedClassInfoPool->_beforeInfos);
  if (instanceInfoPool) ffi_call_infos(instanceInfoPool->_beforeInfos);
  
  // instead hooks
  if (instanceInfoPool && instanceInfoPool->_insteadInfos.count) {
    id <STHookInfo> info = instanceInfoPool->_insteadInfos[0];
    id block = info.block;
    innerArgs[0] = &block;
    ffi_call(&(hookedClassInfoPool->_blockCif), impForBlock(block), ret, innerArgs);
  } else if (statedClassInfoPool->_insteadInfos.count) {
    id <STHookInfo> info = statedClassInfoPool->_insteadInfos[0];
    id block = info.block;
    innerArgs[0] = &block;
    ffi_call(&(hookedClassInfoPool->_blockCif), impForBlock(block), ret, innerArgs);
  } else {
    // original IMP
    /// if hooked by aspects or jspatch.. which use message-forwarding.
    BOOL isForward = hookedClassInfoPool->_originalIMP == _objc_msgForward
#if !defined(__arm64__)
    || hookedClassInfoPool->_originalIMP == (IMP)_objc_msgForward_stret
#endif
    ;
    if (isForward) {
      [params invokeAndGetOriginalRetValue:ret];
    } else {
      ffi_call(cif, (void (*)(void))hookedClassInfoPool->_originalIMP, ret, args);
    }
  }
  // after hooks
  ffi_call_infos(statedClassInfoPool->_afterInfos);
  if (instanceInfoPool) ffi_call_infos(instanceInfoPool->_afterInfos);
  
  free(innerArgs);
}

复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning Apache Struts

Beginning Apache Struts

Arnold Doray / Apress / 2006-02-20 / USD 44.99

Beginning Apache Struts will provide you a working knowledge of Apache Struts 1.2. This book is ideal for you Java programmers who have some JSP familiarity, but little or no prior experience with Ser......一起来看看 《Beginning Apache Struts》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具