OC工具类:防崩溃策略(runtime机制)

由于上一篇文章中,很多人都说通过类别拓展,添加新方法,然后对新方法进行判断,来达到防止崩溃的目的,这样做法不利于项目的维护和后期的扩展,毕竟长期以来大家习惯了用系统自带的方法。所以最好的方法就是在系统方法里面判断,因此研究了下,通过runtime机制替换了系统的方法,来对参数添加判断,以此来防止崩溃现象。主要启发自这篇文章:

从NSArray看类簇,这篇文章比较详细介绍了类簇的设计模式。

风景.jpeg

一.原理

1.由于NSString、NSArray、NSMutableString、NSMutableArray、NSMutableDictionary这几个类型都是Class Clusters(类簇)设计模式设计出来的,它们偷偷隐藏了很多具体的实现类, 只暴露出简单的接口来供外层调用,实现了很好的封装。

首先我们利用 NSArray 的几个方法来创建实例对象:

(lldb) po [NSArray array]
<__NSArray0 0x600000017670>(
)
(lldb) po [NSArray arrayWithObject:@"Hello,Zie"];
<__NSSingleObjectArrayI 0x600000017680>(
Hello,Zie
)
po [NSArray arrayWithObjects:@1,@2, nil];
<__NSArrayI 0x100500050>(
1,
2
)

通过打印可以看到由 NSArray ,创建的对象并不是 NSArray 本身,有可能是 <__NSArray0 、 __NSSingleObjectArrayI 、 __NSArrayI >, 这里 NSArray 就是那个抽象类,而被创建出来那些奇奇怪的类就是作为具体的实现类,同时是内部私有的。 所以通过运行时的class_addMethod和class_replaceMethod来添加方法或是通过method_exchangeImplementations来替换系统中对应的原生方法必须依据它具体的实现类,也就是说必须依据<__NSArray0 、 __NSSingleObjectArrayI 、 __NSArrayI>这三者,而不能依据NSArray。

二.具体实现:以NSString和NSMutableArray为例

1.NSString:

NSString实例类型.png

经发现NSString实际的实现类有<__NSCFConstantString、NSTaggedPointerString、__NSCFString>这三者,由于NSMutableString主要实现类是<__NSCFString>,所以将<__NSCFString>相应的替代方法放到NSMutableString相关文件里面。

#import <objc/runtime.h>
#import "NSString+Safe.h"

@implementation NSString (Safe)

#pragma mark --- init method

+ (void)load {
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        NSString *tmpSubFromStr = @"substringFromIndex:";
        NSString *tmpSafeSubFromStr = @"safe_substringFromIndex:";
        NSString *tmpSafePointSubFromStr = @"safePoint_substringFromIndex:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafeSubFromStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafePointSubFromStr];
    
    
    
       NSString *tmpSubToStr = @"substringToIndex:";
        NSString *tmpSafeSubToStr = @"safe_substringToIndex:";
        NSString *tmpSafePointSubToStr = @"safePoint_substringToIndex:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubToStr newMethodStr:tmpSafeSubToStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubToStr newMethodStr:tmpSafePointSubToStr];
    
    
    
        NSString *tmpSubRangeStr = @"substringWithRange:";
        NSString *tmpSafeSubRangeStr = @"safe_substringWithRange:";
        NSString *tmpSafePointSubRangeStr = @"safePoint_substringWithRange:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubRangeStr newMethodStr:tmpSafeSubRangeStr];
    
       [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubRangeStr newMethodStr:tmpSafePointSubRangeStr];
    
    
    
        NSString *tmpRangeOfStr = @"rangeOfString:options:range:locale:";
        NSString *tmpSafeRangeOfStr = @"safe_rangeOfString:options:range:locale:";
        NSString *tmpSafePointRangeOfStr = @"safePoint_rangeOfString:options:range:locale:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpRangeOfStr newMethodStr:tmpSafeRangeOfStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpRangeOfStr newMethodStr:tmpSafePointRangeOfStr];
    
    
    
    });

}



