- 数组初始化带空值
- 数组插入空值
- 数组越界(存、取、删除、交换等)
- 字典初始化带空值
- 字典设置value为空值
- 子线程UI操作
- 字符串范围越界
- 消息未找到对应方法
- KVC 未找到对应的key 或插入了空值、字典操作不匹配
- KVO 未实现监听、add后未remove、多次remove
通过hook关键的带元素下标的方法,判断是否越界。
hook带参数的初始化方法,判断是否存在空值。
同时因为NSArray是类蔟模式,类蔟是抽象工厂模式在iOS下的一种实现方式。包括NSArray、NSDictionary、NSNumber、NSString等。用户使用抽象层的简单接口来调用,但实际上内部已经实现了很多具体的类。
通过Xcode调试可以打印出具体实现的类。
比如NSArray的具体类有NSArrayI、NSArray0、NSArrayM、__NSPlaceholderArray
代码如下:
// 以下交换的方法都封装成类方法便于调用
+ (void)swizzleInstanceMethodWithSelector:(SEL)selector1 AndSelector:(SEL)selector2 {
Method originMethod = class_getInstanceMethod(self, selector1);
Method newMethod = class_getInstanceMethod(self, selector2);
method_exchangeImplementations(originMethod, newMethod);
}
+ (void)swizzleClassMethodWithSelector:(SEL)selector1 AndSelector:(SEL)selector2 {
Method originMethod = class_getClassMethod(self, selector1);
Method newMethod = class_getClassMethod(self, selector2);
method_exchangeImplementations(originMethod, newMethod);
}
// hook 初始化方法
Class class = NSClassFromString(@"__NSPlaceholderArray");
[class swizzleInstanceMethodWithSelector:@selector(initWithObjects:count:) AndSelector:@selector(ch_initWithObjects:count:)];
- (instancetype)ch_initWithObjects:(const id _Nonnull [_Nullable])objects count:(NSUInteger)cnt {
id nObjects[cnt];
int i = 0;
for (; i < cnt; i++) {
if (objects[i]) {
nObjects[i] = objects[i];
}
else {
// 插入空值
nObjects[i] = NSNull.null;
}
}
return [self ch_initWithObjects:nObjects count:i];
}
// hook 不同模式下的array的取下标方法
Class class = NSClassFromString(@"__NSArrayI");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(ch_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(ch_objectAtIndexedSubscript:)];
class = NSClassFromString(@"__NSArray0");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(ch0_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(ch0_objectAtIndexedSubscript:)];
// 可变数组
class = NSClassFromString(@"__NSArrayM");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(chM_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(chM_objectAtIndexedSubscript:)];
[class swizzleInstanceMethodWithSelector:@selector(addObject:) AndSelector:@selector(ch_addObject:)];
[class swizzleInstanceMethodWithSelector:@selector(insertObject:atIndex:) AndSelector:@selector(ch_insertObject:atIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(removeObjectAtIndex:) AndSelector:@selector(ch_removeObjectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(replaceObjectAtIndex:withObject:) AndSelector:@selector(ch_replaceObjectAtIndex:withObject:)];
[class swizzleInstanceMethodWithSelector:@selector(setObject:atIndexedSubscript:) AndSelector:@selector(ch_setObject:atIndexedSubscript:)];
// 交换的实现方法
- (id)ch_objectAtIndex:(NSUInteger)idx {
if (idx < self.count) {
return [self ch_objectAtIndex:idx];
}
else {
return nil;
}
}
- (id)ch_objectAtIndexedSubscript:(NSUInteger)idx {
if (idx < self.count) {
return [self ch_objectAtIndexedSubscript:idx];
}
else {
return nil;
}
}
- (id)ch0_objectAtIndex:(NSUInteger)idx {
if (idx < self.count) {
return [self ch0_objectAtIndex:idx];
}
else {
return nil;
}
}
- (id)ch0_objectAtIndexedSubscript:(NSUInteger)idx {
if (idx < self.count) {
return [self ch0_objectAtIndexedSubscript:idx];
}
else {
return nil;
}
}
- (id)chM_objectAtIndex:(NSUInteger)idx {
if (idx < self.count) {
return [self chM_objectAtIndex:idx];
}
else {
return nil;
}
}
- (id)chM_objectAtIndexedSubscript:(NSUInteger)idx {
if (idx < self.count) {
return [self chM_objectAtIndexedSubscript:idx];
}
else {
return nil;
}
}
- (void)ch_addObject:(id)anObject {
if (anObject) {
return [self ch_addObject:anObject];
}
else {
}
}
- (void)ch_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (anObject && index <= self.count) {
return [self ch_insertObject:anObject atIndex:index];
}
else {
}
}
- (void)ch_removeObjectAtIndex:(NSUInteger)index {
if (index < self.count) {
return [self ch_removeObjectAtIndex:index];
}
else {
}
}
- (void)ch_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
if (anObject && index < self.count) {
return [self ch_replaceObjectAtIndex:index withObject:anObject];
}
else {
}
}
- (void)ch_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
if (obj && idx <= self.count) {
return [self ch_setObject:obj atIndexedSubscript:idx];
}
else {
}
}
同理,先找出字典的具体类,然后交换其初始化、设置值的方法。
Class class = NSClassFromString(@"__NSPlaceholderDictionary");
[class swizzleInstanceMethodWithSelector:@selector(initWithObjects:forKeys:count:) AndSelector:@selector(ch_initWithObjects:forKeys:count:)];
class = NSClassFromString(@"__NSDictionaryM");
[class swizzleInstanceMethodWithSelector:@selector(setObject:forKey:) AndSelector:@selector(ch_setObject:forKey:)];
[class swizzleInstanceMethodWithSelector:@selector(setObject:forKeyedSubscript:) AndSelector:@selector(ch_setObject:forKeyedSubscript:)];
- (instancetype)ch_initWithObjects:(id _Nonnull const [])objects forKeys:(id<NSCopying> _Nonnull const [])keys count:(NSUInteger)cnt {
id nObjects[cnt];
id nKeys[cnt];
int i = 0;
for (; i < cnt; i++) {
if (keys[i]) {
nKeys[i] = keys[i];
}
else {
nKeys[i] = @"";
#if CRASH_HOOK_ALERT
#endif
}
if (objects[i]) {
nObjects[i] = objects[i];
}
else {
nObjects[i] = NSNull.null;
#if CRASH_HOOK_ALERT
#endif
}
}
return [self ch_initWithObjects:nObjects forKeys:nKeys count:i];
}
- (void)ch_setObject:(id)anObject forKey:(id)aKey {
if (anObject && aKey) {
[self ch_setObject:anObject forKey:aKey];
}
else {
}
}
- (void)ch_setObject:(nullable id)obj forKeyedSubscript:(id)key {
if (key) {
[self ch_setObject:obj forKeyedSubscript:key];
}
else {
}
}
主要是针对几个显示布局的代码在主线程中被执行。
[self swizzleInstanceMethodWithSelector:@selector(setNeedsLayout) AndSelector:@selector(ch_setNeedsLayout)];
[self swizzleInstanceMethodWithSelector:@selector(setNeedsDisplay) AndSelector:@selector(ch_setNeedsDisplay)];
[self swizzleInstanceMethodWithSelector:@selector(setNeedsDisplayInRect:) AndSelector:@selector(ch_setNeedsDisplayInRect:)];
- (void)ch_setNeedsLayout {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self ch_setNeedsLayout];
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
});
}
else {
[self ch_setNeedsLayout];
}
}
- (void)ch_setNeedsDisplay {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self ch_setNeedsDisplay];
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
});
}
else {
[self ch_setNeedsDisplay];
}
}
- (void)ch_setNeedsDisplayInRect:(CGRect)rect {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self ch_setNeedsDisplayInRect:rect];
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
});
}
else {
[self ch_setNeedsDisplayInRect:rect];
}
}
主要是针对字符串的范围操作,避免越界。
Class class = NSClassFromString(@"__NSCFConstantString");
[class swizzleInstanceMethodWithSelector:@selector(substringFromIndex:) AndSelector:@selector(ch_cfConstant_substringFromIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringToIndex:) AndSelector:@selector(ch_cfConstant_substringToIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringWithRange:) AndSelector:@selector(ch_cfConstant_substringWithRange:)];
class = NSClassFromString(@"NSTaggedPointerString");
[class swizzleInstanceMethodWithSelector:@selector(substringFromIndex:) AndSelector:@selector(ch_taggedPointer_substringFromIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringToIndex:) AndSelector:@selector(ch_taggedPointer_substringToIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringWithRange:) AndSelector:@selector(ch_taggedPointer_substringWithRange:)];
- (NSString *)ch_cfConstant_substringFromIndex:(NSUInteger)from {
if (from > self.length) {
from = self.length;
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_cfConstant_substringFromIndex:from];
}
- (NSString *)ch_cfConstant_substringToIndex:(NSUInteger)to {
if (to > self.length) {
to = self.length;
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_cfConstant_substringToIndex:to];
}
- (NSString *)ch_cfConstant_substringWithRange:(NSRange)range {
BOOL isRangeModify = NO;
if (range.length > self.length) {
range.length = self.length;
isRangeModify = YES;
}
if (range.location > self.length) {
range.location = self.length;
isRangeModify = YES;
}
if (range.location + range.length > self.length) {
range.length = self.length - range.location;
isRangeModify = YES;
}
if (isRangeModify) {
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_cfConstant_substringWithRange:range];
}
- (NSString *)ch_taggedPointer_substringFromIndex:(NSUInteger)from {
if (from > self.length) {
from = self.length;
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_taggedPointer_substringFromIndex:from];
}
- (NSString *)ch_taggedPointer_substringToIndex:(NSUInteger)to {
if (to > self.length) {
to = self.length;
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_taggedPointer_substringToIndex:to];
}
- (NSString *)ch_taggedPointer_substringWithRange:(NSRange)range {
BOOL isRangeModify = NO;
if (range.length > self.length) {
range.length = self.length;
isRangeModify = YES;
}
if (range.location > self.length) {
range.location = self.length;
isRangeModify = YES;
}
if (range.location + range.length > self.length) {
range.length = self.length - range.location;
isRangeModify = YES;
}
if (isRangeModify) {
[self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
}
return [self ch_taggedPointer_substringWithRange:range];
}
针对系统的方法,我们可以通过hook的方式去避免一些崩溃,主要是找到其中具体的类以及具体的方法。
同时还是有许多场景未能覆盖到,在平时开发中也需要多多留意各种崩溃的情况。