Avoid common crashes on iOS by WYW QiShare team

The author recently looked at some common situations causing App Crash. This time, we will first discuss the contents to prevent common crashes when operating set types (such as NSArray, NSDictionary, etc.) (such as avoiding crossing the boundary of value from array, inserting nil value into dictionary, etc.).

  • To avoid crashes, consider using the following methods when manipulating collection objects and setting values:
      1. useSecurity methods for categorizing additions
      1. useSwap the system approach with the way we did the security

Security operation set class object: Add classification methods

When working with collection objects, we can use the classification of security values we have added.

The part about adding categorization methods starts with the integerValue of NSString and NSNumber.

  • (1) The qi_safeIntegerValue method added to the NSObject class to replace the usual integerValue method.
- (NSInteger)qi_safeIntegerValue { if ([self isKindOfClass:[NSNumber class]]) { return [((NSNumber *)self) integerValue]; } else if([self isKindOfClass:[NSString class]]) { return [((NSString *)self) integerValue]; } else { return kCustomErrorCode; }}Copy the code

Qi_safeIntegerValue can be used as follows:

    id number = @(1);
    [number qi_safeIntegerValue];
Copy the code
  • (2) For example, qi_safeArrayObjectAtIndex was added to the class of NSObject to replace NSArray*- (ObjectType)objectAtIndex:(NSUInteger)index;
- (id)qi_safeArrayObjectAtIndex:(NSUInteger)index { if (! [self isKindOfClass:[NSArray class]]) { return nil; } if (index < 0 || index >= ((NSArray *)self).count) { return nil; } return [(NSArray *)self objectAtIndex:index]; }Copy the code

Qi_safeArrayObjectAtIndex is used as follows:

    NSArray *qiArr = @[@1];
    [qiArr qi_safeArrayObjectAtIndex:0];
    [qiArr qi_safeArrayObjectAtIndex:1];
Copy the code
  • For more information, see Demo QiSafeType.

But using only categories doesn’t work for the literal syntax we use for values. For arrays, for example, if we want to use literal values like qiArr[0], the current way of sorting won’t work. At this point, we need to combine the Runtime with the system method called by swapping qiArr[0] and our own method to achieve literal safety.

Security action set class object: method interchange

Security operation set class object, method interchange section, I’ll talk about NSArray in terms of literals.

Declare arrays and values as literals:

// Insert nil when declaring an array NSString *nilValue = nil; NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"]; NSString *nilValue = nil; NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"]; NSLog (@ qiArr: "% @", qiArr); / / from an array of values Deliberately provocative value NSLog (@ "qiArr [0] : % @ qiArr [1] : % @ qiArr [2] : % @", qiArr [0], qiArr [1], qiArr [2]). QiArr (qishare0, qiShare2) qiArr[0] : qishare0 qiArr[1] : qiShare2 qiArr[2] : (null)Copy the code
  • The operation of the swap method needs to be introduced#import <objc/runtime.h>, mainly using the following API.
// Returns a pointer to the data structure describing a given class method for a given class. // Method class_getClassMethod(Class _Nullable) returns a pointer to the data structure of the specified CLS and methods of the specified sel CLS, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); Return a specified instance method for a given class. // Return a specified instance method for a given class. // Return a specified instance method for a given class Method class_getInstanceMethod(Class _Nullable CLS, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); // Implementations of Method m1 and Method m2 void method_exchangeImplementations(Method _Nonnull M1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);Copy the code
  • The exchange method requires the method to be exchangedSEL, and the classes to swapClass

QiArr NSArray *qiArr = @[@”qishare0″, nilValue, @”qiShare2″]; For example, analyze the required class and SEL to be exchanged

  • (1) The method to be exchanged

In terms of the method to be swapped, declare the qiArr. The first step is to determine which NSArray method to call. + (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger) CNT; ; This can be verified by writing an array subclass of your own, or it can be verified by calling a method after swapping methods. For subclasses of NSArray, see QiSubArray in QiSafeType.

About inheriting NSArray Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.