这里因为将<__NSCFString>放到NSMutableString类别里面去替换,所以只做实例类<__NSCFConstantString、NSTaggedPointerString>的替换。
因为最终调用替换函数:method_exchangeImplementations(originalMethod, swizzledMethod);,所以

NSString *tmpSubFromStr = @"substringFromIndex:"; 
NSString *tmpSafeSubFromStr = @"safe_substringFromIndex:"; 
NSString *tmpSafePointSubFromStr = @"safePoint_substringFromIndex:";

每一个实例类别都应该有自己相对应的替换方法,这里tmpSubFromStr是系统的方法,tmpSafeSubFromStr是<__NSCFConstantString>实例类对应的替换方法,tmpSafePointSubFromStr是NSTaggedPointerString对应的替换方法。这里虽然是NSString,可以通过

SEL originalSelector = NSSelectorFromString(originalMethodStr); 
SEL swizzledSelector = NSSelectorFromString(newMethodStr);

转换成相应的SEL方法。如果每个实力类没有自己对应的方法,比如说像这样:

NSString *tmpSubFromStr = @"substringFromIndex:";
NSString *tmpSafeSubFromStr = @"safe_substringFromIndex:";
    
    
[self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafeSubFromStr];
    
[self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafeSubFromStr];

那最终只会对NSTaggedPointerString原来的substringFromIndex:函数进行替换,<__NSCFConstantString>实例类型的就不起作用。

// 获取 method
+ (Method)methodOfClassStr:(NSString *)classStr selector:(SEL)selector {
    return class_getInstanceMethod(NSClassFromString(classStr),selector);
}

