分析ios常见crash

一.常见crash

1.方法调用错误

1
-[<object> <selector>]: unrecognized selector sent to instance 0x1a12be490

2.僵尸引用,野指针

1
EXC_BAD_ACCESS

二.runtime

消息调用错误导致unrecognized selector sent to instancecrash,大多数原因是开发人员在编码过程中混淆对象类型,theReceiver无法识别theSelector,消息传递失败,且无法转发。

消息机制

  • objc_msgSend
    oc在运行时,编译器会将消息转换为对消息函数 id objc_msgSend(id theReceiver, SEL theSelector, ...) 的调用,根据theReceiver类型,找到SEL对应的方法实现IMP,然后将隐藏参数self,cmd以及指定的参数传递给方法实现 IMP,最后,将方法实现的返回值作为该函数的返回值返回,并且将方法缓存,以便下次调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// class & object
typedef struct objc_class {
    Class isa;
    Class super_class                                       
    ...                 
    struct objc_cache *cache      // 方法缓存                          
    ...                   
} *Class
typedef struct objc_object {
    Class isa;
} *id;
// SEL
typedef struct objc_selector *SEL;    
// IMP
typedef id (*IMP)(id, SEL, ...)
// Method
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};
  • 动态方法决议

如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,进行动态方法决议,实现以下NSObject中方法,在其中为指定的 selector 􏰀供实现即可(调用运行时函数class_addMethod来添加,其中Type Encoding),并返回YES表示不需要进行消息转发。

1
2
3
4
5
//NSObject:
+ (BOOL)resolveClassMethod:(SEL)name;
+ (BOOL)resolveInstanceMethod:(SEL)name;
// objc/runtime.h(objc-runtime-old.m)
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
  • 消息转发

在动态方法决议实现并返回NO,则进行消息转发。新建一个消息接收类,实现forwardingTargetForSelector方法,把消息转给其他接收者来注册并返回该对象。

1
-(id) forwardingTargetForSelector:(SEL)selector;

forwardingTargetForSelector返回nil,若不想程序crash,需要生成一个methodSignature变量来组装,这个变量包含了方法的参数类型、参数个数以及消息接收者等信息。接着把这个变量组装成一个NSInvocation对象进行最后一次的消息转发,调用接收者的forwardInvocation:来进行最后的挽救机会

1
2
- (void) forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
  • 抛出异常
    若对消息转发不对处理则抛出异常。
1
- (void)doesNotRecognizeSelector:(SEL)aSelector

三.内存管理
对象释放后未置为nil,调用方法抛出异常EXC_BAD_ACCESS
1.属性关键字

  • strong: 引用计数加1
  • copy:在堆上分配空间,copy对象,用于集合对象,防止子类可变对象篡改值
  • weak:对象销毁后置为nil
  • assign:对象销毁后不置为nil,用于基本数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface Test()
@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, copy) NSString *copyedStr;
@end
@implement Test {
- (void)test {
NSMutableString *tempStr = [[NSMutableString alloc] initWithString:@"abc"];
self.strongStr = tempStr;
self.copyedStr = tempStr;
NSLog("self.strongStr: %@, self.copyedStr: %@", self.strongStr, self.copyedStr); // abc abc
[tempStr appendString:"de"];
NSLog("self.strongStr: %@, self.copyedStr: %@", self.strongStr, self.copyedStr); // abc abcde
}
}

2.对象置空后发送消息返回nil,不会报错

3.copy与mutableCopy

  • 非集合类对象
非集合类对象 复制 结果
immutable copy 浅copy,immutable
immutable mutableCopy 深copy,mutable
mutable copy 深copy,immutable
mutable mutableCopy 深copy,mutable
  • 集合类对象

集合类对象那个的单层拷贝与非集合类对象类似。
集合对象的深层拷贝方法:
1)集合的深复制有两种方法。可以用 initWithArray:copyItems: 将第二个参数设置为YES

1
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];

2) 归档解档

1
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];