乐虎游戏|乐虎国际登录|欢迎你

深入剖析Objective-C中的Swizzle

日期:2020-04-15编辑作者:计算机资讯

method_name是函数的选择器,method_type是参数和返回值类型编码的c字符串,method_imp是指向实际函数的函数指针。可以通过下面两个方法访问到object_method对象:

SEL

SEL又叫选择器,是表示一个方法的selector的指针,是一个方法的编号。 用于表示运行时方法的名字,Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的标识,这个标识就是SEL。
工程中的所有的SEL组成一个Set集合,SEL是唯一的。只要方法名一样(参数类型可以不一致),它的值就是一样的,不管这个方法定义于哪个类,是不是实例方法。
不同的类可以拥有相同的selector,不同类的实例对象performSelector相同的selector时,会在各自的方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。

总结

method swizzling方法的强大之处在于可以替换的系统的方法实现,添加自己想要的功能。上面的一些使用场景都是网上找的一些经典例子,大家可以举一反三。

附上文章的原著。

+ method1{ // _cmd isEqueToString 'method2'}// at the catogary+ method2{ [self method2]; //_cmd isEqueToString 'method1'}

总结

我们常用的是方案一,方案二相对而言有些难理解,方案三相对来说,实现有些复杂,每个swizzling的方法都需要两个声明,一个定义,还是用函数方式来实现,不是OC语法。

应用场景,三种方案都应该在+load方法中调用,确保原子性、调用顺序,记得调用原方法实现。

  • 方案一:通常用在对_cmd没有严格要求,或者不用_cmd来做事情的场合;通常用方法名前加前缀的方式来避免命名冲突;考虑当前类未实现要被swizzle的方法的应用场景。
  • 方案二:要对某私有类来swizzling方法时,常采用;通常用方法名前加前缀的方式来避免命名冲突;考虑当前类未实现要被swizzle的方法的应用场景。
  • 方案三:标准的swizzling方案,通常用在对_cmd有严格要求,或者用_cmd来做事情的场合;不需要考虑命名冲突;要用static修饰的变量来保存相关IMP,考虑函数和变量的命名冲突。

应用二:全局更换UILabel的默认字体

需求

在项目比较成熟的基础上,遇到了这样一个需求,应用中需要引入新的字体,需要更换所有Label的默认字体,但是同时,对于一些特殊设置了字体的label又不需要更换。乍看起来,这个问题确实十分棘手,首先项目比较大,一个一个设置所有使用到的label的font工作量是巨大的,并且在许多动态展示的界面中,可能会漏掉一些label,产生bug。其次,项目中的label来源并不唯一,有用代码创建的,有xib和storyBoard中的,这也将浪费很大的精力。

解决办法

这是最简单方便的方法,我们可以使用runtime机制替换掉UILabel的初始化方法,在其中对label的字体进行默认设置。因为Label可以从initWithFrame、init和nib文件三个来源初始化,所以我们需要将这三个初始化的方法都替换掉。

实现代码

#import "UILabel+YHBaseChangeDefaultFont.h"
#import 
@implementation UILabel (YHBaseChangeDefaultFont)
/**
 *每个NSObject的子类都会调用下面这个方法 在这里将init方法进行替换,使用我们的新字体
 *如果在程序中又特殊设置了字体 则特殊设置的字体不会受影响 但是不要在Label的init方法中设置字体
 *从init和initWithFrame和nib文件的加载方法 都支持更换默认字体
 */
+(void)load{
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        //替换三个方法
        SEL originalSelector = @selector(init);
        SEL originalSelector2 = @selector(initWithFrame:);
        SEL originalSelector3 = @selector(awakeFromNib);
        SEL swizzledSelector = @selector(YHBaseInit);
        SEL swizzledSelector2 = @selector(YHBaseInitWithFrame:);
        SEL swizzledSelector3 = @selector(YHBaseAwakeFromNib);


        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method originalMethod2 = class_getInstanceMethod(class, originalSelector2);
        Method originalMethod3 = class_getInstanceMethod(class, originalSelector3);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        Method swizzledMethod2 = class_getInstanceMethod(class, swizzledSelector2);
        Method swizzledMethod3 = class_getInstanceMethod(class, swizzledSelector3);
        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        BOOL didAddMethod2 =
        class_addMethod(class,
                        originalSelector2,
                        method_getImplementation(swizzledMethod2),
                        method_getTypeEncoding(swizzledMethod2));
        BOOL didAddMethod3 =
        class_addMethod(class,
                        originalSelector3,
                        method_getImplementation(swizzledMethod3),
                        method_getTypeEncoding(swizzledMethod3));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));

        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        if (didAddMethod2) {
            class_replaceMethod(class,
                                swizzledSelector2,
                                method_getImplementation(originalMethod2),
                                method_getTypeEncoding(originalMethod2));
        }else {
            method_exchangeImplementations(originalMethod2, swizzledMethod2);
        }
        if (didAddMethod3) {
            class_replaceMethod(class,
                                swizzledSelector3,
                                method_getImplementation(originalMethod3),
                                method_getTypeEncoding(originalMethod3));
        }else {
            method_exchangeImplementations(originalMethod3, swizzledMethod3);
        }
    });

}
/**
 *在这些方法中将你的字体名字换进去
 */