// 判断添加 新方法 或 新方法 替换 原来 方法
+ (void)exchangeImplementationWithClassStr:(NSString *)classStr originalMethodStr:(NSString *)originalMethodStr newMethodStr:(NSString *)newMethodStr {

    SEL originalSelector = NSSelectorFromString(originalMethodStr);
    SEL swizzledSelector = NSSelectorFromString(newMethodStr);

    Method originalMethod = [NSString methodOfClassStr:classStr selector:NSSelectorFromString(originalMethodStr)];
    Method swizzledMethod = [NSString methodOfClassStr:classStr selector:NSSelectorFromString(newMethodStr)];
  
    // 判断 是否 可以添加 新方法
    BOOL didAddMethod =
class_addMethod(NSClassFromString(classStr),
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(NSClassFromString(classStr),
                        swizzledSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
    
    } else {
        // 替换 原有 方法
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}



这段代码主要通过运行时,先判断是否可以往该实际类里面添加新方法,如果可以则添加新方法,如果不行,则替换原有的方法。但我们要替换的函数一般系统包含,所以只会走替换函数method_exchangeImplementations,但为了系统的健壮性,还是有必要先进行判断。

#pragma mark --- implement method
/****************************************  substringFromIndex:  ***********************************/
/**
 从from位置截取字符串 对应 __NSCFConstantString

 @param from 截取起始位置
 @return 截取的子字符串
 */
- (NSString *)safe_substringFromIndex:(NSUInteger)from {
    if (from > self.length ) {
        return nil;
    }
    return [self safe_substringFromIndex:from];
}
/**
 从from位置截取字符串 对应  NSTaggedPointerString

 @param from 截取起始位置
 @return 截取的子字符串
 */
- (NSString *)safePoint_substringFromIndex:(NSUInteger)from {
    if (from > self.length ) {
        return nil;
    }
    return [self safePoint_substringFromIndex:from];
}

/****************************************  substringFromIndex:  ***********************************/
/**
 从开始截取到to位置的字符串  对应  __NSCFConstantString

 @param to 截取终点位置
 @return 返回截取的字符串
 */
- (NSString *)safe_substringToIndex:(NSUInteger)to {
    if (to > self.length ) {
        return nil;
    }
    return [self safe_substringToIndex:to];
}

/**
 从开始截取到to位置的字符串  对应  NSTaggedPointerString

 @param to 截取终点位置
 @return 返回截取的字符串
 */
- (NSString *)safePoint_substringToIndex:(NSUInteger)to {
   if (to > self.length ) {
        return nil;
    }
    return [self safePoint_substringToIndex:to];
}



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  对应  __NSCFConstantString

 @param searchString 指定 字符串
 @param mask 比较模式
 @param rangeOfReceiverToSearch 搜索 范围
 @param locale 本地化
 @return 返回搜索到的字符串 范围
 */
- (NSRange)safe_rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch locale:(nullable NSLocale *)locale {
    if (!searchString) {
        searchString = self;
    }

    if (rangeOfReceiverToSearch.location > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if (rangeOfReceiverToSearch.length > self.length) {
         rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if ((rangeOfReceiverToSearch.location + rangeOfReceiverToSearch.length) > self.length) {
         rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }


    return [self safe_rangeOfString:searchString options:mask range:rangeOfReceiverToSearch locale:locale];
}


/**
 搜索指定 字符串  对应  NSTaggedPointerString

 @param searchString 指定 字符串
 @param mask 比较模式
 @param rangeOfReceiverToSearch 搜索 范围
 @param locale 本地化
 @return 返回搜索到的字符串 范围
 */
- (NSRange)safePoint_rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch locale:(nullable NSLocale *)locale {
    if (!searchString) {
        searchString = self;
    }

    if (rangeOfReceiverToSearch.location > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if (rangeOfReceiverToSearch.length > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if ((rangeOfReceiverToSearch.location + rangeOfReceiverToSearch.length) > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }


    return [self safePoint_rangeOfString:searchString options:mask range:rangeOfReceiverToSearch locale:locale];
}

/*********************************** substringWithRange:  ***************************/
/**
 截取指定范围的字符串  对应  __NSCFConstantString

 @param range 指定的范围
 @return 返回截取的字符串
 */
- (NSString *)safe_substringWithRange:(NSRange)range {
    if (range.location > self.length) {
        return nil;
    }

    if (range.length > self.length) {
        return nil;
    }

    if ((range.location + range.length) > self.length) {
        return nil;
    }
    return [self safe_substringWithRange:range];
}

/**
 截取指定范围的字符串 对应  NSTaggedPointerString

 @param range 指定的范围
 @return 返回截取的字符串
 */
- (NSString *)safePoint_substringWithRange:(NSRange)range {
    if (range.location > self.length) {
        return nil;
    }

    if (range.length > self.length) {
        return nil;
    }

    if ((range.location + range.length) > self.length) {
        return nil;
    }
    return [self safePoint_substringWithRange:range];
}
@end

这里是具体的替换方法,对传入的参数进行判断,如果参数合理,就执行原来系统的方法,如果参数不合理,就直接返回适当内容(一般返回nil或直接return);这里主要说一点:

// __NSCFConstantString 类型
NSString *tmpConstantString = @"432423432432432432";
[tmpConstantString rangeOfString:nil];

比如说系统原有的rangeOfString:函数,当你传入为nil的时候,就会导致崩溃,所以你很容易,就会去替换掉系统的该函数,但这是没有效果的,


NSString的rangeOfString.png

从这里我们可以看出来,真正导致系统崩溃的是rangeOfString:options:range:locale:执行了系统的该函数,所以应该去替换的是系统的rangeOfString:options:range:locale:这个函数。

同样的NSMutableArray:

// __NSArrayM 类型
NSMutableArray *tmpMutableArrayM = [NSMutableArray arrayWithCapacity:0];
[tmpMutableArrayM removeObjectAtIndex:1000];

NSMutableArray的removeObjectAtIndex函数传入一个超过数组本身长度的索引,就会导致崩溃,所以你去替换系统的removeObjectAtIndex方法这样也是没有效果的


NSMutableArray的removeObjectAtIndex.png

因为真正导致该崩溃的因为是因为系统执行了removeObjectsInRange:函数,函数里面的参数range超出了数组的范围,需要对该参数进行判断,才能有效的防止崩溃,所以替换系统的方法,必须先清楚引起系统崩溃的具体原因,才能对症下药。

2.NSMutableArray:

NSMutableArray主要原理跟NSString是一样的,但这里有一点需要注意的:

由于NSArray和NSMutableArray里面都有objectAtIndex这个原生函数,但是NSArray和NSMutableArray的实际类不一样,所以NSArray和NSMutableArray里面的替换方法必须不一样,否则因为替换 method_exchangeImplementations(originalMethod, swizzledMethod);只是简单的进行将新方法替换掉原有的方法,但是它并没有类别的区分,所以如果NSArray和NSMutableArray里面的替换方法一样,那就只会执行NSMutableArray里面的替换方法,NSArray里面的不起作用(NSArray里面的load方法先执行)。

风景2.jpg

三.总结:

1.NSArray

主要替换objectAtIndex:方法

+ (void)load {
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        //替换 objectAtIndex
        NSString *tmpStr = @"objectAtIndex:";
        NSString *tmpFirstStr = @"safe_ZeroObjectAtIndex:";
        NSString *tmpThreeStr = @"safe_objectAtIndex:";
        NSString *tmpSecondStr = @"safe_singleObjectAtIndex:";
    
        [self exchangeImplementationWithClassStr:@"__NSArray0" originalMethodStr:tmpStr newMethodStr:tmpFirstStr];
    
        [self exchangeImplementationWithClassStr:@"__NSSingleObjectArrayI" originalMethodStr:tmpStr newMethodStr:tmpSecondStr];
    
        [self exchangeImplementationWithClassStr:@"__NSArrayI" originalMethodStr:tmpStr newMethodStr:tmpThreeStr];
    });
}

实现方法:

#pragma mark --- implement method

/**
 取出NSArray 第index个 值 对应 __NSArrayI

 @param index 索引 index
 @return 返回值
 */
- (id)safe_objectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_objectAtIndex:index];
}


/**
 取出NSArray 第index个 值 对应   __NSSingleObjectArrayI

 @param index 索引 index
 @return 返回值
 */
- (id)safe_singleObjectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_singleObjectAtIndex:index];
}

