OC工具類:防崩潰策略(runtime機(jī)制)

由于上一篇文章中,很多人都說通過類別拓展,添加新方法,然后對新方法進(jìn)行判斷,來達(dá)到防止崩潰的目的,這樣做法不利于項目的維護(hù)和后期的擴(kuò)展,畢竟長期以來大家習(xí)慣了用系統(tǒng)自帶的方法。所以最好的方法就是在系統(tǒng)方法里面判斷,因此研究了下,通過runtime機(jī)制替換了系統(tǒng)的方法,來對參數(shù)添加判斷,以此來防止崩潰現(xiàn)象。主要啟發(fā)自這篇文章:

從NSArray看類簇,這篇文章比較詳細(xì)介紹了類簇的設(shè)計模式。

風(fēng)景.jpeg

一.原理

1.由于NSString、NSArray、NSMutableString、NSMutableArray、NSMutableDictionary這幾個類型都是Class Clusters(類簇)設(shè)計模式設(shè)計出來的,它們偷偷隱藏了很多具體的實現(xiàn)類, 只暴露出簡單的接口來供外層調(diào)用,實現(xiàn)了很好的封裝。

首先我們利用 NSArray 的幾個方法來創(chuàng)建實例對象:

(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 ,創(chuàng)建的對象并不是 NSArray 本身,有可能是 <__NSArray0 、 __NSSingleObjectArrayI 、 __NSArrayI >, 這里 NSArray 就是那個抽象類,而被創(chuàng)建出來那些奇奇怪的類就是作為具體的實現(xiàn)類,同時是內(nèi)部私有的。 所以通過運行時的class_addMethod和class_replaceMethod來添加方法或是通過method_exchangeImplementations來替換系統(tǒng)中對應(yīng)的原生方法必須依據(jù)它具體的實現(xiàn)類,也就是說必須依據(jù)<__NSArray0 、 __NSSingleObjectArrayI 、 __NSArrayI>這三者,而不能依據(jù)NSArray。

二.具體實現(xiàn):以NSString和NSMutableArray為例

1.NSString:

NSString實例類型.png

經(jīng)發(fā)現(xiàn)NSString實際的實現(xiàn)類有<__NSCFConstantString、NSTaggedPointerString、__NSCFString>這三者,由于NSMutableString主要實現(xiàn)類是<__NSCFString>,所以將<__NSCFString>相應(yīng)的替代方法放到NSMutableString相關(guān)文件里面。

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

@implementation NSString (Safe)

#pragma mark --- init method

+ (void)load {
    //只執(zhí)行一次這個方法
    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>的替換。
因為最終調(diào)用替換函數(shù):method_exchangeImplementations(originalMethod, swizzledMethod);,所以

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

每一個實例類別都應(yīng)該有自己相對應(yīng)的替換方法,這里tmpSubFromStr是系統(tǒng)的方法,tmpSafeSubFromStr是<__NSCFConstantString>實例類對應(yīng)的替換方法,tmpSafePointSubFromStr是NSTaggedPointerString對應(yīng)的替換方法。這里雖然是NSString,可以通過

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

轉(zhuǎn)換成相應(yīng)的SEL方法。如果每個實力類沒有自己對應(yīng)的方法,比如說像這樣:

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

那最終只會對NSTaggedPointerString原來的substringFromIndex:函數(shù)進(jìn)行替換,<__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);
    }
}



這段代碼主要通過運行時,先判斷是否可以往該實際類里面添加新方法,如果可以則添加新方法,如果不行,則替換原有的方法。但我們要替換的函數(shù)一般系統(tǒng)包含,所以只會走替換函數(shù)method_exchangeImplementations,但為了系統(tǒng)的健壯性,還是有必要先進(jìn)行判斷。

#pragma mark --- implement method
/****************************************  substringFromIndex:  ***********************************/
/**
 從from位置截取字符串 對應(yīng) __NSCFConstantString

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

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

/****************************************  substringFromIndex:  ***********************************/
/**
 從開始截取到to位置的字符串  對應(yīng)  __NSCFConstantString

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

/**
 從開始截取到to位置的字符串  對應(yīng)  NSTaggedPointerString

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



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  對應(yīng)  __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];
}


/**
 搜索指定 字符串  對應(yīng)  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:  ***************************/
