2018总结-2019展望

2018已完成目标

1.4月换了一份工作到了lc;
2.10月买了一辆车;
3.11月拿到了c1牌照。


说说读书

感觉能从书里面听到很多以前没有想法,没有见过的事,每次读到的时候都会很激动,我也在不断的寻找这样的书。但总觉着不够深入,因为没能表达自己的想法,每次看完一本书,总觉着不是真的结束了,又好像还没开始,对书的内容,没有深入的理解,对那些可以随意看的书另当别论,可是遇到好书,也会有这种感觉,看完后,比较心慌,于是马上找下一本书,让自己忘记这种感觉。可是下次看完新的一本书后,心慌、甚至有点失落的感觉又上心头,没完没了。且记住这种感觉吧。


说说工作

虽然从已经倒闭了的公司wb出来后,进了新的东家,但是不顺心的时候还是会有,新公司给自己的心理预期还是有所差距「证明了面试的承诺千万不能相信,尤其是小的创业公司」。主要是还是工作的环境和工作的时间。当然,新的工作也给我带来了收获,收获就是学会了swift编程,这是最大的收获。工作上,编程的能力还是很欠缺,主要是性能优化,对iOS的深入了解,还是很肤浅。希望在新的2019,我能突破一下。


说说家庭

家里的欠债现在好了一些了,希望2019年能过的更好,这样我们家也越来越好。说到自己的儿,已经3岁了,现在已经会打酱油了,很快也上学了,我自己也一直坚持,多给他一些陪伴,这3年,自己虽说做的不是十全十美,但也问心无愧了。


说说朋友

知心的朋友真的越来越少,以前可能还有几个,时光飞逝,大家都各奔东西,和自己没交集的朋友,都离去了,朋友dqq,朋友zyy,都没联系了,不管什么原因,最后还是相忘于江湖吧。珍惜眼前的朋友吧,朋友h,希望能做未来10年的好朋友。


2019展望

  • 健康
    1.参加 2019 年中山或者佛山马拉松半程,拿到奖牌;
    2.参加一次中山或者佛山组织的50公里徒步;
    3.跑步300公里,每周6公里;
    4.完成6块腹肌。

  • 软实力
    1.加入一个能表达能多发言的组织;
    2.重新经营博客,博客30篇,每月3篇 ;
    3.报一个演讲班;
    4.报一个练字班;
    5.AACTP
    6.加入樊登读书会,读 50 本精华的书,一个月一本,写下50篇笔记,每篇500字以上。

  • 技术
    1.学习一门新的编程语言,达到可以编译一个项目,熟悉这门新语言常用的工具;
    2.在 LeetCode 上 刷 100 道题, 100 /10,每个月10道;

  • 奖励
    1.锤子手机奖励
    2.柔记RoWrite智能手写本

【2018-12-31 22:53 by codeRiding in 佛山】【完 】

