天天看點

Runtime 函數 Swizzling 改變OC方法的排程順序

首先加入一個小知識:

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

#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);

    }

}

-------------------------------以上是對Swizzling方法封裝類别--------------------------------

runtime有很多用途:改變ViewController的生命周期、app熱更新、改變系統方法排程(解決擷取索引、添加、删除元素越界崩潰問題)等。今天主要說數組或者字典的越界crash問題。

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

以可變數組為例子:

//  NSMutableArray+Security.h

@interface NSMutableArray (Security)

//  NSMutableArray+Security.m

#import "NSMutableArray+Security.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 {

        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__);

        [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 [self safeObjectAtIndex:index];

- (void)safeRemoveObjectAtIndex:(NSUInteger)index {

    if (self.count <= 0) {

    if (index >= self.count) {

        NSLog(@"%s index out of bound", __FUNCTION__);

    [self safeRemoveObjectAtIndex:index];

然後你在工程中用可變數組的增删改查都不會crash了。

繼續閱讀