/**
 取出NSArray 第index个 值 对应 __NSArray0

 @param index 索引 index
 @return 返回值
 */
- (id)safe_ZeroObjectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safe_ZeroObjectAtIndex:index];
}

2.NSMutableArray

主要替换方法:objectAtIndex:、removeObjectsInRange:、insertObject:atIndex:、removeObject:inRange:这几个方法

+ (void)load {
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        //替换 objectAtIndex:
        NSString *tmpGetStr = @"objectAtIndex:";
        NSString *tmpSafeGetStr = @"safeMutable_objectAtIndex:";
        [self exchangeImplementationWithClassStr:@"__NSArrayM" originalMethodStr:tmpGetStr newMethodStr:tmpSafeGetStr];
    
    
        //替换 removeObjectsInRange:
        NSString *tmpRemoveStr = @"removeObjectsInRange:";
        NSString *tmpSafeRemoveStr = @"safeMutable_removeObjectsInRange:";
    
        [self exchangeImplementationWithClassStr:@"__NSArrayM" originalMethodStr:tmpRemoveStr newMethodStr:tmpSafeRemoveStr];
    
    
        //替换 insertObject:atIndex:
        NSString *tmpInsertStr = @"insertObject:atIndex:";
        NSString *tmpSafeInsertStr = @"safeMutable_insertObject:atIndex:";
    
        [self exchangeImplementationWithClassStr:@"__NSArrayM" originalMethodStr:tmpInsertStr newMethodStr:tmpSafeInsertStr];
    
        //替换 removeObject:inRange:
        NSString *tmpRemoveRangeStr = @"removeObject:inRange:";
        NSString *tmpSafeRemoveRangeStr = @"safeMutable_removeObject:inRange:";
    
        [self exchangeImplementationWithClassStr:@"__NSArrayM" originalMethodStr:tmpRemoveRangeStr newMethodStr:tmpSafeRemoveRangeStr];
    
    });
}