runtime,你几岁了?

  • #

  • write by coderiding,erliucxy 中山 东区 广东 2018-02-23- 17:19:20

  • 第一部分:runtime的第一神器-Associated Objects

  • Associated Objects的介绍:中文翻译为关联对象、对象关联、关联引用

  • 文字总结write by erliucxy
    1. 可以实现什么:给系统的类增加属性对象。
    1. 方便的地方是://TODO
  • 1.1主要涉及3个方法
  • objc_setAssociatedObject 设置

  • objc_getAssociatedObject 获取

  • objc_removeAssociatedObjects 移除

  • ——————————————————————————

    • object 【源对象—{一般是self}】
    • key 【关键字{属性}】write by erliucxy
  • 1.属性最好是 static char 类型的,当然更推荐是指针型的。
  • 2.然而可以用更简单的方式实现:用selector}
  • 3.{Since SELs are guaranteed to be unique and constant, you can use _cmd as the key for objc_setAssociatedObject().采用隐藏参数_cmd作为key}

    • value【被关联的对象】
    • policy 【关联策略{关联对象的行为} objc_AssociationPolicy】
  • ———————————

  • objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,

  • id _Nullable value, objc_AssociationPolicy policy)
  • OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

  • ——————————————————————————

  • OBJC_EXPORT id objc_getAssociatedObject ( id object , const void *key )

  • OBJC_AVAILABLE ( 10.6 , 3.1 , 9.0 , 1.0 );

  • ——————————————————————————

  • objc_removeAssociatedObjects

  • 不应该自己手动调用这个函数,规范的方法是:调用 objc_setAssociatedObject 方法并传入一个 nil 值来清除一个关联;因为可能会导致其他客户对其添加的属性也被移除了

  • 1.2关联策略objc_AssociationPolicy
    • 关联对象的行为,它可以指定Objc内存管理的引用计数机制。
    • 以 OBJC_ASSOCIATION_ASSIGN 类型关联在对象上的弱引用不代表0 retian的 weak 弱引用,行为上更像 unsafe_unretained 属性,所以当在你的视线中调用weak的关联对象时要相当小心。write by erliucxy
  • Behavior

  • @property Equivalent
  • Description
  • OBJC_ASSOCIATION_ASSIGN
  • @property (assign) 或 @property (unsafe_unretained)
  • 指定一个关联对象的弱引用。
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC
  • @property (nonatomic, strong)
  • 指定一个关联对象的强引用,不能被原子化使用。
  • OBJC_ASSOCIATION_COPY_NONATOMIC
  • @property (nonatomic, copy)
  • 指定一个关联对象的copy引用,不能被原子化使用。
  • OBJC_ASSOCIATION_RETAIN
  • @property (atomic, strong)
  • 指定一个关联对象的强引用,能被原子化使用。
  • OBJC_ASSOCIATION_COPY
  • @property (atomic, copy)
  • 指定一个关联对象的copy引用,能被原子化使用。

  • 1.3 Associated Objects常见的使用例子

  • 001–使用例子
  • // 相当于加了一个 @property (nonatomic, strong)

  • @dynamic associatedObject;《OC-checklist》[※※]@synthesize和@dynamic分别有什么作用?

    • (void)setAssociatedObject:(id)object {
  • objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  • }

    • (id)associatedObject {
  • return objc_getAssociatedObject(self, @selector(associatedObject));
  • }
  • 002–使用例子
  • // 相当于加了一个 @property (assign) 或 @property (unsafe_unretained)

  • static const int TagKey = 0;

    • (void)setTag:(NSInteger)tag{
  • objc_setAssociatedObject(self, &TagKey, @(tag), OBJC_ASSOCIATION_ASSIGN);
  • }

    • (NSInteger)tag{
  • return [objc_getAssociatedObject(self, &TagKey) integerValue];
  • }
  • 003—使用例子
  • // 相当于加了一个 @property (nonatomic, strong)

  • static const NSString *ViewHudKey = @”ViewHud”;

    • (void)setHUD:(MBProgressHUD *)HUD{
  • objc_setAssociatedObject(self, &ViewHudKey, HUD, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  • }

    • (MBProgressHUD *)HUD{
  • return objc_getAssociatedObject(self, &ViewHudKey);
  • }
  • —004 使用例子
  • // 相当于加了一个 @property (atomic, strong)

  • static const NSString *ViewControllerInfo = @”ViewControllerInfo”;

    • (void)setRouterUserInfo:(NSDictionary *)routerUserInfo{
  • objc_setAssociatedObject(self, &ViewControllerInfo, routerUserInfo, OBJC_ASSOCIATION_RETAIN);
  • }

    • (NSDictionary *)routerUserInfo{
  • return objc_getAssociatedObject(self, &ViewControllerInfo);
  • }
  • —005 使用例子
  • // 相当于加了一个 @property (nonatomic, copy)

    • (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
  • {
  • objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
  • }

    • (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
  • {
  • return objc_getAssociatedObject(self, _cmd);
  • }
  • —006 使用例子
  • // 相当于加了一个 @property (nonatomic, strong)

    • (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
  • {
  • objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  • }

    • (BOOL)fd_prefersNavigationBarHidden
  • {
  • return [objc_getAssociatedObject(self, _cmd) boolValue];
  • }
  • -007 使用例子
  • // 相当于加了一个 @property (nonatomic, strong)

  • static char RedTipViewKey;

    • (void)setRedTipView:(UILabel *)redTipView
  • {
  • objc_setAssociatedObject(self, &RedTipViewKey, redTipView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  • }

    • (UILabel *)redTipView
  • {
  • return objc_getAssociatedObject(self,&RedTipViewKey);
  • }

  • Associated Objects合体使用的例子

  • —008合体例子

    • (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver
  • {
  • AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));

  • if (notificationObserver == nil)

  • {
  • // 相当于加了一个 @property (nonatomic, strong)
  • notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
  • objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  • }

  • return notificationObserver;

  • }
  • —009 合体例子
    • (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate
  • {
  • _FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);
  • if (!delegate) {
  • // 相当于加了一个 @property (nonatomic, strong)
  • delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
  • delegate.navigationController = self;
  • objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  • }
  • return delegate;
  • }
  • —010 合体例子write by erliucxy
    • (void)bk_addEventHandler:(void (^)(id sender))handler forControlEvents:(UIControlEvents)controlEvents
  • {
  • NSParameterAssert(handler);
  • NSMutableDictionary *events = objc_getAssociatedObject(self, BKControlHandlersKey);
  • if (!events) {
  • // 相当于加了一个 @property (nonatomic, strong)
  • events = [NSMutableDictionary dictionary];
  • objc_setAssociatedObject(self, BKControlHandlersKey, events, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  • }

  • NSNumber *key = @(controlEvents);

  • NSMutableSet *handlers = events[key];
  • if (!handlers) {
  • handlers = [NSMutableSet set];
  • events[key] = handlers;
  • }

  • BKControlWrapper *target = [[BKControlWrapper alloc] initWithHandler:handler forControlEvents:controlEvents];

  • [handlers addObject:target];
  • [self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
  • }
  • 第二部分:你在什么地方接触过runtime?

  • 2.1 三个方面接触过:

    1. 通过 Objective-C 源代码 - 接触runtime
    1. 在runtime中,通过数据结构来定义的一些类、方法、协议等;
    1. 通过 Foundation 框架的NSObject类定义的方法 - 接触runtime
    1. 如isKindOfClass:
    1. 如isMemberOfClass:
    1. 如respondsToSelector:
    1. 如methodForSelector:
    1. 通过对 runtime 函数的直接调用 - 接触runtime
    1. objc_ 开头的方法
    1. 如objc_ivar_list
    1. 如objc_property_attribute_t
    1. ivar_ 开头的方法
    1. 如ivar_getOffset
    1. 如ivar_list
    1. protocol_ 开头的方法
    1. 如protocol_getName
    1. 如protocol_copyPropertyList
    1. method_ 开头的方法
    1. 如method_getName
    1. 如method_setImplementation{第二神器用到}
    1. 如method_exchangeImplementations {第二神器用到}
    1. 如method_getTypeEncoding{第二神器用到}
    1. sel_ 开头的方法
    1. 如sel_getName
    1. 如sel_registerName
    1. imp_ 开头的方法
    1. 如imp_getBlock
    1. 如imp_removeBlock
    1. 如imp_implementationWithBlock
    1. class_ 开头的方法
    1. 如class_addIvar
    1. 如class_getName
    1. 如class_addProperty
    1. 如class_getInstanceMethod {第二神器用到}write by erliucxy
    1. 如class_replaceMethod {第二神器用到}
    1. 如class_addMethod {第二神器用到}
  • 第三部分:runtime的第二神器-Method Swizzling

  • 3.1本质就是

    1. 交换IMP和SEL的对应关系,以达到替换方法实现的目的;
    1. Method Swizzling就是OC中的Hook
  • 3.2必须明确的概念

  • IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。表示同一个id + 同一个SEL = 同一个IMP
  • /// A pointer to the function of a method implementation.
  • #if !OBJC_OLD_DISPATCH_PROTOTYPES
  • typedef void (IMP)(void / id, SEL, … */ );
  • #else
  • typedef id

  • id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员;根据isa指针就可以顺藤摸瓜找到对象所属的类write by erliucxy
  • typedef struct objc_object *id;
  • struct objc_object {
  • Class isa OBJC_ISA_AVAILABILITY;
  • }


  • SEL的本质:SEL 是系统在编译过程中,根据 方法的名字 以及 参数序列 生成一个用来区分这个方法的唯一 ID 编号;这个 ID 就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
  • Selector(typedef struct objc_selector *SEL)


  • Method的本质:指向objc_method结构体指针;是一种代表类中的某个方法的类型。

    • method_name:方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
    • method_types:方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
    • method_imp:指向了方法的实现,本质上是一个函数指针,后面会详细讲到
  • typedef struct objc_method *Method;

  • struct objc_method {
  • SEL method_name OBJC2_UNAVAILABLE ; // 方法名,作为key
  • char *method_types OBJC2_UNAVAILABLE ; // 参数类型以及返回值类型编码
  • IMP method_imp OBJC2_UNAVAILABLE ; // 方法实现指针,作为value
  • }


  • // 疑问:什么时候调用class_addMethod?调用它有什么作用?

  • // 作用:官方解答:Adds a new method to a class with a given name and implementation.class_addMethod will add an override of a superclass’s implementation
  • // 作用:中文翻译:通过给的name和imp添加一个新的方法到类中。它将添加一个父类已经实现的imp。
  • // 注意:调用这个方法不能直接改变该类中已经存在的imp,如果要改变,使用method_setImplementation
  • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);


  • // 当需要替换的方法可能有不存在的情况时,可以考虑使用该方法。write by erliucxy

    • cls:The class you want to modify.
    • name:A selector that identifies the method whose implementation you want to replace.
    • imp:The new implementation for the method identified by name for the class identified by cls.
    • types:An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
  • // 疑问:什么时候调用class_replaceMethod?调用它有什么作用?

  • // 作用:官方:Replaces the implementation of a method for a given class.
  • // 作用:解释:为给定的类替换imp
  • IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);


  • // 当仅仅需要为一个方法设置其实现方式时使用

  • IMP method_setImplementation(Method m, IMP imp);


  • // OC实现的编码类型

  • const char * method_getTypeEncoding(Method m);
  • 3.3 用一个图来表示Method Swizzling在做什么?
  • 交换IMP和SEL的对应关系,以达到替换方法实现的目的;
  • 3.4Method Swizzling的实现方案
  • Method Swizzling的实现方案一:
    • (void)load
  • {
  • static dispatch_once_t onceToken;

  • dispatch_once(&onceToken, ^{

  • Class aClass = [self class];

  • SEL originalSelector = @selector(method_original:);
  • SEL swizzledSelector = @selector(method_swizzle:);
  • Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
  • Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
  • // 直接交换IMP{有风险,因为当前类可以没有实现要交换的方法,而是继承父类的}
  • method_exchangeImplementations(originalMethod, swizzledMethod);
  • });
  • }
  • ————————————————————————————————————————————————————————————————

  • Method Swizzling的实现方案二:–最佳方案

  • 如果类中没有实现 Original selector 对应的方法,那就先添加 Method,并将其 IMP 映射为 Swizzle 的实现。然后替换 Swizzle selector 的 IMP 为 Original 的实现;否则交换二者 IMP。

    • (void)load
  • {
  • static dispatch_once_t onceToken;

  • dispatch_once(&onceToken, ^{

  • Class aClass = [self class];

  • SEL originalSelector = @selector(method_original:);
  • SEL swizzledSelector = @selector(method_swizzle:);
  • Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
  • Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
  • // 通过class_addMethod:判断该类实现了原始方法还是父类实现了原始方法
    • aClass,当前类
    • originalSelector ,如果class_addMethod返回YES,originalSelector就是指向父类的方法;如果返回NO,originalSelector就是指向当前类的方法;
    • method_getImplementation(swizzledMethod) ,自定义方法的IMP
    • method_getTypeEncoding(swizzledMethod),自定义方法的类型
  • BOOL didAddMethod =
  • class_addMethod(aClass,
  • originalSelector,
  • method_getImplementation(swizzledMethod),
  • method_getTypeEncoding(swizzledMethod));
  • if (didAddMethod) {
  • // 如果添加成功,证明这个类没有实现要替换的方法,而是继承了父类的实现;接下来要做的是–使用class_replaceMethod交换IMP,下面就是将(swizzledSelector)与(method_getImplementation(originalMethod))的IMP交换;
  • class_replaceMethod(aClass,
  • swizzledSelector,
  • method_getImplementation(originalMethod),
  • method_getTypeEncoding(originalMethod)
  • );
  • } else {
  • // 【如果添加失败,证明这个类实现了原始方法;交换IMP,originalMethod—swizzledMethod】write by erliucxy
  • method_exchangeImplementations(originalMethod, swizzledMethod);
  • }
  • });
  • }
  • 有时为了避免方法命名冲突和参数 _cmd 被篡改,也会使用下面这种『静态方法版本』的 Method Swizzle。CaptainHook 中的宏定义也是采用这种方式,比较推荐:
  • typedef IMP *IMPPointer;

  • static void MethodSwizzle(id self, SEL _cmd, id arg1);

  • static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

  • static void MethodSwizzle(id self, SEL _cmd, id arg1) {

  • // do custom work
  • MethodOriginal(self, _cmd, arg1);
  • }

  • BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store)

  • {
  • IMP imp = NULL;
  • Method method = class_getInstanceMethod(class, original);
  • if (method) {
  • const char *type = method_getTypeEncoding(method);
  • imp = class_replaceMethod(class, original, replacement, type);
  • if (!imp) {
  • imp = method_getImplementation(method);
  • }
  • }

  • if (imp && store) { *store = imp; }

  • return (imp != NULL);
  • }

    • (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store
  • {
  • return class_swizzleMethodAndStore(self, original, replacement, store);
  • }

    • (void)load
  • {
  • [self swizzle:@selector(originalMethod:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
  • }
  • 3.5Method Swizzling常见的使用例子
  • -001 使用例子

  • #import “NSArray+Swizzle.h”

  • @implementation NSArray (Swizzle)
    • (id)myLastObject
  • {
  • id ret = [self myLastObject];
  • NSLog(@”** myLastObject *** “);
  • return ret;
  • }
  • @end

  • #import <objc/runtime.h>
  • #import “NSArray+Swizzle.h”
  • int main(int argc, char *argv[])
  • {
  • @autoreleasepool {
  • Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
  • Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
  • method_exchangeImplementations(ori_Method, my_Method);
  • NSArray *array = @[@”0”,@”1”,@”2”,@”3”];
  • NSString *string = [array lastObject];
  • NSLog(@”TEST RESULT : %@”,string);
  • return 0;
  • }
  • }

  • 结果输出:
  • ** myLastObject ***
  • :TEST RESULT : 3
  • ——————————————————————————————————————————————————

  • -002 使用例子–RNSwizzle

  • // 直接操作IMP

  • // RNSwizzle.m
  • // MethodSwizzle
  • #import “RNSwizzle.h”
  • #import <objc/runtime.h>
  • @implementation NSObject (RNSwizzle)
    • (IMP)swizzleSelector:(SEL)origSelector
  • withIMP:(IMP)newIMP {

  • Class class = [self class];

  • Method origMethod = class_getInstanceMethod(class,
  • origSelector);
  • IMP origIMP = method_getImplementation(origMethod);
  • if(!class_addMethod(self, origSelector, newIMP,
  • method_getTypeEncoding(origMethod)))
  • {
  • method_setImplementation(origMethod, newIMP);
  • }
  • return origIMP;
  • }
  • @end
  • ——————————————————————————————————————————————————

  • -003 使用例子—猿题库

  • // ImagePickerReplaceMethodsHolder.h
  • @interface ImagePickerReplaceMethodsHolder : NSObject
    • (BOOL)shouldAutorotate;
    • (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
  • @end

  • // ImagePickerReplaceMethodsHolder.m

  • @implementation ImagePickerReplaceMethodsHolder
    • (BOOL)shouldAutorotate {
  • return NO;
  • }
    • (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
  • return UIInterfaceOrientationPortrait;
  • }
  • @end

  • // 开始替换

  • #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

  • #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
    • (void)load {
  • static dispatch_once_t onceToken;
  • dispatch_once(&onceToken, ^{
  • [self hackForImagePicker];
  • });
  • }
    • (void)hackForImagePicker {
  • // fix bug of image picker under iOS 6.0
  • // http://stackoverflow.com/questions/12522491/crash-on-presenting-uiimagepickercontroller-under-ios-6-0
  • if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@”6.0”)
  • && SYSTEM_VERSION_LESS_THAN(@”6.1”))
  • {
  • Method oldMethod1 = class_getInstanceMethod([UIImagePickerController class], @selector(shouldAutorotate));
  • Method newMethod1 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(shouldAutorotate));
  • method_setImplementation(oldMethod1, method_getImplementation(newMethod1));
  • Method oldMethod2 = class_getInstanceMethod([UIImagePickerController class], @selector(preferredInterfaceOrientationForPresentation));
  • Method newMethod2 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(preferredInterfaceOrientationForPresentation));
  • method_setImplementation(oldMethod2, method_getImplementation(newMethod2));
  • }
  • }
  • ——————————————————————————————————————————————————

  • -004 使用例子

  • @interface UIViewController (MRCUMAnalytics)

  • @end

  • @implementation UIViewController (MRCUMAnalytics)

    • (void)load {
  • static dispatch_once_t onceToken;
  • dispatch_once(&onceToken, ^{
  • Class class = [self class];

  • SEL originalSelector = @selector(viewWillAppear:);

  • SEL swizzledSelector = @selector(mrc_viewWillAppear:);

  • Method originalMethod = class_getInstanceMethod(class, originalSelector);

  • Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

  • // class_addMethod 判断能否添加将要替换的方法

  • BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

  • if (success) {

  • class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  • } else {
  • // 主类已经实现,直接替换
  • method_exchangeImplementations(originalMethod, swizzledMethod);
  • }
  • });
  • }

  • #pragma mark - Method Swizzling

    • (void)mrc_viewWillAppear:(BOOL)animated {
  • [self mrc_viewWillAppear:animated];
  • [MobClick beginLogPageView:NSStringFromClass([self class])];
  • }

  • @end

  • ——————————————————————————————————————————————————

  • -005 使用例子

  • #import <objc/runtime.h>

  • @implementation UIViewController (Tracking)

    • (void)load {
  • static dispatch_once_t onceToken;
  • dispatch_once(&onceToken, ^{
  • Class class = [self class];

  • SEL originalSelector = @selector(viewWillAppear:);

  • SEL swizzledSelector = @selector(xxx_viewWillAppear:);

  • // class表示UIViewController

  • Method originalMethod = class_getInstanceMethod(class, originalSelector);
  • Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

  • // When swizzling a class method, use the following:

  • // Class class = object_getClass((id)self);
  • // …
  • // Method originalMethod = class_getClassMethod(class, originalSelector);
  • // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

  • BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));

  • // class_replaceMethod的最后结果和method_exchangeImplementations一样,

  • // 都可以实现交换方法

  • if (didAddMethod)

  • {
  • class_replaceMethod(class,
  • swizzledSelector,
  • method_getImplementation(originalMethod),
  • method_getTypeEncoding(originalMethod));
  • } else {
  • method_exchangeImplementations(originalMethod, swizzledMethod);
  • }
  • });
  • }

  • #pragma mark - Method Swizzling

    • (void)xxx_viewWillAppear:(BOOL)animated {
  • // 在交换了方法的实现后,xxx_viewWillAppear:方法的实现已经被替换为了 UIViewController -viewWillAppear:的原生实现,
  • // 所以这里并不是在递归调用
  • // 下面的方法实际上实在调用viewWillAppear
  • // 而调用UIViewController的viewWillAppear实际是调用xxx_viewWillAppear方法
  • [self xxx_viewWillAppear:animated];

  • NSLog(@”viewWillAppear: %@”, self);

  • }

  • @end

  • ——————————

  • 上面的代码简化
    • 这是因为class_replaceMethod方法其实能够覆盖到class_addMethod和method_setImplementation两种场景, 对于第一个class_replaceMethod来说, 如果viewWillAppear:实现在父类, 则执行class_addMethod, 否则就执行method_setImplementation将原方法的IMP指定新的代码块; 而第二个class_replaceMethod完成的工作便只是将新方法的IMP指向原来的代码.
    • 但此处需要特别注意交换的顺序,应该优先把新的方法指定原IMP,再修改原有的方法的IMP.write by erliucxy
    • 为什么呢?
    • (void)load {
  • Class class = [self class];
  • SEL originalSelector = @selector(viewWillAppear:);
  • SEL swizzledSelector = @selector(xxx_viewWillAppear:);
  • Method originalMethod = class_getInstanceMethod(class, originalSelector);
  • Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
  • if (!originalMethod || !swizzledMethod) {
  • return;
  • }
  • IMP originalIMP = method_getImplementation(originalMethod);
  • IMP swizzledIMP = method_getImplementation(swizzledMethod);
  • const char *originalType = method_getTypeEncoding(originalMethod);
  • const char *swizzledType = method_getTypeEncoding(swizzledMethod);
  • // 这儿的先后顺序是有讲究的,如果先执行后一句,那么在执行完瞬间方法被调用容易引发死循环
  • // 先将新方法指定给原来的IMP,这样调用原来的方法名,就会执行新的IMP
  • // 再将原方法指定给新的IMP,这样调用原来的方法名,就会执行新的IMP
  • class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
  • class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
  • }
  • 3.6Method Swizzling常见的问题
    1. 多继承关系的类使用Method Swizzling时,应该注意的方面是?
    1. 多个有继承关系的类的对象Swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被Swizzle的实现。
    1. 在load中Swizzle不用担心这种问题,因为load类方法会默认从父类开始调用。
    1. 深入了解//TODO
    1. 使用Method Swizzling时解决命名冲突
    1. 为什么Method Swizzling应该在+load方法中实现?write by erliucxy
    1. 在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
    1. +load在每个类被装载到Runtime的时候调用
    1. +initialize 在每个类第一次被发送消息的时候调用。
    1. 之所以要在+load中进行,是因为方法交叉影响的是全局状态,+load中能保证在class 装载的时候进行交叉,而initialize没办法做到。
    1. Swizzling在+load中执行时,不要调用[super load];因为如果是多继承,并且对同一个方法都进行了Swizzling,那么调用[super load]以后,父类的Swizzling就失效了。
    1. 为什么Method Swizzling应该在dispatch_once中完成?
    1. 由于 swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 满足了所需要的需求,并且应该被当做使用 swizzling 的初始化单例方法的标准。
    1. dispatch_once保证方法交叉只交叉一次
  • write by coderiding,erliucxy 中山 东区 广东 2018-02-23- 17:19:20

OC-消息传递和消息转发-从源代码看起

  • write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51

  • 第一部分:从方法调用得出的结论

  • 文字的描述:先来看一段代码:

    1. ClangClass类有个handle方法
    1. handle方法里面是创建AClass对象并调用addO方法
    1. 我们来看看调用addO方法的具体过程//
  • 源代码的描述:

  • #import “ClangClass.h”

  • #import “AClass.h”

  • @interface ClangClass()

  • @property(nonatomic,strong)AClass *acClass;
  • @end

  • @implementation ClangClass

    • (instancetype)init{
  • if (self = [super init]) {
  • _acClass = [[AClass alloc] init];
  • }
  • return self;
  • }

    • (void)handle{
  • [_acClass addO];
  • }

  • @end

  • 将代码操作一下:write by erliucxy
    1. 进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m
    1. 看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];
    1. {是不是感觉一大堆很乱,我也觉得}
  • static void _I_ClangClass_handle(ClangClass * self, SEL _cmd) {

  • ((void ()(id, SEL))(void )objc_msgSend)((id)((AClass **)((char )self + OBJC_IVAR_$_ClangClass$_acClass)), sel_registerName(“addO”));

  • }

  • // 把上面代码优化下,去掉强制转换的符号得到

  • objc_msgSend(_acClass, sel_registerName(“addO”));
  • IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。
  • /// A pointer to the function of a method implementation.
  • #if !OBJC_OLD_DISPATCH_PROTOTYPES
  • typedef void (IMP)(void / id, SEL, … */ );
  • #else
  • typedef id
  • 得出结论:
    1. 调用方法addO就是调用objc_msgSend进行消息传递;
    1. 而objc_msgSend就是去找到IMP,来执行实现的代码;
    1. 编译的时候,确定了方法的名字,但是方法的具体实现可以通过runtime来动态修改{专业术语是:重新映射方法对应的实现},具体查看《OC-runtime你几岁了?》
    1. 第三条涉及两个状态:编译时、运行时
  • 第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret

  • objc_msgSend定义:id objc_msgSend(id self, SEL op, …);

  • self:A pointer that points to the instance of the class that is to receive the message.{消息的接收者}

  • op:The selector of the method that handles the message.{是selector}

  • …:A variable argument list containing the arguments to the method.{是参数列表}

  • id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
  • typedef struct objc_object *id;
  • struct objc_object {
  • Class isa OBJC_ISA_AVAILABILITY;
  • }
  • SEL的本质:SEL 是系统在编译过程中,根据 方法的名字 以及 参数序列 生成一个用来区分这个方法的唯一 ID 编号;这个 ID 就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
  • Selector(typedef struct objc_selector *SEL)

  • write by erliucxy

  • 文字描述objc_msgSend的工作流程:

    1. 在self中沿着isa找到AClass的类对象;
    1. 优先在AClass的cache查找addO方法,如果有就返回imp;
    1. 如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;
    1. 如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;
    1. 判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;
    1. 如果没有在AClass找到addO方法,就会到super_class就是NSObject{具体情况看情况而定}里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;
    1. 如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。
    1. 如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;
    1. 不管上面的哪一步找到imp,都直接返回imp。
  • 相关:

    • _objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。
    • 当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。
  • 本质:

    • objc_msgSend的本质就是找到imp指针执行代码;而静态语言的imp函数指针在编译的时候就已经确定好了,确定好的意思是直接可以执行指针的代码,不需要查找了;动态语言是编译后可以变换的。write by erliucxy
  • 源代码描述objc_msgSend的工作流程:

  • {其实我没有找到这段的实现,就用大神的代码了}

  • id objc_msgSend(id self, SEL op, …) {
  • if (!self) return nil;
  • IMP imp = class_getMethodImplementation(self->isa, SEL op);
  • imp(self, op, …); //调用这个函数,伪代码…
  • }

  • // 源码

  • IMP class_getMethodImplementation(Class cls, SEL sel)
  • {
  • IMP imp;

  • if (!cls || !sel) return nil;

  • imp = lookUpImpOrNil(cls, sel, nil,

  • YES/initialize/, YES/cache/, YES/resolver/);

  • // Translate forwarding function to C-callable external version

  • if (!imp) {
  • // 如果找不到imp就返回消息转发
  • return _objc_msgForward;
  • }

  • return imp;

  • }

  • IMP lookUpImpOrNil(Class cls, SEL sel, id inst,

  • bool initialize, bool cache, bool resolver)
  • {
  • // 查找imp或决定转发
  • IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
  • if (imp == _objc_msgForward_impcache) return nil;
  • else return imp;
  • }

  • IMP lookUpImpOrForward(Class cls, SEL sel, id inst,

  • bool initialize, bool cache, bool resolver)
  • {
  • Class curClass;
  • IMP methodPC = nil;
  • Method meth;
  • bool triedResolver = NO;

  • methodListLock.assertUnlocked();

  • // Optimistic cache lookup

  • if (cache) {
  • // 查找imp,如果有缓存,就返回缓存
  • methodPC = _cache_getImp(cls, sel);
  • if (methodPC) return methodPC;
  • }

  • // Check for freed class

  • // 查看释放的类
  • if (cls == _class_getFreedObjectClass())
  • // 如果类已经释放,返回_freedHandler
  • return (IMP) _freedHandler;

  • // Check for +initialize

  • // 检查有没有实现+initialize方法
  • if (initialize && !cls->isInitialized()) {
  • _class_initialize (_class_getNonMetaClass(cls, inst));
  • // If sel == initialize, _class_initialize will send +initialize and
  • // then the messenger will send +initialize again after this
  • // procedure finishes. Of course, if this is not being called
  • // from the messenger then it won’t happen. 2778172
  • }

  • // The lock is held to make method-lookup + cache-fill atomic

  • // with respect to method addition. Otherwise, a category could
  • // be added but ignored indefinitely because the cache was re-filled
  • // with the old value after the cache flush on behalf of the category.
  • retry:
  • // 加锁
  • methodListLock.lock();

  • // Try this class’s cache.

  • // 查找类的缓存
  • methodPC = _cache_getImp(cls, sel);
  • if (methodPC) goto done;

  • // Try this class’s method lists.

  • // 查找类的方法列表
  • meth = _class_getMethodNoSuper_nolock(cls, sel);
  • if (meth) {
  • log_and_fill_cache(cls, cls, meth, sel);
  • methodPC = method_getImplementation(meth);
  • goto done;
  • }

  • // Try superclass caches and method lists.

  • curClass = cls;

  • while ((curClass = curClass->superclass)) {
  • // Superclass cache.
  • // 查找父类的缓存
  • meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
  • if (meth) {
  • if (meth != (Method)1) {
  • // Found the method in a superclass. Cache it in this class.
  • // 在父类的缓存中查找
  • log_and_fill_cache(cls, curClass, meth, sel);
  • methodPC = method_getImplementation(meth);
  • goto done;
  • }
  • else {
  • // Found a forward:: entry in a superclass.
  • // Stop searching, but don’t cache yet; call method
  • // resolver for this class first.
  • break;
  • }
  • }

  • // Superclass method list.

  • // 查找父类的方法列表
  • meth = _class_getMethodNoSuper_nolock(curClass, sel);
  • if (meth) {
  • log_and_fill_cache(cls, curClass, meth, sel);
  • methodPC = method_getImplementation(meth);
  • goto done;
  • }
  • }

  • // No implementation found. Try method resolver once.

  • if (resolver && !triedResolver) {

  • // 解锁
  • methodListLock.unlock();
  • // 如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了
  • _class_resolveMethod(cls, sel, inst);
  • triedResolver = YES;
  • goto retry;
  • }

  • // No implementation found, and method resolver didn’t help.

  • // Use forwarding.
  • // 方法找不到:尝试一次resolver方法无效;开始转发
  • _cache_addForwardEntry(cls, sel);
  • // 返回_objc_msgForward_impcache表示转发
  • methodPC = _objc_msgForward_impcache;

  • done:

  • // 解锁
  • methodListLock.unlock();

  • return methodPC;

  • }
  • 第三部分:消息转发_objc_msgForward
  • // 因为add1方法是不存在的,所以会走转发

    • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event{
  • AClass *acClass = [[AClass alloc] init];

  • [acClass performSelector:@selector(add1)];
  • }
  • oc转发文字描述:
    1. 调用【1】resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。write by erliucxy
    1. 如果调用resolveInstanceMethod没有实现,就开始调用【2】forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。
    1. 如果调用forwardingTargetForSelector没有效果,就开始调用【3】methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。
    1. 将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。
    1. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。
  • oc转发源代码描述:

  • 使用instrumentObjcMessageSends的方法来查看app的log信息,具体使用方法:在appdelegate.m里面定义如下:

  • #import “AppDelegate.h”

  • #import <objc/runtime.h>

  • void instrumentObjcMessageSends();

  • @interface AppDelegate ()

  • @end

  • @implementation AppDelegate

    • (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
  • // Override point for customization after application launch.

  • instrumentObjcMessageSends(YES);
  • return YES;
  • }
  • // 下面是消息转发的log信息
    • AClass NSObject initialize
    • AClass NSObject alloc
    • AClass AClass init
    • NSObject NSObject init
    • AClass NSObject performSelector:
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject forwardingTargetForSelector:
    • AClass NSObject forwardingTargetForSelector:
    • AClass NSObject methodSignatureForSelector:
    • AClass NSObject methodSignatureForSelector:
    • AClass NSObject class
    • AClass NSObject doesNotRecognizeSelector:
    • AClass NSObject doesNotRecognizeSelector:
    • AClass NSObject class
  • 第四部分 respondsToSelector、instancesRespondToSelector

  • // 因为add1方法是不存在的,所以会走转发 看第四部分

    • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event{
  • AClass *acClass = [[AClass alloc] init];

  • [acClass respondsToSelector:@selector(add1)];
  • }

  • 文字分析:

    1. 如果没有找到imp方法,就尝试一次resolveInstanceMethod,respondsToSelector不会进行消息转发
    1. 不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了write by erliucxy
    1. 主要的原因,看respondsToSelector的源码就知道,在下面
  • 源码分析:

    • (BOOL)respondsToSelector:(SEL)sel {
  • if (!sel) return NO;
  • return class_respondsToSelector_inst(object_getClass(self), sel, self);
  • }

    • (BOOL)respondsToSelector:(SEL)sel {
  • if (!sel) return NO;
  • return class_respondsToSelector_inst([self class], sel, self);
  • }
    • (BOOL)instancesRespondToSelector:(SEL)sel {
  • if (!sel) return NO;
  • return class_respondsToSelector(self, sel);
  • }
  • BOOL class_respondsToSelector(Class cls, SEL sel)
  • {
  • return class_respondsToSelector_inst(cls, sel, nil);
  • }

  • bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)

  • {
  • IMP imp;

  • if (!sel || !cls) return NO;

  • // Avoids +initialize because it historically did so.

  • // We’re not returning a callable IMP anyway.
  • // 这里的resolver传入的是YES,所以当找不到方法的时候,就只会执行一次resolveInstanceMethod方法,避开了消息转发,具体看最上面的lookUpImpOrNil方法
  • imp = lookUpImpOrNil(cls, sel, inst,
  • NO/initialize/, YES/cache/, YES/resolver/);
  • return bool(imp);
  • }
  • // respondsToSelector消息转发部分的log
    • AClass NSObject initialize
    • AClass NSObject alloc
    • AClass AClass init
    • NSObject NSObject init
    • AClass NSObject respondsToSelector:
    • AClass NSObject class
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject dealloc
  • 第五部分 methodForSelector、instanceMethodForSelector

  • // 因为add1方法是不存在的,所以会走转发 看第四部分write by erliucxy

    • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event{
  • AClass *acClass = [[AClass alloc] init];

  • [acClass methodForSelector:@selector(add1)];
  • }
    • (IMP)methodForSelector:(SEL)sel {
  • if (!sel) [self doesNotRecognizeSelector:sel];
  • return object_getMethodImplementation((id)self, sel);
  • }

    • (IMP)methodForSelector:(SEL)sel {
  • if (!sel) [self doesNotRecognizeSelector:sel];
  • return object_getMethodImplementation(self, sel);
  • }
    • (IMP)instanceMethodForSelector:(SEL)sel {
  • if (!sel) [self doesNotRecognizeSelector:sel];
  • return class_getMethodImplementation(self, sel);
  • }

  • 看源码可以知道,是和respondsToSelector一样的实现机制:methodForSelector不会进行消息转发【write by erliucxy】

    • AClass NSObject initialize
    • AClass NSObject alloc
    • AClass AClass init
    • NSObject NSObject init
    • AClass NSObject methodForSelector:
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject resolveInstanceMethod:
    • AClass NSObject dealloc
  • write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51