- (instancetype)YHBaseInit
{
    id __self = [self YHBaseInit];
    UIFont * font = [UIFont fontWithName:@"这里输入你的字体名字" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }
    return __self;
}

-(instancetype)YHBaseInitWithFrame:(CGRect)rect{
    id __self = [self YHBaseInitWithFrame:rect];
    UIFont * font = [UIFont fontWithName:@"这里输入你的字体名字" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }
    return __self;
}
-(void)YHBaseAwakeFromNib{
    [self YHBaseAwakeFromNib];
    UIFont * font = [UIFont fontWithName:@"这里输入你的字体名字" size:self.font.pointSize];
    if (font) {
        self.font=font;
    }

}

@end

图片 1图1.png

Method Swizzling

Method Swizzling是一种改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。基于Runtime一系列强大的函数。

主要用到的函数如下:

OBJC_EXPORT BOOL
/** 
如果本类中包含一个同名的实现,则函数返回为NO
*/
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) ;

OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types);

OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

应用三: 数据统计

需求
跟踪记录APP中按钮的点击次数和频率等数据

实现代码

@implementation UIButton (Hook)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        Class selfClass = [self class];

        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);

        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }

    });
}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [CountTool addClickCount];
    [self mySendAction:action to:target forEvent:event];
}

@end

originImp是原始方法的IMP函数指针。如果想调用原始方法:

Method

主要包含三部分

  • 方法名:方法名为此方法的签名,有着相同函数名和参数名的方法有着相同的方法名。
  • 方法类型:方法类型描述了参数的类型。
  • IMP: IMP即函数指针,为方法具体实现代码块的地址,可像普通C函数调用一样使用IMP。

实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP。

Method Swizzling 原理

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}
typedef struct objc_method *Method;

SEL是把函数的名称,参数类型,返回值类型拼在一起进行hash化后得到的在一个类里唯一存在的用以标识唯一一个函数指针的数据类型。
IMP是用来调用C的函数的函数指针。
Type Coding把OC的各种类型进行了类型编码,其实就是进行了对应关系的约定。

OC里的Method其实是一个封装了方法名method_name(SEL),方法实现的函数指针method_imp(IMP),参数和返回值的类型(method_types)的结构体,由此我们知道方法的SEL和IMP是一一对应的,同一个类中没有相同的两个SEL。

原则上,方法的名称 name 和方法的实现 imp 是一一对应的,而 Method Swizzling 的原理就是动态地改变它们的对应关系,以达到替换方法实现的目的。

originImp(self,_cmd);

IMP

IMP指向方法实现的首地址,类似C语言的函数指针。IMP是消息最终调用的执行代码,是方法真正的实现代码 。

Method Swizzling的使用

1.给NSObject添加分类

@interface NSObject (Swizzling) 

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector; 