实现方法:
#pragma mark --- implement method

/**
 取出NSArray 第index个 值

 @param index 索引 index
 @return 返回值
 */
- (id)safeMutable_objectAtIndex:(NSUInteger)index {
    if (index >= self.count){
        return nil;
    }
    return [self safeMutable_objectAtIndex:index];
}

/**
 NSMutableArray 移除 索引 index 对应的 值

 @param range 移除 范围
 */
- (void)safeMutable_removeObjectsInRange:(NSRange)range {

    if (range.location > self.count) {
        return;
    }

    if (range.length > self.count) {
        return;
    }

    if ((range.location + range.length) > self.count) {
        return;
    }

     return [self safeMutable_removeObjectsInRange:range];
}


/**
 在range范围内, 移除掉anObject

 @param anObject 移除的anObject
 @param range 范围
 */
- (void)safeMutable_removeObject:(id)anObject inRange:(NSRange)range {
    if (range.location > self.count) {
        return;
    }

    if (range.length > self.count) {
        return;
    }

    if ((range.location + range.length) > self.count) {
        return;
    }

    if (!anObject){
        return;
    }


    return [self safeMutable_removeObject:anObject inRange:range];

}

/**
 NSMutableArray 插入 新值 到 索引index 指定位置

 @param anObject 新值
 @param index 索引 index
 */
- (void)safeMutable_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (index > self.count) {
            return;
    }

    if (!anObject){
        return;
    }

    [self safeMutable_insertObject:anObject atIndex:index];
}

3.NSString

主要替换方法:substringFromIndex:,substringToIndex:,substringWithRange:,rangeOfString:options:range:locale:这几个主要方法

+ (void)load {
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        // 替换 substringFromIndex:
        NSString *tmpSubFromStr = @"substringFromIndex:";
        NSString *tmpSafeSubFromStr = @"safe_substringFromIndex:";
        NSString *tmpSafePointSubFromStr = @"safePoint_substringFromIndex:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafeSubFromStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafePointSubFromStr];
    
    
        // 替换 substringToIndex:
        NSString *tmpSubToStr = @"substringToIndex:";
        NSString *tmpSafeSubToStr = @"safe_substringToIndex:";
        NSString *tmpSafePointSubToStr = @"safePoint_substringToIndex:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubToStr newMethodStr:tmpSafeSubToStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubToStr newMethodStr:tmpSafePointSubToStr];
    
    
        // 替换 substringWithRange:
        NSString *tmpSubRangeStr = @"substringWithRange:";
        NSString *tmpSafeSubRangeStr = @"safe_substringWithRange:";
        NSString *tmpSafePointSubRangeStr = @"safePoint_substringWithRange:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpSubRangeStr newMethodStr:tmpSafeSubRangeStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpSubRangeStr newMethodStr:tmpSafePointSubRangeStr];
    
    
        // 替换 rangeOfString:options:range:locale:
        NSString *tmpRangeOfStr = @"rangeOfString:options:range:locale:";
        NSString *tmpSafeRangeOfStr = @"safe_rangeOfString:options:range:locale:";
        NSString *tmpSafePointRangeOfStr = @"safePoint_rangeOfString:options:range:locale:";
    
    
        [self exchangeImplementationWithClassStr:@"__NSCFConstantString" originalMethodStr:tmpRangeOfStr newMethodStr:tmpSafeRangeOfStr];
    
        [self exchangeImplementationWithClassStr:@"NSTaggedPointerString" originalMethodStr:tmpRangeOfStr newMethodStr:tmpSafePointRangeOfStr];
    
    });
}