消息传递和消息转发-从源代码看起

不断更新,最新内容查看pdf版本,最新:2018-02-23

第一部分:从方法调用得出的结论

1.1 文字的描述:先来看一段代码:
  1. ClangClass类有个handle方法
  2. handle方法里面是创建AClass对象并调用addO方法
  3. 我们来看看调用addO方法的具体过程

1.2 源代码的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "ClangClass.h"
#import "AClass.h"

@interface ClangClass()
@property(nonatomic,strong)AClass *acClass;
@end

@implementation ClangClass

- (instancetype)init{
if (self = [super init]) {
_acClass = [[AClass alloc] init];
}

return self;
}

- (void)handle{
[_acClass addO];
}

@end

1.3 将代码操作一下:
  1. 进入到上面ClangClass.m的文件夹下面,使用命令编译,命令为:clang -rewrite-objc ClangClass.m
  2. 看文件ClangClass.cpp得到的结果为,我们具体看调用的代码是:[_acClass addO];
  3. {是不是感觉一大堆很乱,我也觉得}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void _I_ClangClass_handle(ClangClass * self, SEL _cmd) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)(*(AClass **)((char *)self + OBJC_IVAR_$_ClangClass$_acClass)), sel_registerName("addO"));
}

// 把上面代码优化下,去掉强制转换的符号得到
objc_msgSend(_acClass, sel_registerName("addO"));

