OC内存管理
OC内存管理
内存管理是针对对象而言的,所以,基本类型还有结构体我们是不需要管理的
内存管理常见错误
1.EXC_BAD_ACCESS 访问了一块坏的内存:
- 用了一个已经释放的对象
- 已经释放的对象 叫做僵尸对象
- 使用已经释放的对象 就会崩溃
2.内存泄露:
- 有对象没有释放
[mutArra addObject:p1];
往数组里面添加对象,这个对象的引用计数也会增加1
[mutArra removeObject:p1];
从数组,字典里面移除对象也会让对象引用计数 减1
所以, 数组,字典 release 的时候只会对里面装得对象进行release操作也就是会使对象引用计数 -1
assign retain和copy之间的区别:
retain 对象使用 除了字符串
assign 基本类型 结构体 代理delegate
copy 字符串
- @autoreleasepool自动释放池
auto 自动release 自动释放 延时释放
系统会把 自动释放的对象放入到自动释放池里面
在当前的事件结束之后就调用
事件循环
程序 由待命状态 接到一个命令/事件 执行某段代码,当这段代码执行完毕
程序又进入待命状态
@interface DemoViewController : UIViewController
{
People *_p;
int _age;
NSString *_str;
}
@property(nonatomic,retain) People *p;
@property(nonatomic,assign) int age;
@property(nonatomic,copy)NSString *str;
@end
//set方法 retain描述的 property
-(void)setP:(People *)p
{
//每次都要判断传过来的值和自己原来的值一样不一样,如果一样就不用正在赋值了
if (_p != p) {
// 我们在赋值之前先把旧值 release
// 在保留一份新值
[_p release];
_p = [p retain];;
}
}
//set方法 copy描述的 property
-(void)setStr:(NSString *)str
{
// 每次都要判断传过来的值和自己原来的值一样不一样,如果一样就不用正在赋值了
if (_str != str) {
// 我们在赋值之前先把旧值 release
// 在保留一份新值
[_str release];
_str = [_str copy];;
}
}
//set方法 asssign描述的 property
-(void)setAge:(int)age
{
_age = age;
}
ARC
ARC是IOS5.0以后才支持,IOS7.0以后,强制使用ARC(ARC Automatic Refercences Counting)
-原理
依然使用引用计数器来管理内存,只是引用计数器的操作方式不同,由程序员发送消息转换为编译器帮我们发送消息,会在合适的位置自动加入retain、release、autorelease消息来进行计数管理,ARC是一种编译期语法。
MRC和ARC
MRC
1,ios中是如何管理内存的,(引用计数)
2,管理的本质:谁对引用计数增加,就应该负责将引用计数减少 (负责到底)
3,new retain copy alloc
以上都是会使对象的引用计数增加-相应的添加release进行引用计数的减少
addObject(NSArray) addSubView(UIView)
setObject(NSDictionary) //这几个会增加引用计数,但是不需要你添加release
ARC
ARC 下面,
所有的release autorelease [super dealloc] 不能使用了
指针类型:强引用,和弱引用之分
strong (ratain,copy)
weak (assign)
ARC下面所有的release autorelease和super dealloc都不能使用了
指针类型:强指针和弱引用之分
strong (retain ,copy)
weak (assign)
默认就是strong类型的指针 也可以使用__strong关键字来修饰
例: NSMutableArray __strong *mArr;
用__weak修饰的指针,如果对象被释放,指针会自动置空
弱引用还有一种指针类型,对象不会自动置空,跟assign类似
__unsafe_unretained(不安全的,不retain的)
对于局部变量默认也是strong类型的,超出作用域之后自动释放
1.对于weak类型的局部变量什么时候释放
对于局部的weak刚创建出来的对象,因为没有拥有者,只有弱指针指向,所以会直接释放,就是alloc完会直接释放
2.对于strong类型的局部变量什么时候释放
(1).变量的对象释放的时候,全局变量会自动释放
(2).直接通过nil的方式手动释放
总结
1.指针类型
strong (retain ,copy) 强引用 拥有对象(默认强引用)
weak(assign) 弱引用 只指向对象,并不拥有对象(指向的对象为空时,指针自动置nil)
unsafe_unretained (assign) 跟weak的区别是不会自动置nil
2.局部变量
__weak修饰局部变量,alloc之后,直接释放
原因:对象没有拥有者
__strong 修饰局部变量 作用域结束的时候释放
3.全局变量
全局的__weak只是一个指针的弱引用,他指向的对象释放了,他自动置nil
全局的__strong变量
两种释放方式:1.将指针置空nil
2.全局变量的对象释放后,全局变量会自动释放.
retain:始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。
copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制,引用计数每次加一。始终返回一个不可变对象。
mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。
循环引用的问题
ARC下面最常见的一种内存问题
A强引用B,同时B强引用A 最终导致循环引用,A和B都释放不掉造成内存泄露
![](https://img.haomeiwen.com/i1672235/3242c5a50c882817.png)
######## 实际的开发使用过程中那些容易引起循环引用:
1,delegate (通过 assign 修饰来避免)
2,block (通过 __weak 修饰来避免)
######## Block的应用主要有两种,一种是block内存管理,一种是对外部变量的引用
int a2; //全局变量 放在静态区
NSArray *arr3;//全局变量
@interface ViewController ()
{
int a1;//实例变量 放在静态区
NSMutableArray *arr2;//实例变量
}
@property(nonatomic,assign)int a3;//成员变量
@end
易错点:
例1:
● 1,局部变量
int number = 10;
int (^add)(int) = ^(int b)
{
NSLog(@"numbr:%d",number);
return number+b;
};
number = 5;
NSLog(@"1-result:%d",add(5));
运行结果:numbr:15
原因:block 对局部变量进行引用的时候,只是引用了局部变量的值,没有引用变量本身的指针,所以block内部的number从引用那一刻起他的值一直是10,即使下面number = 5重新复制或了,也不会根据外部局部变量的改变而改变.
那么为题来了,如何让block可以引用局部变量本身,不单单引用他的值呢 ?
例2:
__block int number2 = 10;
int (^add2)(int) = ^(int b)
{
//添加过__block之后,block内部可以修改外部局部变量的值
return number2+b;
};
number2 = 5;
NSLog(@"2-result:%d",add2(5));
运行结果:numbr:10
//block引用**局部变量**的时候,通常需要在局部变量前面添加
__block关键字,作用就是让block内部引用局部变量本身的指针,而不仅仅引用值
NSLog(@"2-result:%d",add2(5));
● 2.其他的变量
除了局部需要添加__block,,其他类型的变量,被block内部引用的时候,是引用的变量本身
block造成循环引用的情况,当前block本身是个成员变量,block内部又使用了实例变量,实用实例变量会对sel(block内部的self指的还是block内部本身)的引用计数加一
对实例变量的弱引用
使用__block 修饰一个临时变量,来接收 实例变量
为了避免循环引用,引起的内存泄露问题
######## 深/浅copy 的区别(copy和retain的区别)
问题:为什么总是说NSString类型的属性需要使用copy来修饰
为了防止外部使用NSMutableString的值进行赋值
如果用retain,会随着外部的NSMutableString的值的改变而改变
如果用copy,会copy一份新的内存地址,来保证属性的值不会改变
例子
● 1,使用NSString进行赋值
NSString *str = @"ABC";
//赋值
self.rStr = str;
self.cStr = str;
NSLog(@"%p|%p|%p",str,_rStr,_cStr);//对于NSString,发现retain和copy内存地址一样
//修改str
str = @"123";
NSLog(@"%p|%p|%p",str,_rStr,_cStr);//修改str的值后,retain和copy内存地址依旧一样
● 2,使用NSMutableString进行赋值
NSMutableString *mStr = [[NSMutableString alloc]initWithFormat:@"ABC"];
self.rStr = mStr;
self.cStr = mStr;
NSLog(@"%p|%p|%p",mStr,_rStr,_cStr);//对于NSMutableString,retain只是对于值的引用,而copy是copy一份新的内存地址
//修改mStr
[mStr setString:@"123"];
NSLog(@"R:%@|C:%@",_rStr,_cStr);//这里retain修饰的话会随着NSMutableString的值的改变而改变,但是会copy一份新的内存地址,来保证属性的值不会改变
//深浅copy的问题
//深copy会内存地址会改变
//浅copy是指copy指针,内存地址不会改变
1,使用copy (copy过来的是不可变对象)
//NSString使用copy 浅copy (地址没有变化)
{
NSString *str = @"123";
NSString *cStr = [str copy];
NSLog(@"%p|%p",str,cStr);
}
//NSMutableString使用copy 深copy(地址变化)
{
NSMutableString *str = [[NSMutableString alloc]initWithFormat:@"123"];
NSString *mCstr = [str copy];
NSLog(@"%p|%p",str,mCstr);
}
2.使用mutableCopy (copy过来的时可变对象)
//NSString 使用 mutableCopy (深copy,内存地址改变)
{
NSString *str = @"123";
NSMutableString *cStr = [str mutableCopy];
[cStr appendString:@"aaaa"];
NSLog(@"%p|%p",str,cStr);
}
//NSMutableString 使用 mutableCopy (深copy)
{
NSMutableString *str = [[NSMutableString alloc]initWithFormat:@"aaa"];
NSMutableString *cStr = [str mutableCopy];
NSLog(@"%p|%p",str,cStr);
}
######## strong和copy的区别
当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
######## strong 和 weak 的区别
之前巧哥有骗文章讲的不错,就说来说weak和strong的,问题如下
以下引自唐乔iOS面试题(六):自己写的 view 成员,应该用 weak 还是 strong?
<http://mp.weixin.qq.com/s?__biz=MjM5NTIyNTUyMQ==&mid=2709545260&idx=1&sn=0f56404983015476f1c7a56457f0235f&chksm=828f0bf2b5f882e45466059cad18a71b15471e17e8e814854a2ee6d4a1eadd9d4f3968c4fb62&mpshare=1&scene=23&srcid=0115hucaAqmvFk9HTn9CsL7V#rd>
我们知道,从 Storyboard 往编译器拖出来的 UI 控件的属性是 weak 的,如下所示
@property (weak, nonatomic) IBOutlet UIButton *myButton;
那么,如果有一些 UI 控件我们要用代码的方式来创建,那么它应该用 weak 还是 strong 呢?为什么?
答案
简单来说,这道题并没有标准答案,但是答案背后的解释却非常有价值,能够看出一个人对于引用计数,对于 view 的生命周期的理解是否到位。
UI 控件用默认用 weak,根源还是苹果希望只有这些 UI 控件的父 View 来强引用它们,而 ViewController 只需要强引用 ViewController.view 成员,则可以间接持有所有的 UI 控件。这样有一个好处是:在以前,当系统收到 Memory Warning 时,会触发 ViewController 的 viewDidUnload 方法,这样的弱引用方式,可以让整个 view 整体都得到释放,也更方便重建时整体重新构造。
但是首先 viewDidUnload 方法在 iOS 6 开始就被废弃掉了,苹果用了更简单有效地方式来解决内存警告时的视图资源释放,具体如何做的呢?嗯,这个可以当作某一期的面试题展开介绍。总之就是,除非你特殊地操作 view 成员,ViewController.view 的生命期和 ViewController 是一样的了。
所以在这种情况下,其实 UI 控件是不是 weak 其实关系并不大。当 UI 控件是 weak 时,它的引用计数是 1,持有它的是它的 superview,当 UI 控件是 strong 时,它的引用计数是 2,持有它的有两个地方,一个是它的 superview,另一个是这个 strong 的指针。UI 控件并不会持有别的对象,所以,不管是手写代码还是 Storyboard,UI 控件是 strong 都不会有循环引用的。
那么回到我们的最初的问题,自己写的 view 成员,应该用 weak 还是 strong?我个人觉得应该用 strong,因为用 weak 并没有什么特别的优势,加上上一篇面试题文章中,我们还看到,其实 weak 变量会有额外的系统维护开销的,如果你没有使用它的特别的理由,那么用 strong 的话应该更好。
另外有读者也提到,如果你要做 Lazy 加载,那么你也只能选择用 strong。
当然,如果你非要用 weak,其实也没什么问题,只需要注意在赋值前,先把这个对象用 addSubView 加到父 view 上,否则可能刚刚创建完,它就被释放了。