介绍Runtime

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

  • 开始之前,你可以直接查看图片文章图片链接

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

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

  • 文字总结write by erliucxy

  • 可以实现什么:给系统的类增加属性对象。

  • 方便的地方是://TODO

1.1主要涉及3个方法

  • objc_setAssociatedObject 设置

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_getAssociatedObject 获取

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 值来清除一个关联;因为可能会导致其他客户对其添加的属性也被移除了

  • 基本参数说明

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】

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);
}
  • 008合体例子

  • Associated Objects合体使用的例子

- (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

    • 在runtime中,通过数据结构来定义的一些类、方法、协议等;

  2. 通过 Foundation 框架的NSObject类定义的方法 - 接触runtime

    • 如isKindOfClass:

    • 如isMemberOfClass:

    • 如respondsToSelector:

    • 如methodForSelector:

  3. 通过对 runtime 函数的直接调用 - 接触runtime

    • objc_ 开头的方法

      1. 如objc_ivar_list

      2. 如objc_property_attribute_t

    • ivar_ 开头的方法

      1. 如ivar_getOffset

      2. 如ivar_list

    • protocol_ 开头的方法

      1. 如protocol_getName

      2. 如protocol_copyPropertyList

    • method_ 开头的方法

      1. 如method_getName

      2. 如method_setImplementation{第二神器用到}

      3. 如method_exchangeImplementations {第二神器用到}

      4. 如method_getTypeEncoding{第二神器用到}

    • sel_ 开头的方法

      1. 如sel_getName

      2. 如sel_registerName

    • imp_ 开头的方法

      1. 如imp_getBlock

      2. 如imp_removeBlock

      3. 如imp_implementationWithBlock

    • class_ 开头的方法

      1. 如class_addIvar

      2. 如class_getName

      3. 如class_addProperty

      4. 如class_getInstanceMethod {第二神器用到}write by erliucxy

      5. 如class_replaceMethod {第二神器用到}

      6. 如class_addMethod {第二神器用到}

第三部分:runtime的第二神器-Method Swizzling

3.1本质就是

  1. 交换IMP和SEL的对应关系,以达到替换方法实现的目的;

  2. 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.4 Method 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.5 Method 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.6 Method Swizzling常见的问题

  1. 多继承关系的类使用Method Swizzling时,应该注意的方面是?

    • 多个有继承关系的类的对象Swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被Swizzle的实现。

    • 在load中Swizzle不用担心这种问题,因为load类方法会默认从父类开始调用。

    • 深入了解//TODO

  2. 使用Method Swizzling时解决命名冲突

  3. 为什么Method Swizzling应该在+load方法中实现?write by erliucxy

    • 在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。

    • +load在每个类被装载到Runtime的时候调用

    • +initialize 在每个类第一次被发送消息的时候调用。

    • 之所以要在+load中进行,是因为方法交叉影响的是全局状态,+load中能保证在class 装载的时候进行交叉,而initialize没办法做到。

    • Swizzling在+load中执行时,不要调用[super load];因为如果是多继承,并且对同一个方法都进行了Swizzling,那么调用[super load]以后,父类的Swizzling就失效了。

  4. 为什么Method Swizzling应该在dispatch_once中完成?

    • 由于 swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 满足了所需要的需求,并且应该被当做使用 swizzling 的初始化单例方法的标准。

    • dispatch_once保证方法交叉只交叉一次

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

第四部分:疑问

4.1 Method Swizzling危险吗?

What are the Dangers of Method Swizzling in Objective C?

分析 Here are some of the pitfalls of method swizzling:

  • Method swizzling is not atomic

  • Changes behavior of un-owned code

  • Possible naming conflicts

  • Swizzling changes the method's arguments

  • The order of swizzles matters

  • Difficult to understand (looks recursive)

  • Difficult to debug

Last updated