---------------------------------------------

IMP本质:是一个函数指针,保存了方法地址,指向方法的实现,这是由编译器生成的。
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id

---------------------------------------------
得出结论:
1. 调用方法addO就是调用objc_msgSend进行消息传递;
2. 而objc_msgSend就是去找到IMP,来执行实现的代码;

第二部分:消息传递objc_msgSend、objc_msgSendSuper、objc_msgSend_stret、objc_msgSendSuper_stret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objc_msgSend定义:id objc_msgSend(id self, SEL op, …);

self:A pointer that points to the instance of the class that is to receive the message.{消息的接收者}
op:The selector of the method that handles the message.{是selector}
…:A variable argument list containing the arguments to the method.{是参数列表}


id的本质:id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
typedef struct objc_object *id;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
}


SEL的本质:SEL 是系统在编译过程中,根据 方法的名字 以及 参数序列 生成一个用来区分这个方法的唯一 ID 编号;这个 ID 就是 SEL 类型的。和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
Selector(typedef struct objc_selector *SEL)

2.1 文字描述objc_msgSend的工作流程:
  1. 在self中沿着isa找到AClass的类对象;
  2. 优先在AClass的cache查找addO方法,如果有就返回imp;
  3. 如果第一次没找到缓存中的addO方法,就查看这个类有没有被释放,如果释放了,就返回;
  4. 如果这个类没有被释放,就检查这个类有没有实现 +initialize方法,如果有但是没有初始化,就初始化;
  5. 判断初始化后,尝试再在AClass的cache查找addO方法,如果找不到,就到AClass的methodLists查找addO方法;
  6. 如果没有在AClass找到addO方法,就会到super_class就是NSObject里面的cache查找;如果在父类的cache找不到,就到父类的methodLists中查找addO方法;
  7. 如果在父类的methodLists也没有找到addO方法,且如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了。
  8. 如果resolver设置为NO,且还没有找到IMP,就返回_objc_msgForward_impcache开始消息转发;
  9. 不管上面的哪一步找到imp,都直接返回imp。