@end


====================================================================
#import "NSObject+MethodSwizzling.h"
#import 

@implementation NSObject (MethodSwizzling)
+  (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector
{
    Class class = [self class];

    Method originalMethod = class_getInstanceMethod(class, originalSelector);

    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
    BOOL didAddMethod = class_addMethod(class,originalSelector,

                                        method_getImplementation(swizzledMethod),

                                        method_getTypeEncoding(swizzledMethod));




    if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
        class_replaceMethod(class,swizzledSelector,

                            method_getImplementation(originalMethod),

                            method_getTypeEncoding(originalMethod));

    } else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可

        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end

简单调用:

#import "UIViewController+MethodSwizzling.h"
#import "NSObject+MethodSwizzling.h"

@implementation UIViewController (MethodSwizzling)
+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    [self methodSwizzlingWithOriginalSelector:@selector(viewWillAppear:) bySwizzledSelector:@selector(my_viewWillAppear:) ];
    });
}

-(void)my_viewWillAppear:(BOOL)animated{
    NSLog(@"调用了自己定义的viewWillAppear方法");
    [self my_viewWillAppear:YES];
}
@end

Tips:
1.为什么要在+(void)load方法里面调用method swizzling?
+load 和 +initialize 是 Objective-C runtime 会自动调用的两个类方法。但是它们被调用的时机却是有差别的,+load 方法是在类被加载的时候调用的,也就是一定会被调用。而 +initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。此外 +load 方法还有一个非常重要的特性,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。换句话说在 Objective-C runtime 自动调用 +load 方法时,分类中的 +load 方法并不会对主类中的 +load 方法造成覆盖。综上所述,+load 方法是实现 Method Swizzling 逻辑的最佳“场所”。

2.为什么使用dispatch_once执行方法交换?
方法交换应该要线程安全,而且保证在任何情况下(多线程环境,或者被其他人手动再次调用+load方法)只交换一次,除非只是临时交换使用,在使用完成后又交换回来。 最常用的用法是在+load方法中使用dispatch_once来保证交换是安全的。

void method_exchangeImplementations(Method m1, Method m2);

直接使用函数指针,下文中称为方案三

@implementation NSObject (Swizzle)
+ (void)swizzlleImpFromSel:(SEL)fromSel
                     toImp:(IMP)toImp
                     store:(IMP *)oldIMPPointer {
    IMP oldImp = NULL;
    Method method = class_getInstanceMethod(self, fromSel);
    if (method) {
        oldImp = class_replaceMethod(self, fromSel, toImp, method_getTypeEncoding(method));
        if (!oldImp) {
            oldImp = method_getImplementation(method);
        }
    }
    *oldIMPPointer = oldImp;
}
@end
//使用
static void MySetFrame(id self, SEL _cmd, CGRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, CGRect frame);

static void MySetFrame(id self, SEL _cmd, CGRect frame) {
    // do custom work
    NSLog(@"MySetFrame:%@", NSStringFromSelector(_cmd));
    SetFrameIMP(self, _cmd, frame);
}
- (void)viewDidLoad {
    [super viewDidLoad];
     [NSView swizzlle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

方案的缺点是 没有直接用方法一目了然,需要两个声明,一个函数指针。如果要swizzling的方法比较多,写着会比较麻烦。

应用一:NSMutableArray崩溃问题

当我们对一个NSMutableArray插入或者添加一个nil会导致崩溃,或者需要获取、移除的对象超过了数组的最大边界,就会出现数组越界,也会导致崩溃。

这个时候我们可以使用method swizzling来解决该问题,让数组在上述情况不会崩溃。

** 实现代码: **

#import "NSObject+MethodSwizzling.h"
#import "NSMutableArray+SWmethod.h"
#import 

@implementation NSMutableArray (SWmethod)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(removeObject:) bySwizzledSelector:@selector(safeRemoveObject:) ];

        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(addObject:) bySwizzledSelector:@selector(safeAddObject:)];

        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(removeObjectAtIndex:) bySwizzledSelector:@selector(safeRemoveObjectAtIndex:)];

        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(insertObject:atIndex:) bySwizzledSelector:@selector(safeInsertObject:atIndex:)];

        [objc_getClass("__NSArrayM") methodSwizzlingWithOriginalSelector:@selector(objectAtIndex:) bySwizzledSelector:@selector(safeObjectAtIndex:)];
    });
}



