iOS iOS Developer

runtime之Method Swizzing

2016-10-22  本文已影响62人  动机至善私心了无

首先: 非常感谢大神的文章以及网上各种各样的学习资料,让我学习到了很多的知识,并且稍微运用了一下.我的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
上一篇下一篇

猜你喜欢

热点阅读