2.2 相关:
  • _objc_msgForward是用于消息转发的。这个函数的实现并没有在objc-runtime的开源代码里面,而是在Foundation框架里面实现的。加上断点启动程序后,会发现__CFInitialize这个方法会调用objc_setForwardHandler函数来注册一个实现。
  • 当向一般对象发送消息时,调用objc_msgSend;当向super发送消息时,调用的是objc_msgSendSuper; 如果返回值是一个结构体,则会调用objc_msgSend_stret或objc_msgSendSuper_stret。

2.3 本质:
  • objc_msgSend的本质就是找到imp指针执行代码,而静态语言的imp函数指针在编译的时候就已经确定好了,动态语言是编译后可以变换的。

2.4 源代码描述objc_msgSend的工作流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
{其实我没有找到这段的实现,就用大神的代码了}
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}

// 源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;

if (!cls || !sel) return nil;

imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

// Translate forwarding function to C-callable external version
if (!imp) {
// 如果找不到imp就返回消息转发
return _objc_msgForward;
}

return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// 查找imp或决定转发
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP methodPC = nil;
Method meth;
bool triedResolver = NO;

methodListLock.assertUnlocked();

// Optimistic cache lookup
if (cache) {
// 查找imp,如果有缓存,就返回缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) return methodPC;
}