实现方法:
#pragma mark --- implement method

/****************************************  substringFromIndex:  ***********************************/
  /**
 从from位置截取字符串 对应 __NSCFConstantString

 @param from 截取起始位置
 @return 截取的子字符串
 */
- (NSString *)safe_substringFromIndex:(NSUInteger)from {
    if (from > self.length ) {
        return nil;
    }
    return [self safe_substringFromIndex:from];
}
/**
 从from位置截取字符串 对应  NSTaggedPointerString

 @param from 截取起始位置
 @return 截取的子字符串
 */
- (NSString *)safePoint_substringFromIndex:(NSUInteger)from {
    if (from > self.length ) {
        return nil;
    }
    return [self safePoint_substringFromIndex:from];
}

/****************************************  substringFromIndex:  ***********************************/
 /**
 从开始截取到to位置的字符串  对应  __NSCFConstantString

 @param to 截取终点位置
 @return 返回截取的字符串
 */
- (NSString *)safe_substringToIndex:(NSUInteger)to   {
    if (to > self.length ) {
        return nil;
    }
    return [self safe_substringToIndex:to];
}

/**
 从开始截取到to位置的字符串  对应  NSTaggedPointerString

 @param to 截取终点位置
 @return 返回截取的字符串
 */
  - (NSString *)safePoint_substringToIndex:(NSUInteger)to {
    if (to > self.length ) {
        return nil;
    }
    return [self safePoint_substringToIndex:to];
}



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  对应  __NSCFConstantString

 @param searchString 指定 字符串
 @param mask 比较模式
 @param rangeOfReceiverToSearch 搜索 范围
 @param locale 本地化
 @return 返回搜索到的字符串 范围
 */