- (void)safeAddObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s can add nil object into NSMutableArray", __FUNCTION__);
    } else {
        [self safeAddObject:obj];
    }
}
- (void)safeRemoveObject:(id)obj {
    if (obj == nil) {
        NSLog(@"%s call -removeObject:, but argument obj is nil", __FUNCTION__);
        return;
    }
    [self safeRemoveObject:obj];
}

- (void)safeInsertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject == nil) {
        NSLog(@"%s can't insert nil into NSMutableArray", __FUNCTION__);
    } else if (index > self.count) {
        NSLog(@"%s index is invalid", __FUNCTION__);
    } else {
        [self safeInsertObject:anObject atIndex:index];
    }
}

- (id)safeObjectAtIndex:(NSUInteger)index {
    if (self.count == 0) {
        NSLog(@"%s can't get any object from an empty array", __FUNCTION__);
        return nil;
    }
    if (index > self.count) {
        NSLog(@"%s index out of bounds in array", __FUNCTION__);
        return nil;
    }
    return [self safeObjectAtIndex:index];
}

- (void)safeRemoveObjectAtIndex:(NSUInteger)index {
    if (self.count <= 0)="" {="" nslog(@"%s="" can't="" get="" any="" object="" from="" an="" empty="" array",="" __function__);="" return;="" }="" if="" (index="">= self.count) {
        NSLog(@"%s index out of bound", __FUNCTION__);
        return;
    }
    [self safeRemoveObjectAtIndex:index];
}
@end

上面只是展示了如何避免NSMutableArray的崩溃,主要是在原有的系统方法里面加上了判断。据此大家可以自己实现NSArray、NSDictonary的崩溃处理。

我们可以将这个函数作为一个IMP来使用,通过method_setImplementation方法,将这个IMP替换掉原始方法的IMP:

Swizzle的方法实现写在其他类中

以AFN 3.2.0中的一端代码为例,也是从中受到的启发。

static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}

@interface _AFURLSessionTaskSwizzling : NSObject

@end

@implementation _AFURLSessionTaskSwizzling

+ (void)load {
    if (NSClassFromString(@"NSURLSessionTask")) {

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        Class currentClass = [localDataTask class];

        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            Class superClass = [currentClass superclass];
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            currentClass = [currentClass superclass];
        }

        [localDataTask cancel];
        [session finishTasksAndInvalidate];
    }
}

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}
  • 当前类增加方法,不修改父类,下文中称思路为方案二
