IMG_5569.JPG

补充

initialize

  • lookUpImpOrforward函数中会调用realizeAndInitializeIfNeeded_locked -> realizeClassMaybeSwiftAndLeaveLocked,即类的initialize方法 ```objectivec static Class realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize) { runtimeLock.assertLocked(); if (slowpath(!cls->isRealized())) {

    1. cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    2. // runtimeLock may have been dropped but is now locked again

    }

    if (slowpath(initialize && !cls->isInitialized())) {

    1. cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    2. // runtimeLock may have been dropped but is now locked again
    3. // If sel == initialize, class_initialize will send +initialize and
    4. // then the messenger will send +initialize again after this
    5. // procedure finishes. Of course, if this is not being called
    6. // from the messenger then it won't happen. 2778172

    } return cls; }

  1. <a name="uO41r"></a>
  2. ## unrecognized selector
  3. - 方法找不到时,报经典错误unrecognized selector sent to instance 0x...........
  4. - 其调用流程为:
  5. - lookUpImpOrForward(初始赋值forward_imp = (IMP)_objc_msgForward_impcache)
  6. - 方法未找到时,imp = _objc_msgForward_impcache, _objc_msgForward_impcache被赋值给了imp
  7. - _objc_msgForward_impcache汇编中调用了objc_defaultForwardHandler方法
  8. - objc_defaultForwardHandler中执行了格式打印
  9. ```objectivec
  10. __attribute__((noreturn, cold)) void
  11. objc_defaultForwardHandler(id self, SEL sel)
  12. {
  13. _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
  14. "(no message forward handler is installed)",
  15. class_isMetaClass(object_getClass(self)) ? '+' : '-',
  16. object_getClassName(self), sel_getName(sel), self);
  17. }

instrumentObjc函数

在lookupImpOrForward函数中,done:结束后调用了log_and_fill_cache,该函数中有一个判断是否支持写入messageLog,如果支持且开启,则可以在对应文件夹中找到消息发送的日志文件

  1. log_and_fill_cache(cls, imp, sel, inst, curClass);
  2. static void
  3. log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
  4. {
  5. #if SUPPORT_MESSAGE_LOGGING
  6. ///<< implementer为传入的implementer,在lookupImpOrForward函数中,传入的是curClass为真
  7. ///<< 所以此时objcMsgLogEnabled如果为真,则可以开启写入流程
  8. if (slowpath(objcMsgLogEnabled && implementer)) {
  9. bool cacheIt = logMessageSend(implementer->isMetaClass(),
  10. cls->nameForLogging(),
  11. implementer->nameForLogging(),
  12. sel);
  13. if (!cacheIt) return;
  14. }
  15. #endif
  16. cls->cache.insert(sel, imp, receiver);
  17. }
  18. ///<< logMessageSend写文件的地址为 "/tmp/msgSends-%d"
  19. bool logMessageSend(bool isClassMethod,
  20. const char *objectsClass,
  21. const char *implementingClass,
  22. SEL selector)
  23. {
  24. char buf[ 1024 ];
  25. // Create/open the log file
  26. if (objcMsgLogFD == (-1))
  27. {
  28. snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
  29. objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
  30. if (objcMsgLogFD < 0) {
  31. // no log file - disable logging
  32. objcMsgLogEnabled = false;
  33. objcMsgLogFD = -1;
  34. return true;
  35. }
  36. }
  37. ...
  38. }

建立person类,在对应想打印的方法前后添加调用

  1. LGPerson *person = [LGPerson alloc];
  2. instrumentObjcMessageSends(YES);
  3. [person sayHello];
  4. instrumentObjcMessageSends(NO);

编译后在对应的temp文件夹下找到了msgSends-70902文件
image.png
msgSends-70902文件中记录的msgSend的流程
image.png

动态消息处理