/**
 截取指定范圍的字符串  對應(yīng)  __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];
}

/**
 截取指定范圍的字符串 對應(yīng)  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

這里是具體的替換方法,對傳入的參數(shù)進(jìn)行判斷,如果參數(shù)合理,就執(zhí)行原來系統(tǒng)的方法,如果參數(shù)不合理,就直接返回適當(dāng)內(nèi)容(一般返回nil或直接return);這里主要說一點:

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

比如說系統(tǒng)原有的rangeOfString:函數(shù),當(dāng)你傳入為nil的時候,就會導(dǎo)致崩潰,所以你很容易,就會去替換掉系統(tǒng)的該函數(shù),但這是沒有效果的,


NSString的rangeOfString.png

從這里我們可以看出來,真正導(dǎo)致系統(tǒng)崩潰的是rangeOfString:options:range:locale:執(zhí)行了系統(tǒng)的該函數(shù),所以應(yīng)該去替換的是系統(tǒng)的rangeOfString:options:range:locale:這個函數(shù)。

同樣的NSMutableArray:

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

NSMutableArray的removeObjectAtIndex函數(shù)傳入一個超過數(shù)組本身長度的索引,就會導(dǎo)致崩潰,所以你去替換系統(tǒng)的removeObjectAtIndex方法這樣也是沒有效果的


NSMutableArray的removeObjectAtIndex.png

因為真正導(dǎo)致該崩潰的因為是因為系統(tǒng)執(zhí)行了removeObjectsInRange:函數(shù),函數(shù)里面的參數(shù)range超出了數(shù)組的范圍,需要對該參數(shù)進(jìn)行判斷,才能有效的防止崩潰,所以替換系統(tǒng)的方法,必須先清楚引起系統(tǒng)崩潰的具體原因,才能對癥下藥。

2.NSMutableArray:

NSMutableArray主要原理跟NSString是一樣的,但這里有一點需要注意的:

由于NSArray和NSMutableArray里面都有objectAtIndex這個原生函數(shù),但是NSArray和NSMutableArray的實際類不一樣,所以NSArray和NSMutableArray里面的替換方法必須不一樣,否則因為替換 method_exchangeImplementations(originalMethod, swizzledMethod);只是簡單的進(jìn)行將新方法替換掉原有的方法,但是它并沒有類別的區(qū)分,所以如果NSArray和NSMutableArray里面的替換方法一樣,那就只會執(zhí)行NSMutableArray里面的替換方法,NSArray里面的不起作用(NSArray里面的load方法先執(zhí)行)。

風(fēng)景2.jpg

三.總結(jié):

1.NSArray

主要替換objectAtIndex:方法

+ (void)load {
    //只執(zhí)行一次這個方法
    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];
    });
}

實現(xiàn)方法:

#pragma mark --- implement method

/**
 取出NSArray 第index個 值 對應(yīng) __NSArrayI

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


/**
 取出NSArray 第index個 值 對應(yīng)   __NSSingleObjectArrayI

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

/**
 取出NSArray 第index個 值 對應(yīng) __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 {
    //只執(zhí)行一次這個方法
    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];
    
    });
}

實現(xiàn)方法:
#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 對應(yīng)的 值

 @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范圍內(nèi), 移除掉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 {
    //只執(zhí)行一次這個方法
    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];
    
    });
}

實現(xiàn)方法:
#pragma mark --- implement method

/****************************************  substringFromIndex:  ***********************************/
  /**
 從from位置截取字符串 對應(yīng) __NSCFConstantString

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

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

/****************************************  substringFromIndex:  ***********************************/
 /**
 從開始截取到to位置的字符串  對應(yīng)  __NSCFConstantString

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

/**
 從開始截取到to位置的字符串  對應(yīng)  NSTaggedPointerString

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



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  對應(yīng)  __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];
}


/**
 搜索指定 字符串  對應(yīng)  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:  ***************************/
/**
 截取指定范圍的字符串  對應(yīng)  __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];
}

/**
 截取指定范圍的字符串 對應(yīng)  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 {
    //只執(zhí)行一次這個方法
    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];
    
    });
 }

實現(xiàn)方法:
#pragma mark --- implement method
/**************************************** substringFromIndex: ***********************************/
/**
從from位置截取字符串 對應(yīng) __NSCFString

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


/****************************************  substringFromIndex:  ***********************************/
/**
 從開始截取到to位置的字符串  對應(yīng)  __NSCFString

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



/*********************************** rangeOfString:options:range:locale:  ***************************/
/**
 搜索指定 字符串  對應(yīng)  __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:  ***************************/
/**
 截取指定范圍的字符串  對應(yīng)  __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:  ***************************/
/**
 追加字符串 對應(yīng)  __NSCFString

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

5.NSMutableDictionary

主要替換removeObjectForKey:和 setObject:forKey:這兩個方法
+ (void)load {
//只執(zhí)行一次這個方法
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];
    });
}

實現(xiàn)方法:
#pragma mark --- implement method

/**
 根據(jù)akey 移除 對應(yīng)的 鍵值對

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

/**
 將鍵值對 添加 到 NSMutableDictionary 內(nèi)

 @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 是非原子性的,在多線程環(huán)境下可能被多次修改,但同樣 Method swizzling 又是全局性的,就會造成不可預(yù)知的錯誤。

  • 可能出現(xiàn)命名沖突的問題,這樣就不會調(diào)用到系統(tǒng)原方法,可能導(dǎo)致未知問題。

  • Method swizzling 看起來像遞歸,對新人來說不容易理解。

  • 出現(xiàn)問題 Method swizzling 不容易進(jìn)行debug,來發(fā)現(xiàn)問題

  • 隨著項目迭代和人員更換,使用Method swizzling 的項目不容易維護(hù),因為開發(fā)人員有時根本不知道在Method swizzling 里面修改了東西。

2.總結(jié)

runtime是把雙刃劍,因為所有的代碼都運行在它之上,改變它,可能會改變代碼的正常運行邏輯和所有與之交互的東西,因此會產(chǎn)生可怕的副作用。但同時它強(qiáng)大的功能也可以給應(yīng)用的框架或者代碼的編寫帶來非常大的遍歷。

因此,對于runtime唯一的建議就是,需謹(jǐn)慎使用,一旦使用,必須先了解runtime的相關(guān)原理,做好預(yù)防措施,在添加完自己的代碼之后,一定要調(diào)用系統(tǒng)原來的方法。

五: 最后

送上一張喜歡的圖片:

風(fēng)景3.jpeg

提醒:不應(yīng)該把runtime的使用看成是高大上的東西,并以使用這個為榮,實際開發(fā)中runtime能少用應(yīng)該少用,正常的系統(tǒng)方法才是正道!

**這是gitHub鏈接地址:FJ_Safe,大家有興趣可以看一下,如果覺得不錯,麻煩給個喜歡或star,若發(fā)現(xiàn)問題請及時反饋,謝謝! **

-- 2017,新的開始!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容