Runtime 函数 Swizzling 改变OC方法的调度顺序

简介: 首先加入一个小知识:SEL、Method、IMP的含义及区别在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键是这个方法的名字(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。

首先加入一个小知识:

SEL、Method、IMP的含义及区别

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

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

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

 

对Swizzling方法封装

//

//  NSObject+Swizzling.h

//  Swizzling

//

//  Created by peter.zhang on 2016/12/14.

//  Copyright © 2016年 Peter. All rights reserved.

//

 

#import <Foundation/Foundation.h>

#import <objc/runtime.h>

 

@interface NSObject (Swizzling)

 

/**

 * Adds a new method to a class with a given name and implementation.

 *

 * @param originalSelector 原来的方法

 * @param swizzledSelector 替换成的方法

 *

*/

 

 + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector

                         bySwizzledSelector:(SEL)swizzledSelector;

 

@end

 

 

//

//  NSObject+Swizzling.m

//  Swizzling

//

//  Created by peter.zhang on 2016/12/14.

//  Copyright © 2016年 Peter. All rights reserved.

//

 

#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

 

-------------------------------以上是对Swizzling方法封装类别--------------------------------

runtime有很多用途:改变ViewController的生命周期、app热更新、改变系统方法调度(解决获取索引、添加、删除元素越界崩溃问题)等。今天主要说数组或者字典的越界crash问题。

 

啥都不是了,你把Swizzling方法封装类别添加到工程中:

以可变数组为例子:

//

//  NSMutableArray+Security.h

//  Swizzling

//

//  Created by peter.zhang on 2016/12/14.

//  Copyright © 2016年 Peter. All rights reserved.

//

 

#import <Foundation/Foundation.h>

 

@interface NSMutableArray (Security)

 

@end

 

 

 

//

//  NSMutableArray+Security.m

//  Swizzling

//

//  Created by peter.zhang on 2016/12/14.

//  Copyright © 2016年 Peter. All rights reserved.

//

 

#import "NSMutableArray+Security.h"

#import "NSObject+Swizzling.h"

 

@implementation NSMutableArray (Security)

 

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

 

然后你在工程中用可变数组的增删改查都不会crash了。

 

相关文章
|
存储 运维 安全
iOS加固原理与常见措施:保护移动应用程序安全的利器
iOS加固原理与常见措施:保护移动应用程序安全的利器
162 0
|
开发框架 前端开发 Swift
【Swift开发专栏】Swift与跨平台应用开发
【4月更文挑战第30天】Swift 在跨平台开发中优缺点并存,其代码复用性、高性能和易于集成是亮点,但生态系统限制和高学习成本是挑战。开发者可借助 SwiftUI、Combine 等工具,配合React Native、Flutter、Xamarin等框架实现跨平台。Swift 不是独立的跨平台框架,但能与其他框架结合使用,适用于不同项目需求。
454 0
|
存储 JavaScript 前端开发
HarmonyOS 3.1/4.0应用升级到HarmonyOS NEXT改动点
在 “2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)”(<https://coding.imooc.com/class/843.html>)视频课程中,因为讲师在该课程授课时是使用的HarmonyOS 3.1/4.0应用(API 9),如果部分学员采用了最新的HarmonyOS NEXT API,此时就会遇到API兼容性的问题。
483 0
HarmonyOS 3.1/4.0应用升级到HarmonyOS NEXT改动点
|
前端开发 编译器 测试技术
Kotlin Multiplatform 跨平台开发的优化策略与实践
本文深入讲解Kotlin Multiplatform(KMP)的优化策略与实践。KMP是由JetBrains推出的开源技术,允许跨平台共享代码同时保持原生优势。文章覆盖KMP核心概念、性能优化技巧(如代码结构优化、利用`expect`/`actual`关键字、Kotlin/Native性能特性等),以及在移动、桌面和Web应用的实际案例分析。此外,还介绍了如何利用KMP生态系统工具进行快速开发,并展望了KMP的未来发展。
378 0
|
Java Maven
Maven国内镜像配置
Maven国内镜像配置
20109 1
|
Web App开发 Rust Kubernetes
2021 年 Rust 生态调研报告 | 星辰大海 【上篇】
2021 年 Rust 生态调研报告 | 星辰大海 【上篇】
1018 0
|
API iOS开发
iOS底层原理:Method Swizzling原理和注意事项(二)
Method Swizzling的含义是方法交换,其核心内容是使用runtime api在运行时将一个方法的实现替换成另一个方法的实现。我们利用它可以替换系统或者我们自定义类的方法实现,进而达到我们的特殊目的,这就是我们常说的iOS黑魔法。
iOS底层原理:Method Swizzling原理和注意事项(二)
|
编译器 Linux 开发工具
Swift REPL简介
原文链接:https://developer.apple.com/swift/blog/?id=18 Xcode 6.1引入了另外一种以交互式的方式来体验Swift的方法:Read Eval PrintLoop,简称REPL。
1441 0