// Check for freed class
// 查看释放的类
if (cls == _class_getFreedObjectClass())
// 如果类已经释放,返回_freedHandler
return (IMP) _freedHandler;

// Check for +initialize
// 检查有没有实现+initialize方法
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}

// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
// be added but ignored indefinitely because the cache was re-filled
// with the old value after the cache flush on behalf of the category.
retry:
// 加锁
methodListLock.lock();

// Try this class's cache.
// 查找类的缓存
methodPC = _cache_getImp(cls, sel);
if (methodPC) goto done;

// Try this class's method lists.
// 查找类的方法列表
meth = _class_getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, cls, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}

// Try superclass caches and method lists.

curClass = cls;
while ((curClass = curClass->superclass)) {
// Superclass cache.
// 查找父类的缓存
meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
if (meth) {
if (meth != (Method)1) {
// Found the method in a superclass. Cache it in this class.
// 在父类的缓存中查找
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
// 查找父类的方法列表
meth = _class_getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, curClass, meth, sel);
methodPC = method_getImplementation(meth);
goto done;
}
}

// No implementation found. Try method resolver once.

if (resolver && !triedResolver) {
// 解锁
methodListLock.unlock();
// 如果强制传入resolver为YES,且找不到imp,尝试一次_class_resolveMethod,不尝试消息转发了
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.
// 方法找不到:尝试一次resolver方法无效;开始转发
_cache_addForwardEntry(cls, sel);
// 返回_objc_msgForward_impcache表示转发
methodPC = _objc_msgForward_impcache;

done:
// 解锁
methodListLock.unlock();

return methodPC;
}