- (void)swizzleImpFromSel:(SEL)fromSel
                    toSel:(SEL)toSel
                 forClass:(Class)theClass
{

    Method fromMethod = class_getInstanceMethod(theClass, fromSel);
    Method toImpMethod= class_getInstanceMethod([self class], toSel);

    if (class_addMethod(theClass, toSel, method_getImplementation(toImpMethod), method_getTypeEncoding(toImpMethod))) {

        //如果当前类未实现fromSel方法,而是从父类继承过来的方法实现,class_addMethod为YES
        if (class_addMethod(theClass, fromSel, method_getImplementation(toImpMethod), method_getTypeEncoding(toImpMethod))) {

            class_replaceMethod(theClass, toSel, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
        }else{

            //这个地方一定注意,exchange的是theClass的两个方法
           Method toMethod= class_getInstanceMethod(theClass, toSel);
            method_exchangeImplementations(fromMethod, toMethod);
        }
    }

}
  • 父类中添加方法
- (void)swizzleImpFromSel:(SEL)fromSel
                    toSel:(SEL)toSel
                 forClass:(Class)theClass
{
    IMP theClassIMP = NULL;
    IMP superclassIMP = NULL;
    Class superClass = NULL;
    //找到方法fromSel真正实现的类
    while (class_getInstanceMethod(theClass, fromSel) ) {
        superClass = [theClass superclass];
        theClassIMP = method_getImplementation(class_getInstanceMethod(theClass, fromSel));
        superclassIMP = method_getImplementation(class_getInstanceMethod(superClass, fromSel));
        //把toSel添加到真正实现了fromSel方法的类上面,避免在toSel方法实现中调用toSel方法,父类无法响应toSel方法
        if (theClassIMP != superclassIMP) {
             Method method = class_getInstanceMethod([self class], toSel);
            [self swizzleImpFromSel:fromSel toSel:toSel toMethod:method forClass:theClass];
            break;
        }
        theClass = superClass;
    }
}
- (void)swizzleImpFromSel:(SEL)fromSel
                    toSel:(SEL)toSel
                 toMethod:(Method)toMethod
                 forClass:(Class)theClass{
    if (class_addMethod(theClass, toSel, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
        Method fromMethod = class_getInstanceMethod(theClass, fromSel);
        Method toMethod = class_getInstanceMethod(theClass, toSel);
        method_exchangeImplementations(fromMethod, toMethod);
    }
}
void (id,SEL,...)

危险性

#What are the Dangers of Method Swizzling in Objective C?

  • Method swizzling is not atomic
    Method swizzling不是原子性操作。如果在+load方法里面写,是没有问题的,但是如果写在+initialize方法中就会出现一些奇怪的问题。
  • Changes behavior of un-owned code
    如果你在一个类中重写一个方法,并且不调用super方法,你可能会导致一些问题出现。在大多数情况下,super方法是期望被调用的(除非有特殊说明)。如果你是用同样的思想来进行Swizzling,可能就会引起很多问题。如果你不调用原始的方法实现,那么你Swizzling改变的越多就越不安全。
  • Possible naming conflicts
    命名冲突是程序开发中经常遇到的一个问题。我们经常在类别中的前缀类名称和方法名称。不幸的是,命名冲突是在我们程序中的像一种瘟疫。一般我们用方案一的方式来写Method Swizzling
@interface NSView : NSObject
- (void)setFrame:(CGRect)frame;
@end

@implementation NSView
- (void)setFrame:(CGRect)frame {
    NSLog(@"setFrame:%@", NSStringFromSelector(_cmd));
}

- (void)my_viewSetFrame:(CGRect)frame {
    NSLog(@"%@..my_viewSetFrame", self);
    [self my_viewSetFrame:frame];
}
+ (void)load {
    [self swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_setFrame:)];
}
@end

但是如果程序的其他地方也定义了my_viewSetFrame :呢,那么会造成命名冲突的问题。
最好的方式是使用方案三,能有效的避免命名冲突的问题。原则上来说,其实上述做法更加符合标准化的Swizzling方法。

  • Swizzling changes the method's arguments
    标准的Method Swizzling是不会改变方法参数的。使用Swizzling中,会改变传递给原来的一个函数实现的参数,例如:
[self my_setFrame:frame];

转换成

objc_msgSend(self, @selector(my_setFrame:), frame);

objc_msgSend会去查找my_setFrame对应的IMP。一旦IMP找到,会把相同的参数传递进去。这里会找到最原始的setFrame:方法,调用执行它。但是这里的_cmd参数并不是setFrame:,现在是my_setFrame:。原始的方法就被一个它不期待的接收参数调用了。

用方案三,用函数指针去实现。参数就不会变了。

  • The order of swizzles matters
    调用顺序对于Swizzling来说,很重要。
    以方案一为例
@interface NSView : NSObject
- (void)setFrame:(CGRect)frame;
@end

@implementation NSView
- (void)setFrame:(CGRect)frame {
    NSLog(@"setFrame:%@", NSStringFromSelector(_cmd));
}

- (void)my_viewSetFrame:(CGRect)frame {
    NSLog(@"%@..my_viewSetFrame", self);
    [self my_viewSetFrame:frame];
}

@end

@interface NSContol : NSView

@end

@implementation NSContol
- (void)my_controlSetFrame:(CGRect)frame
{
    NSLog(@"%@..my_controlSetFrame", self);
    [self my_controlSetFrame:frame];
}

@end

@interface NSButton : NSContol
@end

@implementation NSButton
- (void)my_buttonSetFrame:(CGRect)frame
{
    NSLog(@"%@..my_buttonSetFrame", self);
    [self my_buttonSetFrame:frame];
}
@end
#pragma mark - 调用
- (void)viewDidLoad {
    [super viewDidLoad];
      [NSButton swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_buttonSetFrame:)];
    [NSContol swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_controlSetFrame:)];
    [NSView swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_viewSetFrame:)];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSButton *btn = [[NSButton alloc] init];
    btn.frame = CGRectMake(0, 0, 100, 200);

    NSContol *con = [[NSContol alloc] init];
    con.frame = CGRectMake(0, 0, 100, 200);

    NSView *view = [[NSView alloc] init];
    view.frame = CGRectMake(0, 0, 100, 200);
}
#pragma mark - 打印顺序一
     [NSButton swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_buttonSetFrame:)];
     [NSContol swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_controlSetFrame:)];
      [NSView swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_viewSetFrame:)];
