runtime之Method Swizzing

首先: 非常感谢大神的文章以及网上各种各样的学习资料,让我学习到了很多的知识,并且稍微运用了一下.我的Demo
动态添加方法, 交换类方法, 截取系统方法换成自定义方法, 使用runtime给系统类添加属性,获取所有成员变量, 获取属性列表, 获取协议列表, 获取方法列表这些在峥大神等人的文章(runtime中有链接)中都有介绍,我就不画蛇添足了.
简单列举一下我在工作中使用到的Method Swizzing吧(很多都是在网上和别人学习的,当然也有自己弄的)
只是简单介绍一下, 然后就是上代码的时候了

  1. 输出各个控制器名称(多人开发的时候控制器不太好寻找,我们在进入每个控制器的时候输出一下控制器的名字就很好找了)
    .h
//
//  UIViewController+Statistical.h
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/10.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//
/**
 *  输出当前控制器名称   分类黑魔法不需要导入头文件即可使用
 *
 *  @param Statistical 类别
 */
#import <UIKit/UIKit.h>
@interface UIViewController (Statistical)
@end

.m

//
//  UIViewController+Statistical.m
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/10.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//
#import "UIViewController+Statistical.h"
#import <objc/runtime.h>
@implementation UIViewController (Statistical)
+ (void)load
{
    [super load];
    Method methodDid = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method methodStatistical = class_getInstanceMethod([self class], @selector(statisticalViewDidLoad));
    method_exchangeImplementations(methodDid, methodStatistical);
}

/**
 *  输出控制器名称
 */
- (void)statisticalViewDidLoad
{
    NSString *string = [NSString stringWithFormat:@"%@",self.class];
    //这里加一个判断, 将系统的UIViewController的对象剔除掉
    if (![string containsString:@"UI"])
    {
        NSLog(@"当前控制器名称 : %@",self.class);
    }
    /**
     *  因为方法已经交换, 实际上这个方法调用的是[self viewDidLoad];
     */
    [self statisticalViewDidLoad];
}
@end

2.防止数组越界崩溃
.h

//
//  NSArray+Crash.h
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/10.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSArray (Crash)

@end

.m

//
//  NSArray+Crash.m
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/10.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import "NSArray+Crash.h"
#import <objc/runtime.h>

@implementation NSArray (Crash)
+ (void)load
{
    [super load];
    
    //这里不能使用[self class],因为__NSArrayI才是NSArray真正的类. 通过runtime函数获取真正的类objc_getClass("__NSArrayI");
    //一些常用类簇真身
    //NSArray                   __NSArrayI
    //NSMutableAray             __NSArrayM
    //NSDictionary              __NSDictionaryI
    //NSMutableDictionary       __NSDictionaryM
    
    Method objcMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method crashMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(crashObjectAtIndex:));
    method_exchangeImplementations(objcMethod, crashMethod);
}

#pragma mark ----判断数组是否越界,越界则输出
- (id)crashObjectAtIndex:(NSUInteger)index
{
    if (index > self.count - 1)
    {
        /**
         *  做一下异常处理,   C++语法
         */
        @try {
            return [self crashObjectAtIndex:index];
        }
        @catch (NSException *exception) {
//            NSLog(@"----------  %s Crash Because Method %s  ----------\n",class_getName(self.class), __func__);
//            NSLog(@"%@",[exception callStackSymbols]);
            
            //或者这样输出也行
            NSLog(@"异常名称: %@   异常原因: %@",exception.name,exception.reason);
            NSLog(@"%@",[exception callStackSymbols]);
        }
        @finally {}
        
        return nil;
    }
    else
    {
        return [self crashObjectAtIndex:index];
    }
}
@end

3.判断系统版本使用图片
.h

//
//  UIImage+ChangeImage.h
//  WXQRuntime
//
//  Created by 吴晓群 on 16/9/23.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIImage (ChangeImage)
/**
 *  使用runtime给系统类添加属性
 */
@property (nonatomic, copy)NSString *titleString;       //类别属性
@end

.m

//
//  UIImage+ChangeImage.m
//  WXQRuntime
//
//  Created by 吴晓群 on 16/9/23.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import "UIImage+ChangeImage.h"
#import <objc/runtime.h>            //runtime类

@implementation UIImage (ChangeImage)


#pragma mark ----给类别添加属性
static const char *titleStringKey;