第三部分:消息转发_objc_msgForward

1
2
3
4
5
// add1方法是不存在的,现在试试转发
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass performSelector:@selector(add1)];
}

3.1 oc转发文字描述:
  1. 调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。
  2. 如果调用resolveInstanceMethod没有实现,就开始调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。
  3. 如果调用forwardingTargetForSelector没有效果,就开始调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。
  4. 将第三步获取到的方法签名包装成Invocation传入,调用forwardInvocation:方法,如何处理就在这里面了,并返回非nil。
  5. 调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。

3.2 oc转发源代码描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
使用instrumentObjcMessageSends的方法来查看app的log信息,具体使用方法:在appdelegate.m里面定义如下:

#import "AppDelegate.h"
#import <objc/runtime.h>

void instrumentObjcMessageSends();
@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
instrumentObjcMessageSends(YES);
return YES;
}


// 下面是消息转发的log信息
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject performSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject forwardingTargetForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject methodSignatureForSelector:
- AClass NSObject class
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject doesNotRecognizeSelector:
- AClass NSObject class

第四部分 respondsToSelector、instancesRespondToSelector

1
2
3
4
5
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass respondsToSelector:@selector(add1)];
}

4.1 文字分析:
  1. 如果没有找到imp方法,就尝试一次resolveInstanceMethod,不会进行消息转发
  2. 不会进行消息转发,所以就算找不到方法,也不会报doesNotRecognizeSelector这个找不到方法的报错了
  3. 主要的原因,看respondsToSelector的源码就知道,在下面