lookUpImpOrForward中的单例

  • LOOKUP_RESOLVER = 2, 0011 & 0010 = 0010

    1. if (slowpath(behavior & LOOKUP_RESOLVER)) {
    2. behavior ^= LOOKUP_RESOLVER; 异或
    3. return resolveMethod_locked(inst, sel, cls, behavior);
    4. }

    resolveInstanceMethod

  • resolveInstanceMethod:(inst, sel, cls), 非元类时走这里

    • 进而包装resolve…method
      • resolve_sel = @selector(resolveInstanceMethod:) 实现了此方法,则进行容错处理
      • bool resolved = msg(cls, resolve_sel, sel) 根据resovled是否成功进行下一步处理
      • imp = looUpImpOrNilTryCache(inst, sel, cls)
    • 这里走两次原因是一次根据继承链查找,一次根据isa的走位图查找,所以后续可以写在NSObject分类中统一处理(resolveInstanceMethod + resolveClassMethod) ```objectivec
  • (BOOL)resolveInstanceMethod:(SEL)sel { // 来了两次 // 处理 sel -> imp

    IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB)); Method method = class_getInstanceMethod(self , @selector(sayNB)); const char * type = method_getTypeEncoding(method); if (sel == @selector(say666)) {

    1. return class_addMethod(self, sel, sayNBImp, type);

    } return [super resolveInstanceMethod:sel]; } ```

    resolveClassMethod

  • resolveClassMethod:(inst, sel, cls),元类时走这里 ```objectivec //// 元类的以对象方法的方法
  • (BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@”resolveClassMethod :%@-%@”,self,NSStringFromSelector(sel));

    if (sel == @selector(sayHappy)) {

    1. IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
    2. Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
    3. const char *type = method_getTypeEncoding(method);
    4. return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);

    }

    return [super resolveClassMethod:sel]; } ```

    AOP

  • 切面编程,上述在Person类中写的resolve方法,可以统一写在NSObject分类中,根据isa的走位图和继承链,会分别调用实例方法和对象方法共两次 ```objectivec
  • (void)sayNB{ NSLog(@”%@ - %s”,self , func); }
  • (void)sayKC{ NSLog(@”%@ - %s”,self , func); }

pragma clang diagnostic push

// 让编译器忽略错误

pragma clang diagnostic ignored “-Wundeclared-selector”

  • (BOOL)resolveInstanceMethod:(SEL)sel{ // resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业 // 处理 sel -> imp

    NSLog(@”resolveInstanceMethod :%@-%@”,self,NSStringFromSelector(sel));

    if (sel == @selector(say666)) {

    1. IMP sayNBImp = class_getMethodImplementation(self, @selector(sayNB));
    2. Method method = class_getInstanceMethod(self, @selector(sayNB));
    3. const char *type = method_getTypeEncoding(method);
    4. return class_addMethod(self, sel, sayNBImp, type);

    }else if (sel == @selector(sayHappy)) {

    1. IMP sayNBImp = class_getMethodImplementation(objc_getMetaClass("LGTeacher"), @selector(sayKC));
    2. Method method = class_getInstanceMethod(objc_getMetaClass("LGTeacher"), @selector(sayKC));
    3. const char *type = method_getTypeEncoding(method);
    4. return class_addMethod(objc_getMetaClass("LGTeacher"), sel, sayNBImp, type);

    } return NO; } @end

    pragma clang diagnostic pop

    ```

  • 本质就是通过say666() sel 查找imp的过程
    • 苹果给的一次机会
    • 写在NSObject分类中,全局方法找不到时,我们都能监听到
    • 命名规范:lg_model_traffic -> lg_home_didClickDetail -> pop home -> 发送消息给后台 -> 修改为题
    • 利用了Runtime特性
    • oop优缺点
      • 对象分工明确
      • 代码冗余,某一特性,需要继承(强依赖 - 强耦合)
    • aop 无入侵 - 动态注入代码,注意切入的方法以及切入的类
      • 会有性能消耗,此时是消息转发前的最后一次,后面的消息转发无意义,所以这里NSObject做Aop不好

消息转发

  • 消息转发分为快速转发和慢速转发,其实现是在CoreFoundation框架。

    forwardingTargetForSelector

  • 快速转发

  • 我给你,要不起,转给别人,找背锅侠
  • 所有方法找不到,都向Student类中堆积,看是否会实现

建立一个person类,一个student类,teacher声明一个say方法且未实现,在person中有对应声明和实现,运行时会报错

  1. LGPerson *person = [LGPerson alloc];
  2. [person sayHello];

在person类中加入forwardingTargetForSelector方法,则消息转发给了student处理

  1. - (id)forwardingTargetForSelector:(SEL)aSelector {
  2. return [LGStudent alloc];
  3. }
  4. -[LGStudent sayHello]

methodSignatureForSelector

  • 慢速转发
  • methodSignatureForSelector 要和forwarInvocation配套使用才能生效,在methodSignatureForSelector方法签名后,系统会保存此方法。
    • methodSignature判断sel,进行签名
    • forwardInvocation处理
  • 签名即”V@:@”格式
  • 所有系统消息都称为事务,可做可不做。所以在methodSignature签名后,forwardInvocation中不处理也不会崩溃 ```objectivec
  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@”%s - %@”,func,NSStringFromSelector(aSelector)); if (aSelector == @selector(sayHello)) {

    1. return [NSMethodSignature signatureWithObjCTypes:"v@:@"];

    } return [super methodSignatureForSelector:aSelector]; }

  • (void)forwardInvocation:(NSInvocation )anInvocation{ NSLog(@”%@ - %@”,anInvocation.target,NSStringFromSelector(anInvocation.selector)); LGStudent s = [LGStudent alloc]; if ([self respondsToSelector:anInvocation.selector]) {

    1. [anInvocation invoke];

    }else if ([s respondsToSelector:anInvocation.selector]){

    1. [anInvocation invokeWithTarget:s]; // s来接

    }else{

    1. NSLog(@"%s - %@",__func__,NSStringFromSelector(anInvocation.selector));

    } } ```

    流程探索

    去除上述快速转发、慢速转发消息的处理,运行时崩溃在方法找不到,这是bt查看函数调用栈,发现CoreFoundation框架中的-[NSObject(NSObject) doesNotRecognizeSelector:] 与当前程序崩溃信息一致,其向前调用函数分别为forwarding和_CF_forwarding_prep_0
    image.png

  • CoreFoundation静态库部分开源,查找不到forwarding和_CF_forwarding_prep_0,所用需要用CoreFoundation的动态库查看

  • 通过hopper查看分析coreFoundation

image.png
未实现forwardingTargetForSelector则跳转loc_64a67
image.png流程继续进行,则会判断是否实现了forwardInvocation
image.png

  • 动态决议resolveInstanceMethod走两次的原因:
    • 第一次方法没有此方法,会调用resolveInstanceMethod方法
    • 通过bt查看堆栈信息,第二次则为
      • _CF_forwarding_prep_0
      • forwarding
      • [NSObject(NSObject) doesNotRecongnizeSelector:]
      • class_respondsToSelector_inst
      • lookUpImpOrNilTryCache

IMG_8261B72CD690-1.jpeg

流程图

未命名文件.jpg