//打印结果
<NSButton: 0x60c000005820>..my_buttonSetFrame
setFrame:my_buttonSetFrame:
<NSContol: 0x60c000005990>..my_controlSetFrame
setFrame:my_controlSetFrame:
<NSView: 0x604000005730>..my_viewSetFrame
setFrame:my_viewSetFrame:

#pragma mark - 打印顺序二
  [NSView swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_viewSetFrame:)];
  [NSContol swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_controlSetFrame:)];
  [NSButton swizzleImpFromSel:@selector(setFrame:) toSel:@selector(my_buttonSetFrame:)];
//打印结果
<NSButton: 0x6040000189a0>..my_buttonSetFrame
<NSButton: 0x6040000189a0>..my_controlSetFrame
<NSButton: 0x6040000189a0>..my_viewSetFrame
setFrame:my_viewSetFrame:
<NSContol: 0x60c000018740>..my_controlSetFrame
<NSContol: 0x60c000018740>..my_viewSetFrame
setFrame:my_viewSetFrame:
<NSView: 0x604000202ee0>..my_viewSetFrame
setFrame:my_viewSetFrame:

在load方法中加载swizzle,可以保证swizzle的顺序。load方法能保证父类会在其任何子类加载方法之前,加载相应的方法。

  • Difficult to understand (looks recursive)
    看着传统定义的swizzled method,我认为很难去预测会发生什么。但是对比上面标准的swizzling(方案三),还是很容易明白。这一点已经被解决了。
  • Difficult to debug
    在调试中,会出现奇怪的堆栈调用信息,尤其是swizzled的命名很混乱,一切方法调用都是混乱的。对比标准的swizzled方式,你会在堆栈中看到清晰的命名方法。swizzling还有一个比较难调试的一点, 在于你很难记住当前确切的哪个方法已经被swizzling了。

本文由乐虎游戏发布于计算机资讯,转载请注明出处:深入剖析Objective-C中的Swizzle

关键词:

计算机在iOS开垦中应用Protobuf

Protobuf简介 protocolbuffer 是google的一种数据交换的格式,它独立于语言,独立于平台。google提供了多种语言的实现:j...

详细>>

Atitit.收银系统pos 以及打印功能的行业标准

OG视讯直播,Atitit.收银系统pos以及打印功能的行业标准 之前公司有个面向商户的项目,需要连接商户打印机打印小票...

详细>>

自个儿的局地iOS进级路上的素材采取

其实就是我个人在具备初级开发能力后,进一步的提升技术能力和知识面的途径。 ** 本文摘自同行说用户“星空”分...

详细>>

iOS开拓:多个方可实时加点的Chart

商厦项目有亟待用到胎儿心率监测,设计的分界面逻辑是内需一个得以表格,可以动态的在下边画上数据。有一些相...

详细>>