+ (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt; Is a class method, so the method interchange part, the author also wrote a security declaration array class method. + (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt

+ (instanceType)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger) CNT:

+ (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger)cnt { id instance = nil; id safeObjs[cnt]; NSUInteger j = 0; for (NSUInteger i = 0; i < cnt; i ++) { if (! objects[i]) { continue; } safeObjs[j++] = objects[i]; } instance = [self qisafeArrayWithObjects:safeObjs count:j]; return instance; }Copy the code

Idea: When declaring a literal array, we iterate through the specified array, filter out the empty objects, and store the rest in another array. Then call the class method that you add to declare the array (because the method we add and the method of the system have a method exchange, so the essence here is the method that calls the system to declare the array.)

  • (2) Classes to be swapped

When declaring qiArr, the methods to be swapped are class methods, so the class to be swapped is [NSArray class].

In terms of the method to be swapped. Take qiArr[0] as an example. The method to determine the array to call. – (ObjectType)objectAtIndex:(NSUInteger)index; This can be verified by writing an array subclass of your own, or it can be verified by calling a method after swapping methods.

Once these questions have been clarified, We can then add + (instanceType)arrayWithObjects (const ObjectType _Nonnull) to the + (void)load method of the NSArray classification based on the specified class [NSArray class] [_Nonnull])objects count:(NSUInteger)cnt; And + (instancetype)qisafeArrayWithObjects:(const id _Nonnull [_Nonnull])objects count:(NSUInteger) CNT.

The key codes are as follows:

    Method originMethod = class_getClassMethod([NSArray class], @selector(arrayWithObjects:count:));
    Method alterMethod = class_getClassMethod([NSArray class], @selector(qisafeArrayWithObjects:count:));
    method_exchangeImplementations(originMethod, alterMethod);
Copy the code

In the previous section, I explained that NSArray swaps by declaring arrays as literals, and I will continue to talk about the value of NSArray literals. NSArray works in abstract factory design mode. Abstract Factory pattern is an implementation of class clusters in iOS. Many commonly used classes, such as NSString, NSArray, NSDictionary, and NSNumber, operate in abstract factory mode.

The class of the created instance of NSArray* (qiArr) depends on how the qiArr is created and how many objects it contains.

NSString *nilValue = nil; NSArray *qiArr = @[@"qishare0", nilValue, @"qiShare2"]; NSLog (@ qiArr: "% @", qiArr); NSLog (@ "qiArr [0] : % @ qiArr [1] : % @ qiArr [2] : % @", qiArr [0], qiArr [1], qiArr [2]). id tempValue = nil; NSArray *qiArr0 = @[]; tempValue = qiArr0[0]; tempValue = qiArr0[1]; NSArray *qiArr1 = @[@1]; tempValue = qiArr1[0]; tempValue = qiArr1[1]; NSArray *qiArr2 = @[@1, @2]; tempValue = qiArr2[1]; tempValue = qiArr2[2]; NSLog(@"qiArr0 class: %@", NSStringFromClass([qiArr0 class])); NSLog(@"qiArr1 class: %@", NSStringFromClass([qiArr1 class])); NSLog(@qiarr2 class: %@), NSStringFromClass([qiArr2 class])); NSArray *qiArr3 = [NSArray arrayWithObjects:@"1", @"2", @"3", nil]; NSLog(@"qiArr3 class: %@", NSStringFromClass([qiArr3 class])); QiArr0 class: __NSArray0 qiArr1 class: __NSSingleObjectArrayI qiArr2 class: __NSArrayI qiArr3 class: __NSArrayICopy the code

With that in mind, we can add a method exchange whenever an array object is intentionally accessed through literal syntax, following a crash cue.

The key codes are as follows:

// __NSArray0 Method originArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(objectAtIndex:)); Method alterArr0ObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSArray0"), @selector(qiSafeArr0ObjectAtIndex:)); method_exchangeImplementations(originArr0ObjectAtIndexMethod, alterArr0ObjectAtIndexMethod); // __NSSingleObjectArrayI Method originSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(objectAtIndex:)); Method alterSingleObjArrIObjectAtIndexMethod = class_getInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(qiSafeSingleObjArrIObjectAtIndex:)); method_exchangeImplementations(originSingleObjArrIObjectAtIndexMethod, alterSingleObjArrIObjectAtIndexMethod); / / note that __NSArrayI call literal syntax called when the system of methods for ` - (ObjectType) objectAtIndexedSubscript: (NSUInteger) independence idx API_AVAILABLE (macos (10.8), Ios (6.0), watchos (2.0), tvos (9.0)); 'Unlike the above type of NSArray to be swapped methods, this point can be deliberately tested for crashes to see the crash prompt drawn. // __NSArrayI Method originArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndexedSubscript:)); Method alterArrIObjAtIndexedSubMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(qiSafeArrIObjAtIndexedSubscript:)); method_exchangeImplementations(originArrIObjAtIndexedSubMethod, alterArrIObjAtIndexedSubMethod);Copy the code

Demo

  • For more information, see Demo QiSafeType.

Refer to study website

  • SafeKit
  • AvoidCrash
  • When NSDictionary meets nil
  • How to inherit a class cluster (for example, NSString)

Recommended articles:

IOS message forwarding iOS custom drag-and-drop control: QiDragView iOS custom card control: QiCardView iOS Wireshark Capture iOS Charles capture TCP IP address and UDP QiCardView Weekly