- (NSRange)safe_rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch locale:(nullable NSLocale *)locale {
    if (!searchString) {
        searchString = self;
    }

    if (rangeOfReceiverToSearch.location > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if (rangeOfReceiverToSearch.length > self.length) {
         rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if ((rangeOfReceiverToSearch.location + rangeOfReceiverToSearch.length) > self.length) {
         rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }


    return [self safe_rangeOfString:searchString options:mask range:rangeOfReceiverToSearch locale:locale];
}


/**
 搜索指定 字符串  对应  NSTaggedPointerString

 @param searchString 指定 字符串
 @param mask 比较模式
 @param rangeOfReceiverToSearch 搜索 范围
 @param locale 本地化
 @return 返回搜索到的字符串 范围
 */
- (NSRange)safePoint_rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch locale:(nullable NSLocale *)locale {
    if (!searchString) {
        searchString = self;
    }

    if (rangeOfReceiverToSearch.location > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if (rangeOfReceiverToSearch.length > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }
  
    if ((rangeOfReceiverToSearch.location + rangeOfReceiverToSearch.length) > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }


    return [self safePoint_rangeOfString:searchString options:mask range:rangeOfReceiverToSearch locale:locale];
}

/*********************************** substringWithRange:  ***************************/
/**
 截取指定范围的字符串  对应  __NSCFConstantString

 @param range 指定的范围
 @return 返回截取的字符串
 */
- (NSString *)safe_substringWithRange:(NSRange)range {
   if (range.location > self.length) {
        return nil;
    }

    if (range.length > self.length) {
        return nil;
    }

    if ((range.location + range.length) > self.length) {
        return nil;
    }
    return [self safe_substringWithRange:range];
}

/**
 截取指定范围的字符串 对应  NSTaggedPointerString

 @param range 指定的范围
 @return 返回截取的字符串
 */
- (NSString *)safePoint_substringWithRange:(NSRange)range {
    if (range.location > self.length) {
        return nil;
    }

    if (range.length > self.length) {
        return nil;
    }

    if ((range.location + range.length) > self.length) {
        return nil;
    }
    return [self safePoint_substringWithRange:range];
}

4.NSMutableString

主要替换方法:substringFromIndex:,substringToIndex:,substringWithRange:,rangeOfString:options:range:locale:,appendString,这几个主要方法

+ (void)load {
    //只执行一次这个方法
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        // 替换  substringFromIndex:
        NSString *tmpSubFromStr = @"substringFromIndex:";
        NSString *tmpSafeSubFromStr = @"safeMutable_substringFromIndex:";

        [self exchangeImplementationWithClassStr:@"__NSCFString" originalMethodStr:tmpSubFromStr newMethodStr:tmpSafeSubFromStr];
    
    
        // 替换  substringToIndex:
        NSString *tmpSubToStr = @"substringToIndex:";
        NSString *tmpSafeSubToStr = @"safeMutable_substringToIndex:";

        [self exchangeImplementationWithClassStr:@"__NSCFString" originalMethodStr:tmpSubToStr newMethodStr:tmpSafeSubToStr];
    
    
        // 替换  substringWithRange:
       NSString *tmpSubRangeStr = @"substringWithRange:";
        NSString *tmpSafeSubRangeStr = @"safeMutable_substringWithRange:";

        [self exchangeImplementationWithClassStr:@"__NSCFString" originalMethodStr:tmpSubRangeStr newMethodStr:tmpSafeSubRangeStr];
    
    
        // 替换  rangeOfString:options:range:locale:
        NSString *tmpRangeOfStr = @"rangeOfString:options:range:locale:";
        NSString *tmpSafeRangeOfStr = @"safeMutable_rangeOfString:options:range:locale:";
    
        [self exchangeImplementationWithClassStr:@"__NSCFString" originalMethodStr:tmpRangeOfStr newMethodStr:tmpSafeRangeOfStr];
    
        // 替换  appendString
        NSString *tmpAppendStr = @"appendString:";
        NSString *tmpSafeAppendStr = @"safeMutable_appendString:";
    
        [self exchangeImplementationWithClassStr:@"__NSCFString" originalMethodStr:tmpAppendStr newMethodStr:tmpSafeAppendStr];
    
    });
 }

实现方法:
#pragma mark --- implement method
/**************************************** substringFromIndex: ***********************************/
/**
从from位置截取字符串 对应 __NSCFString

 @param from 截取起始位置
 @return 截取的子字符串
 */
- (NSString *)safeMutable_substringFromIndex:(NSUInteger)from {
    if (from > self.length ) {
        return nil;
    }
    return [self safeMutable_substringFromIndex:from];
}


/****************************************  substringFromIndex:  ***********************************/
/**
 从开始截取到to位置的字符串  对应  __NSCFString

 @param to 截取终点位置
 @return 返回截取的字符串
 */
- (NSString *)safeMutable_substringToIndex:(NSUInteger)to {
    if (to > self.length ) {
        return nil;
    }
    return [self safeMutable_substringToIndex:to];
}



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  对应  __NSCFString

 @param searchString 指定 字符串
 @param mask 比较模式
 @param rangeOfReceiverToSearch 搜索 范围
 @param locale 本地化
 @return 返回搜索到的字符串 范围
 */
- (NSRange)safeMutable_rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch locale:(nullable NSLocale *)locale {
    if (!searchString) {
        searchString = self;
    }

    if (rangeOfReceiverToSearch.location > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if (rangeOfReceiverToSearch.length > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }

    if ((rangeOfReceiverToSearch.location + rangeOfReceiverToSearch.length) > self.length) {
        rangeOfReceiverToSearch = NSMakeRange(0, self.length);
    }


    return [self safeMutable_rangeOfString:searchString options:mask range:rangeOfReceiverToSearch locale:locale];
}



/*********************************** substringWithRange:  ***************************/
/**
 截取指定范围的字符串  对应  __NSCFString

 @param range 指定的范围
 @return 返回截取的字符串
 */
- (NSString *)safeMutable_substringWithRange:(NSRange)range {
    if (range.location > self.length) {
        return nil;
    }

    if (range.length > self.length) {
        return nil;
    }

    if ((range.location + range.length) > self.length) {
        return nil;
    }
    return [self safeMutable_substringWithRange:range];
}


/*********************************** safeMutable_appendString:  ***************************/
/**
 追加字符串 对应  __NSCFString

 @param aString 追加的字符串
 */
- (void)safeMutable_appendString:(NSString *)aString {
    if (!aString) {
        return;
    }
    return [self safeMutable_appendString:aString];
}

5.NSMutableDictionary

主要替换removeObjectForKey:和 setObject:forKey:这两个方法
+ (void)load {
//只执行一次这个方法
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

        // 替换 removeObjectForKey:
        NSString *tmpRemoveStr = @"removeObjectForKey:";
        NSString *tmpSafeRemoveStr = @"safeMutable_removeObjectForKey:";
    
        [self exchangeImplementationWithClassStr:@"__NSDictionaryM" originalMethodStr:tmpRemoveStr newMethodStr:tmpSafeRemoveStr];
    
    
        // 替换 setObject:forKey:
        NSString *tmpSetStr = @"setObject:forKey:";
        NSString *tmpSafeSetRemoveStr = @"safeMutable_setObject:forKey:";
    
        [self exchangeImplementationWithClassStr:@"__NSDictionaryM" originalMethodStr:tmpSetStr newMethodStr:tmpSafeSetRemoveStr];
    });
}