4.2 源码分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
+ (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst(object_getClass(self), sel, self);
}

- (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst([self class], sel, self);
}

---------------------------------------------
+ (BOOL)instancesRespondToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(self, sel);
}

---------------------------------------------
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(cls, sel, nil);
}

bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
IMP imp;

if (!sel || !cls) return NO;

// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
// 这里的resolver传入的是YES,所以当找不到方法的时候,就只会执行一次resolveInstanceMethod方法,避开了消息转发,具体看最上面的lookUpImpOrNil方法
imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
return bool(imp);
}

---------------------------------------------

// respondsToSelector消息转发部分的log
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject respondsToSelector:
- AClass NSObject class
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

第五部分 methodForSelector、instanceMethodForSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// add1方法是不存在的,现在试试转发,看第四部分
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AClass *acClass = [[AClass alloc] init];
[acClass methodForSelector:@selector(add1)];
}
---------------------------------------------

+ (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation((id)self, sel);
}

- (IMP)methodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return object_getMethodImplementation(self, sel);
}

---------------------------------------------
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}

5.1 看源码可以知道,是和respondsToSelector一样的实现机制:没有消息转发
1
2
3
4
5
6
7
8
+ AClass NSObject initialize
+ AClass NSObject alloc
- AClass AClass init
- NSObject NSObject init
- AClass NSObject methodForSelector:
+ AClass NSObject resolveInstanceMethod:
+ AClass NSObject resolveInstanceMethod:
- AClass NSObject dealloc

write: by coderiding \ by erliucxy \ 东区-中山-广东 2018.2.12 5:25:51