- (void)setTitleString:(NSString *)titleString
{
    /**
     *  第一个参数: 给哪个对象添加关联
     第二个参数: 关联的key, 通过这个key获取
     第三个参数: 关联的value
     第四个参数: 关联的策略,就是copy, strong, assign 等
     */
    objc_setAssociatedObject(self, &titleStringKey, titleString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)titleString
{
    //根据关联的key,获取关联的值.
    return objc_getAssociatedObject(self, &titleStringKey);
}


#pragma mark ----自定义实现图片方法
+ (UIImage *)WXQ_imageName:(NSString *)name
{
    double verSion = [UIDevice currentDevice].systemVersion.doubleValue;
    if ((verSion >= 7.0))
    {
        //如果系统版本是7.0以上, 使用另外一套文件名结尾是'_os7'的图片
        name = [name stringByAppendingString:@"_os7"];
    }
    UIImage *image = [UIImage WXQ_imageName:name];
    if (image == nil)
    {
        NSLog(@"图片没有加载出来");
    }
    return [UIImage WXQ_imageName:name];
}

//加载分类到内存的时候调用
+ (void)load
{
    /**
     *  通过class_getInstanceMethod()函数从当前对象中的method list 获取method结构体,如果是类方法就使用class_getClassMethod()函数获取
     *
     *  @param class]     类
     *  @param suiBianXie 对象方法(减号方法)
     *
     *  @return 返回哪个类中的减号方法
     */
//    Method methodObject = class_getInstanceMethod([self class], @selector(suiBianXie));
    
    //获取两个类的类方法
    Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method m2 = class_getClassMethod([UIImage class], @selector(WXQ_imageName:));
    
    method_exchangeImplementations(m1, m2);
}
@end

4.防止按钮多次点击
.h

//
//  UIButton+PreVentMultipleClick.h
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/11.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//
/**
 *  使用黑魔法防止按钮在一定时间内被多次点击造成多次跳转至同一个界面,但是这会造成无论点击哪个按钮, 在规定的时间内都不能再点击任何按钮
 */
#import <UIKit/UIKit.h>

@interface UIButton (PreVentMultipleClick)

@property (nonatomic, assign)NSTimeInterval clickDurationTime;  //点击间隔时间
@end

.m

//
//  UIButton+PreVentMultipleClick.m
//  MethodSwizzingConclusion(黑魔法总结)
//
//  Created by 吴晓群 on 16/10/11.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import "UIButton+PreVentMultipleClick.h"
#import <objc/runtime.h>

@implementation UIButton (PreVentMultipleClick)

//默认的按钮点击时间
static const NSTimeInterval defaultDuration = 0.1;

//记录是否忽略按钮点击事件, 默认第一次执行事件
static BOOL _isIgnoreEvent = NO;
//设置执行按钮事件状态
static void resetState(){
    _isIgnoreEvent = NO;
}

static const char *clickDurationTimeKey = "clickDutationTimeKey";
#pragma mark ----关联对象
- (void)setClickDurationTime:(NSTimeInterval)clickDurationTime
{
#warning 这里最后的类型不能使用OBJC_ASSOCIATION_ASSIGN,否则时间间隔不是整数的话会崩溃.写成这样就可以了
    objc_setAssociatedObject(self, clickDurationTimeKey, @(clickDurationTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSTimeInterval)clickDurationTime
{
    return [objc_getAssociatedObject(self, clickDurationTimeKey) floatValue];
}

#pragma mark ----重写系统点击方法
- (void)replaceSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    // 保险起见, 判断下class类型
    if ([self isKindOfClass:[UIButton class]])
    {
        //1. 按钮点击间隔事件
        self.clickDurationTime = self.clickDurationTime == 0 ? defaultDuration : self.clickDurationTime;
        
        //2. 是否忽略按钮点击事件
        if (_isIgnoreEvent)
        {
            //2.1 忽略按钮事件
            //直接拦截掉super函数进行发送消息
            return;
        }
        else if (self.clickDurationTime > 0)
        {
            //2.2 不忽略按钮事件
            //后续在间隔时间内直接忽视按钮事件
            [self replaceSendAction:action to:target forEvent:event];
            _isIgnoreEvent = YES;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.clickDurationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                resetState();
            });
        }
    }
}

#pragma mark ----交换系统方法
+ (void)load
{
    Method buttonMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
    Method replaceMethod = class_getInstanceMethod([self class], @selector(replaceSendAction:to:forEvent:));
    method_exchangeImplementations(buttonMethod, replaceMethod);
}
@end

5.去除字符串中的null(这里是因为多人开发, 别人接收请求数据的时候没有做任何处理,导致项目中有一些地方会出现(null)字符,一开始打算在使用runtime在源头(也就是请求数据的地方)进行处理,但是后来感觉在终点(使用数据给界面赋值的时候)处理会比较方便一些)
.h

//
//  UILabel+RemoveNull.h
//  取出字符串中的null
//
//  Created by 吴晓群 on 16/10/18.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UILabel (RemoveNull)

@end

.m

//
//  UILabel+RemoveNull.m
//  取出字符串中的null
//
//  Created by 吴晓群 on 16/10/18.
//  Copyright © 2016年 sanMiTeconology. All rights reserved.
//

#import "UILabel+RemoveNull.h"
#import <objc/runtime.h>

@implementation UILabel (RemoveNull)
+ (void)load
{
    [super load];
    Method method = class_getInstanceMethod([self class], @selector(setText:));
    Method removeMethod = class_getInstanceMethod([self class], @selector(removeNullSetText:));
    method_exchangeImplementations(method, removeMethod);
}

- (void)removeNullSetText:(NSString *)string
{
    if (string == nil || [string isEqualToString:@"(null)"])
    {
        string = @"";
    }
#pragma mark ----字符串中包含某个字符串
    else if ([string rangeOfString:@"(null)"].location != NSNotFound)
    {
#pragma mark ----使用某字符串代替原来的字符串
        string = [string stringByReplacingOccurrencesOfString:@"(null)" withString:@""];
    }
    [self removeNullSetText:string];
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,093评论 4 62
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,554评论 33 466
  • 过不了几天就有人提醒你,曾经的你,内心有爱,无比善良!正是这种善良,让人觉得你软弱可欺!总会有人利用你的爱,用超乎...
    桉桉欧尼阅读 759评论 16 5