实现方法:
#pragma mark --- implement method

/**
 根据akey 移除 对应的 键值对

 @param aKey key
 */
- (void)safeMutable_removeObjectForKey:(id<NSCopying>)aKey {
    if (!aKey) {
        return;
    }
    [self safeMutable_removeObjectForKey:aKey];
}

/**
 将键值对 添加 到 NSMutableDictionary 内

 @param anObject 值
 @param aKey 键
 */
- (void)safeMutable_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
    if (!anObject) {
        return;
    }
    if (!aKey) {
        return;
    }
    return [self safeMutable_setObject:anObject forKey:aKey];
}

四: 感想

1. 常见坑

  • Method swizzling 是非原子性的,在多线程环境下可能被多次修改,但同样 Method swizzling 又是全局性的,就会造成不可预知的错误。

  • 可能出现命名冲突的问题,这样就不会调用到系统原方法,可能导致未知问题。

  • Method swizzling 看起来像递归,对新人来说不容易理解。

  • 出现问题 Method swizzling 不容易进行debug,来发现问题

  • 随着项目迭代和人员更换,使用Method swizzling 的项目不容易维护,因为开发人员有时根本不知道在Method swizzling 里面修改了东西。

2.总结

runtime是把双刃剑,因为所有的代码都运行在它之上,改变它,可能会改变代码的正常运行逻辑和所有与之交互的东西,因此会产生可怕的副作用。但同时它强大的功能也可以给应用的框架或者代码的编写带来非常大的遍历。

因此,对于runtime唯一的建议就是,需谨慎使用,一旦使用,必须先了解runtime的相关原理,做好预防措施,在添加完自己的代码之后,一定要调用系统原来的方法。

五: 最后

送上一张喜欢的图片:

风景3.jpeg

提醒:不应该把runtime的使用看成是高大上的东西,并以使用这个为荣,实际开发中runtime能少用应该少用,正常的系统方法才是正道!

**这是gitHub链接地址:FJ_Safe,大家有兴趣可以看一下,如果觉得不错,麻烦给个喜欢或star,若发现问题请及时反馈,谢谢! **

-- 2017,新的开始!

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

推荐阅读更多精彩内容