iOS程序警告:EXC_BAD_ACCESS(code=1

时间:2022-12-17 22:59:45

【问题】

BirdWatching的iOS app,现在想要去多多折腾,搞懂不同property的setter修饰符:assign,copy,retain等的更深层的含义。

所以,专门去把代码改为:

?
123 //@property
(nonatomic, weak) UIImagePickerController *imgPickerController;
//@property
(nonatomic) UIImagePickerController *imgPickerController;
@property
(nonatomic, assign) UIImagePickerController *imgPickerController;

改了之后,结果就是.m文件中初始化的代码:

?
1 self.imgPickerController
= [[UIImagePickerController alloc] init];

出现了警告:

ARC Semantic Issue,Assigning retained object to unsafe property;object will be released after assignment,然后程序运行,也出错了:

iOS程序警告:EXC_BAD_ACCESS(code=1

 

【解决过程】

1.根据之前的学习,对于assign等setter的含义为:

The Objective-C Programming Language – Declared Properties

Setter Semantics

These attributes specify the semantics of a set accessor. They are mutually exclusive.

strong

Specifies that there is a strong (owning) relationship to the destination object.

weak

Specifies that there is a weak (non-owning) relationship to the destination object.

If the destination object is deallocated, the property value is automatically set to nil.

(Weak properties are not supported on OS X v10.6 and iOS 4; use assigninstead.)

copy

Specifies that a copy of the object should be used for assignment.

The previous value is sent a release message.

The copy is made by invoking the copy method. This attribute is valid only for object types, which must implement the NSCopying protocol.

assign

Specifies that the setter uses simple assignment. This attribute is the default.

You use this attribute for scalar types such as NSInteger and CGRect.

retain

Specifies that retain should be invoked on the object upon assignment.

The previous value is sent a release message.

In OS X v10.6 and later, you can use the __attribute__ keyword to specify that a Core Foundation property should be treated like an Objective-C object for memory management:

@property(retain) __attribute__((NSObject)) CFDictionaryRef myDictionary;

所以,此处代码改为:

?
1 @property
(nonatomic, assign) UIImagePickerController *imgPickerController;

后,加上之前对于assign的学习,知道了此处对于assign,首先是只适用于非对象类的数据,比如NSInteger,也就是,对于对象引用计数的话,没有任何改变。

所以,上述的初始化代码部分中的:

[[UIImagePickerController alloc] init];

得到了一个UIImagePickerController,然后赋值给了

self.imgPickerController

但是要知道,此处的self.imgPickerController由于是assign,所以没有对于上述得到的UIImagePickerController引用计数增加,即没有打算再用到UIImagePickerController,所以刚生成的UIImagePickerController,因为没有人再用刀它,就自动释放掉了。所以后续对于self.imgPickerController的操作,都是在操作一个没有分配实体对象的空的指针,所以肯定都是无效操作,肯定就会出现EXC_BAD_ACCESS错误了。

就是之前C语言中的野指针的意思了,只是有个指针变量而已,而指针所指向的物理内存,早已被释放掉了,所以你再继续操作此块物理内存,就会出现异常操作了。

 

2.对应的,去改为:

?
1234 //@property
(nonatomic, weak) UIImagePickerController *imgPickerController;
//@property
(nonatomic) UIImagePickerController *imgPickerController;
//@property
(nonatomic, assign) UIImagePickerController *imgPickerController;
@property
(nonatomic, retain) UIImagePickerController *imgPickerController;

然后运行结果就是OK的了。

 

3.相应的,也基本明白了,之前对于写成weak的话:

?
1234 @property
(nonatomic, weak) UIImagePickerController *imgPickerController;
//@property
(nonatomic) UIImagePickerController *imgPickerController;
//@property
(nonatomic, assign) UIImagePickerController *imgPickerController;
//@property
(nonatomic, retain) UIImagePickerController *imgPickerController;

然后对于此处的和imgPickerController全部相关的代码是:

?
12345678910111213141516171819 -
(
void)viewDidLoad
{    self.imgPickerController = [[UIImagePickerController alloc] init];    self.imgPickerController.delegate = self; }    //handle
the tap to image
-(void)handleImageTap:(UITapGestureRecognizer *)sender{         if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum])    {        NSArray *availableMediaTypeArr = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeSavedPhotosAlbum];                 self.imgPickerController.mediaTypes = availableMediaTypeArr;        [self presentViewController:self.imgPickerController animated:YES completion:NULL];    }}

其中,运行到handleImageTap的时候,在view画面切换的时候:

?
1 [self
presentViewController:self.imgPickerController animated:YES completion:NULL];

程序会出错。

所以,内部的逻辑,应该是:

最开始用:

?
1 self.imgPickerController
= [[UIImagePickerController alloc] init];

初始化后,就是一个weak弱引用了,意思是,如果后者,即alloc的UIImagePickerController被dealloc的话,

那么前者self.imgPickerController就自动设置为nil了。

但是由于当前程序,显示控件中,一直使用到了UIImagePickerController,所以一直也没什么问题。

但是当调用presentViewController画面切换的时候,就自动去dealloc释放了,那个引用为0的,之前alloc的UIImagePickerController,所以,导致此时self.imgPickerController也就自动变为nil了。

所以程序会出错了。

4.对应的,当程序再改为:

?
1234 //@property
(nonatomic, weak) UIImagePickerController *imgPickerController;
@property
(nonatomic) UIImagePickerController *imgPickerController;
//@property
(nonatomic, assign) UIImagePickerController *imgPickerController;
//@property
(nonatomic, retain) UIImagePickerController *imgPickerController;

后,此时默认的是strong,即owning的效果了,所以引用计数为1了,所以即使画面切换,由于self.imgPickerController对于UIImagePickerController的引用计数还是1,没有变为0,所以,后续的self.imgPickerController指针指向的,是真正存在的UIImagePickerController对象,所以程序可以正常执行的。

即默认的strong和retain的效果是一致的。

5.对应的,后来也看到:

iOS 5中的strong和weak关键字

中说,weak相当于assign,strong相当于retain,但是看着还是很晕。

6.最后看到这里:

Weak and strong property setter attributes in Objective-C

解释的很清楚:

对于单个文件,根据设置,可以开启或关闭ARC。

如果用了ARC,则不能使用retain,release,autorelease等等修饰符,而只能:

针对属性property,使用weak,strong;

针对变量variable,使用__weak,__strong;

strong等价于retain;

weak等价于assign;

 

只有一种情况下需要用到weak:

当你想要避免循环引用的时候,才会考虑用weak;

因为如果都用strong的话,有可能出现,父类retain子类,而子类retain父类,即循环引用了,导致两者始终都无法释放。

此时就可以用weak避免此情况。

 

另外还有个toll free bridging的部分,不多解释,有空看官网解释:

Core Foundation Design Concepts – Toll-Free Bridged Types

 

而关于此部分的内容,相关的官网解释,在这里:

Transitioning to ARC Release Notes

有空需要好好看看。

 

【总结】

在对于变量/属性,设置strong还是weak,是assign还是retain,还是copy的时候,需要搞清楚意思,才能设置,不能随便设置,否则很容易导致程序死掉。

对于这部分的内容,目前还没有透彻理解,等有空的话,好好研究一下再总结出来。