美文网首页常用技术收集iOS 技术文档收录iOS Blog
Runtime Method Swizzling开发实例汇总(持

Runtime Method Swizzling开发实例汇总(持

作者: 卖报的小画家Sure | 来源:发表于2016-11-10 15:37 被阅读4308次
    前言:什么是Method Swizzling,在iOS开发中它有什么作用?

    简单来说我们主要是使用Method Swizzling来把系统的方法交换为我们自己的方法,从而给系统方法添加一些我们想要的功能。该篇文章主要列举Method Swizzling在开发中的一些现实用例,同时文中也有补充读者的一些疑点。希望阅读文章的朋友们也可以提供一些文中尚未举出的例子,本文持续更新中。

    目前已更新实例汇总:
    • 实例一:替换ViewController生命周期方法
    • 实例二:解决获取索引、添加、删除元素越界崩溃问题
    • 实例三:防止按钮重复暴力点击
    • 实例四:全局更换控件初始效果
    • 实例五:App热修复
    • 实例六:App异常加载占位图通用类封装
    • 实例七:全局修改导航栏后退(返回)按钮
    Method Swizzling通用方法封装

    在列举之前,我们可以将Method Swizzling功能封装为类方法,作为NSObject的类别,这样我们后续调用也会方便些。

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    @interface NSObject (Swizzling) 
    
    + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                             bySwizzledSelector:(SEL)swizzledSelector;
    @end
    
    #import "NSObject+Swizzling.h"
    @implementation NSObject (Swizzling)
    
    + (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
    
    ⚠️补充知识点
    • SEL、Method、IMP的含义及区别

    在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键是这个方法的名字(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。
    伪代码表示:

    Class {
            MethodList (
                        Method{
                            SEL:IMP;
                        }
                        Method{
                            SEL:IMP;
                        }
                        );
            };
    

    Method Swizzling就是改变类的消息分发列表来让消息解析时从一个选择器(SEL)对应到另外一个的实现(IMP),同时将原始的方法实现混淆到一个新的选择器(SEL)。

    • 为什么要添加didAddMethod判断?

    先尝试添加原SEL其实是为了做一层保护,因为如果这个类没有实现originalSelector,但其父类实现了,那class_getInstanceMethod会返回父类的方法。这样method_exchangeImplementations替换的是父类的那个方法,这当然不是我们想要的。所以我们先尝试添加 orginalSelector,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。
    如果理解还不够透彻,我们可以进入runtime.h中查看class_addMethod源码解释:

    /** 
     * Adds a new method to a class with a given name and implementation.
     * 
     * @param cls The class to which to add a method.
     * @param name A selector that specifies the name of the method being added.
     * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
     * @param types An array of characters that describe the types of the arguments to the method. 
     * 
     * @return YES if the method was added successfully, otherwise NO 
     *  (for example, the class already contains a method implementation with that name).
     *
     * @note class_addMethod will add an override of a superclass's implementation, 
     *  but will not replace an existing implementation in this class. 
     *  To change an existing implementation, use method_setImplementation.
     */
    

    大概的意思就是我们可以通过class_addMethod为一个类添加方法(包括方法名称(SEL)和方法的实现(IMP)),返回值为BOOL类型,表示方法是否成功添加。需要注意的地方是class_addMethod会添加一个覆盖父类的实现,但不会取代原有类的实现。也就是说如果class_addMethod返回YES,说明子类中没有方法originalSelector,通过class_addMethod为其添加了方法originalSelector,并使其实现(IMP)为我们想要替换的实现。

    class_addMethod(class,originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
    

    同时再将原有的实现(IMP)替换到swizzledMethod方法上,

    class_replaceMethod(class,swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
    

    从而实现了方法的交换,并且未影响父类方法的实现。反之如果class_addMethod返回NO,说明子类中本身就具有方法originalSelector的实现,直接调用交换即可。

    method_exchangeImplementations(originalMethod, swizzledMethod);
    

    这一部分内容比较绕口,希望大家可以耐下心来仔细反复阅读。

    -------------------------------实例列举-------------------------------
    实例一:替换ViewController生命周期方法

    App跳转到某具有网络请求的界面时,为了用户体验效果常会添加加载栏或进度条来显示当前请求情况或进度。这种界面都会存在这样一个问题,在请求较慢时,用户手动退出界面,这时候需要去除加载栏。
    当然可以依次在每个界面的viewWillDisappear方法中添加去除方法,但如果类似的界面过多,一味的复制粘贴也不是方法。这时候就能体现Method Swizzling的作用了,我们可以替换系统的viewWillDisappear方法,使得每当执行该方法时即自动去除加载栏。

    #import "UIViewController+Swizzling.h"
    #import "NSObject+Swizzling.h"
    @implementation UIViewController (Swizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self methodSwizzlingWithOriginalSelector:@selector(viewWillDisappear:) bySwizzledSelector:@selector(sure_viewWillDisappear:)];
        });
    }
    
    - (void)sure_viewWillDisappear:(BOOL)animated {
        [self sure_viewWillDisappear:animated];
        [SVProgressHUD dismiss];
    }
    

    代码如上,这样就不用考虑界面是否移除加载栏的问题了。补充一点,通常我们也会在生命周期方法中设置默认界面背景颜色,因若背景颜色默认为透明对App的性能也有一定影响,这大家可以在UIKit性能优化那篇文章中查阅。但类似该类操作也可以书写在通用类中,所以具体使用还要靠自己定夺。

    ⚠️补充知识点
    • 为什么方法交换调用在+load方法中?

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

    • 为什么方法交换要在dispatch_once中执行?

    方法交换应该要线程安全,而且保证在任何情况下(多线程环境,或者被其他人手动再次调用+load方法)只交换一次,防止再次调用又将方法交换回来。除非只是临时交换使用,在使用完成后又交换回来。 最常用的解决方案是在+load方法中使用dispatch_once来保证交换是安全的。之前有读者反馈+load方法本身即为线程安全,为什么仍需添加dispatch_once,其原因就在于+load方法本身无法保证其中代码只被执行一次。

    • 为什么没有发生死循环?

    一定有很多读者有疑惑,为什么sure_viewWillDisappear方法中的代码没有发生递归死循环。其原因很简单,因为方法已经执行过交换,调用[self sure_viewWillDisappear:animated]本质是在调用原有方法viewWillDisappear,反而如果我们在方法中调用[self viewWillDisappear:animated]才真的会发生死循环。是不是很绕?仔细看看。

    实例二:解决获取索引、添加、删除元素越界崩溃问题

    对于NSArray、NSDictionary、NSMutableArray、NSMutableDictionary不免会进行索引访问、添加、删除元素的操作,越界问题也是很常见,这时我们可以通过Method Swizzling解决这些问题,越界给予提示防止崩溃。

    这里以NSMutableArray为例说明

    #import "NSMutableArray+Swizzling.h"
    #import "NSObject+Swizzling.h"
    @implementation NSMutableArray (Swizzling)
    
    + (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
    

    对应大家可以举一反三,相应的实现添加、删除等,以及NSArray、NSDictionary等操作,因代码篇幅较大,这里就不一一书写了。
    这里没有使用self来调用,而是使用objc_getClass("__NSArrayM")来调用的。因为NSMutableArray的真实类只能通过后者来获取,而不能通过[self class]来获取,而method swizzling只对真实的类起作用。这里就涉及到一个小知识点:类簇。补充以上对象对应类簇表。


    类簇表.png
    实例三:防止按钮重复暴力点击

    程序中大量按钮没有做连续响应的校验,连续点击出现了很多不必要的问题,例如发表帖子操作,用户手快点击多次,就会导致同一帖子发布多次。

    #import <UIKit/UIKit.h>
    //默认时间间隔
    #define defaultInterval 1
    @interface UIButton (Swizzling)
    //点击间隔
    @property (nonatomic, assign) NSTimeInterval timeInterval;
    //用于设置单个按钮不需要被hook
    @property (nonatomic, assign) BOOL isIgnore;
    @end
    
    #import "UIButton+Swizzling.h"
    #import "NSObject+Swizzling.h"
    
    @implementation UIButton (Swizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self methodSwizzlingWithOriginalSelector:@selector(sendAction:to:forEvent:) bySwizzledSelector:@selector(sure_SendAction:to:forEvent:)];
        });
    }
    
    - (NSTimeInterval)timeInterval{
        return [objc_getAssociatedObject(self, _cmd) doubleValue];
    }
    - (void)setTimeInterval:(NSTimeInterval)timeInterval{
        objc_setAssociatedObject(self, @selector(timeInterval), @(timeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
    }
    //当按钮点击事件sendAction 时将会执行sure_SendAction
    - (void)sure_SendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
        if (self.isIgnore) {
            //不需要被hook
            [self sure_SendAction:action to:target forEvent:event];
            return;
        }
        if ([NSStringFromClass(self.class) isEqualToString:@"UIButton"]) {
            self.timeInterval =self.timeInterval == 0 ?defaultInterval:self.timeInterval;
            if (self.isIgnoreEvent){
                return;
            }else if (self.timeInterval > 0){
                [self performSelector:@selector(resetState) withObject:nil afterDelay:self.timeInterval];
            }
        }
        //此处 methodA和methodB方法IMP互换了,实际上执行 sendAction;所以不会死循环
        self.isIgnoreEvent = YES;
        [self sure_SendAction:action to:target forEvent:event];
    }
    //runtime 动态绑定 属性
    - (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent{
        // 注意BOOL类型 需要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用错,否则set方法会赋值出错
        objc_setAssociatedObject(self, @selector(isIgnoreEvent), @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (BOOL)isIgnoreEvent{
        //_cmd == @select(isIgnore); 和set方法里一致
        return [objc_getAssociatedObject(self, _cmd) boolValue];
    }
    - (void)setIsIgnore:(BOOL)isIgnore{
        // 注意BOOL类型 需要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用错,否则set方法会赋值出错
        objc_setAssociatedObject(self, @selector(isIgnore), @(isIgnore), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    - (BOOL)isIgnore{
        //_cmd == @select(isIgnore); 和set方法里一致
        return [objc_getAssociatedObject(self, _cmd) boolValue];
    }
    - (void)resetState{
        [self setIsIgnoreEvent:NO];
    }
    @end
    
    实例四:全局更换控件初始效果

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

    #import "UILabel+Swizzling.h"
    #import "NSObject+Swizzling.h"
    @implementation UILabel (Swizzling)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self methodSwizzlingWithOriginalSelector:@selector(init) bySwizzledSelector:@selector(sure_Init)];
            [self methodSwizzlingWithOriginalSelector:@selector(initWithFrame:) bySwizzledSelector:@selector(sure_InitWithFrame:)];
            [self methodSwizzlingWithOriginalSelector:@selector(awakeFromNib) bySwizzledSelector:@selector(sure_AwakeFromNib)];
        });
    }
    - (instancetype)sure_Init{
        id __self = [self sure_Init];
        UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
        if (font) {
            self.font=font;
        }
        return __self;
    }
    - (instancetype)sure_InitWithFrame:(CGRect)rect{
        id __self = [self sure_InitWithFrame:rect];
        UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
        if (font) {
            self.font=font;
        }
        return __self;
    }
    - (void)sure_AwakeFromNib{
        [self sure_AwakeFromNib];
        UIFont * font = [UIFont fontWithName:@"Zapfino" size:self.font.pointSize];
        if (font) {
            self.font=font;
        }
    }
    @end
    

    这一实例个人认为使用率可能不高,对于产品的设计这些点都是已经确定好的,更改的几率很低。况且我们也可以使用appearance来进行统一设置。

    实例五:App热修复

    因为AppStore上线审核时间较长,且如果在线上版本出现bug修复起来也是很困难,这时App热修复就可以解决此问题。热修复即在不更改线上版本的前提下,对线上版本进行更新甚至添加模块。国内比较好的热修复技术:JSPatch。JSPatch能做到通过JS调用和改写OC方法最根本的原因是Objective-C是动态语言,OC上所有方法的调用/类的生成都通过Objective-C Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,进而替换出现bug的方法或者添加方法等。bang的博客上有详细的描述有兴趣可以参考,这里就不赘述了。

    实例六:App异常加载占位图通用类封装(更新于:2016/12/01)

    详情可见文章:《零行代码为App添加异常加载占位图》
    在该功能模块中,使用Runtime Method Swizzling进行替换tableView、collectionView的reloadData方法,使得每当执行刷新操作时,自动检测当前组数与行数,从而实现零代码判断占位图是否显示的功能,同样也适用于网络异常等情况,详细设置可前往阅读。

    实例七:全局修改导航栏后退(返回)按钮(更新于:2016/12/05)

    在真实项目开发中,会全局统一某控件样式,以导航栏后退(返回)按钮为例,通常项目中会固定为返回字样,或者以图片进行显示等。

    iOS默认的返回按钮样式如下,默认为蓝色左箭头,文字为上一界面标题文字。


    默认返回按钮样式

    这里我们仍可以通过Runtime Method Swizzling来实现该需求,在使用Method Swizzling进行更改之前,必须考虑注意事项,即尽可能的不影响原有操作,比如对于系统默认的返回按钮,与其对应的是有界面边缘右滑返回功能的,因此我们进行统一更改后不可使其功能废弃。

    闲话少说,我们创建基于UINavigationItem的类别,在其load方法中替换方法backBarButtonItem
    代码如下

    #import "UINavigationItem+Swizzling.h"
    #import "NSObject+Swizzling.h"
    static char *kCustomBackButtonKey;
    @implementation UINavigationItem (Swizzling)
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self methodSwizzlingWithOriginalSelector:@selector(backBarButtonItem)
                                   bySwizzledSelector:@selector(sure_backBarButtonItem)];
    
        });
    }
    
    - (UIBarButtonItem*)sure_backBarButtonItem {
        UIBarButtonItem *backItem = [self sure_backBarButtonItem];
        if (backItem) {
            return backItem;
        }
        backItem = objc_getAssociatedObject(self, &kCustomBackButtonKey);
        if (!backItem) {
            backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL];
            objc_setAssociatedObject(self, &kCustomBackButtonKey, backItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        return backItem;
    }
    @end
    

    这里进行将返回按钮的文字清空操作,其他需求样式大家也可随意替换,现在再次运行程序,就会发现所有的返回按钮均只剩左箭头,并右滑手势依然有效。如图所示


    全局统一设置返回按钮

    暂时写到这里,部分内容来源于网络,后续还会更新。
    最后,还是希望看过的朋友们可以提供一些自己开发中的实例加以补充。

    相关文章

      网友评论

      • 狮子汉堡:你好,我不明的的是,在class_addMethod或者class_replaceMethod的时候,为什么要用对方的IMP和Type呢?
      • 雪_晟:防重复点击后 系统的拍照按钮点击不能拍照了
      • andy_xin:看到那个防按钮重复点击的,当同个按钮添加多个点击点击事件,如
        [button addTarget:self action:@selector(dismissWithButton:) forControlEvents:UIControlEventTouchUpInside];
        [button addTarget:self action:@selector(setHighlightBackgroundColorForButton:) forControlEvents:UIControlEventTouchDown];
        [button addTarget:self action:@selector(setBackgroundColorForButton:) forControlEvents:UIControlEventTouchDragExit];
        的时候,就会出现问题
      • 来自宇宙边际的奥特蛋:全局修改导航栏后退(返回)按钮使用上面的方法失效了吗,试了一下没变化啊,求指点:joy:
      • Jakiro_Fan:大神,有个问题想要问下,你写的关于NSArray 和NSMUtableArray的真身是__NSArrayI 和__NSArrayM。我觉得不对,在不不同的iOS版本,他的真身是会发生变化的,而且同样是NSArray调用不用的方法,他的真身也是不同的。由于苹果底层是不会公开这些真身的,所以以此来解决数组越界是不对的。
      • ______Dx:想问一下 你写好的NSObject扩展方法 改成替换类方法
        里面的方法也是用的class_getClassMethod
        为什么实现不了
        原因是didAddMethod在originalMethod存在的情况下也是YES
        能帮我解答一下么
        ______Dx:@BeyondScience 谢谢,后来我有改成这个,可还是不行
        myk:不是class_getClassMethod,应该是class_getInstanceMethod
      • 背着吉他去流浪:大佬, 你的Demo地址在哪?
      • 34码的小孩子:在按钮防暴力点击的实例中,UIButton+swizzling 需要实现sendAction:to:forEvent:,才会调用sure_sendAction:to:forEvent: 而且,按道理这句话[self sure_sendAction:action to:target forEvent:event]; 应该是将action发出去了,但是按钮的点击事件没有响应。请问这是什么问题?如果没有实现sendAction:to:forEvent: 发现在交换两个方法那里是添加成功的。
        34码的小孩子:@__________mo Rac是什么??
        __________mo:@34码的小孩子 话说Rac实现的可以么。。。还没试~
        34码的小孩子:现在我是需要在UIButton+swizzling 需要实现sendAction:to:forEvent:,然后里面调用父类方法,就可以把action给传出去了:joy:
      • 28bb64fffadd:想问下,使用此种是否会和使用热更新一样,无法通过审核呢
        卖报的小画家Sure:这个情况还不是很清楚,苹果要求是在6月12号不去除热更新就会下架,今天刚好是12号。。。当然JSPatch这个插件肯定是不允许了,个人觉得其他的暂时没有什么风险,毕竟不是属于下发脚本到App中。
      • doudo:首先感谢分享,我有个问题,class_addMethod不是会覆盖父类的方法吗,为什么你在文章里还写道:并且未影响父类方法的实现呢?这里不是太懂,望指教。
        doudo:@卖报的小画家Sure 恩理解了,我昨天试了一下,对父类本身没影响,然后就想api的注释可能是自己开始理解错了。你的解释很到位,也很耐心,赞!
        卖报的小画家Sure:@doudo 仔细阅读下文中加粗的这一句话:“需要注意的地方是class_addMethod会添加一个覆盖父类的实现,但不会取代原有类的实现。”,这句话的意思是如果为一个子类添加方法A,而此时父类已有方法名称为A的方法。这时class_addMethod会为子类添加A方法,并覆盖(重写)父类方法A,并且该操作不会对父类的A方法的原有实现造成影响。可以认为是为子类添加了一个与父类重名的方法,但两个方法完全不一样。归根到底class_addMethod方法就是为了避免为子类添加方法不小心把父类的也替换掉了。不知现在能不能明白呢?
      • 50395d397ad3:很强大:+1:
      • 布袋的世界:···
        Class {
        MethodList (
        Method{
        SEL:IMP;
        }

        Method{
        SEL:IMP;
        }
        )
        }
        上面这个,一下就明白了他们三者的关系,谢谢!
        ···
      • 半块:数组越界 这块 不适用了
        卖报的小画家Sure:@半块 嗯呢,多谢提醒,手误忘改了
        半块:而且 在数组越界 这块 例如 safeObjectAtIndex方法中 if判断 if (index > self.count) 这个本身不适合了 仍然会报错 你应该改成 if (index > self.count - 1) {}
      • eagleTang:最近正好在看runtime这一块,但是具体如何使用,在怎样的场景下使用本来还是十分模糊的,但是看了你的文章之后,觉得可能在网上能够找到的对于 这一块最能让人理解的文章了, 多谢了
        卖报的小画家Sure:@eagleTang 多谢赞赏 :smile:
      • AllenYukin:看入迷了,大神写的真不错。可以对现有项目进行优化了。也了解关于runtime的一些问题。cool
        卖报的小画家Sure:@乄丶呵小北 不是很理解你的意思,是在替换方法中重复调用的问题吗?比如[self sure_viewWillDisAppear]这行代码?
        AllenYukin:@卖报的小画家Sure 大神我有个小疑问 就是弱引用的问题 在+load 方法中调用替换类的方法 会用到dispatch one 其中self 会发生循环引用么
        卖报的小画家Sure:@乄丶呵小北 多谢支持:smile:
      • 8823e4cc91b4:简明,清晰易懂,非常好:+1:
        卖报的小画家Sure:@BobStone 多谢支持
      • 未来行者:写的还是不错,尤其是关于数组,字典空值校验,这块在开发中还是比较有用的,其他的很多其实都可以在appearance设置
        卖报的小画家Sure:@还在路上的唐小哥 多谢,像控件初始化等的确可以在appearance设置,文中有写。

      本文标题:Runtime Method Swizzling开发实例汇总(持

      本文链接:https://www.haomeiwen.com/subject/vtatpttx.html