iOS开发之内存管理
文章目录
- iOS开发之内存管理
- 引用计数(Reference Count)
- 内存管理的思考方式
- 对象操作与对应的Objective-C方法
- autorelease
- 权限修饰符
- __strong修饰符
- 循环引用
- __weak修饰符
- __unsafe_unretained修饰符
- __autoreleasing修饰符
- 属性修饰符
- assign修饰符
- copy修饰符
- 参考资料
- 更新中
引用计数(Reference Count)
iOS的内存管理基于引用计数这一机制,简单地说就是一个对象被其他对象持有时,引用计数+1,当不再被持有时-1,当引用计数减为零的时候对象不被任何人持有,就会被释放。
内存管理的思考方式
- 自己生成的对象自己持有
id obj = [[NSObject alloc] init];
- 不是自己生成的对象自己也能持有
id obj = [NSMutableArray array];
[obj retain];
- 不再需要自己所持有的对象时释放
id obj = [[NSObject alloc] init];
[obj release]
- 不是自己持有的对象不能释放
id obj = [NSMutableArray array];
[obj release]
程序崩溃(obj
并不持有NSMutableArray
对象)
对象操作与对应的Objective-C方法
对象操作 | Objective-C方法 | 对应的操作结果 |
生成并持有对象 |
| 生成对象并设置引用计数=1 |
持有对象 |
| 引用计数+1 |
释放对象 |
| 引用计数-1 |
释放对象 |
| 引用计数=0时调用 |
使用某个方法生成对象,其源码实现:
-(id)allocObject
{
id obj = [[NSObject alloc] init]; //生成并持有了NSObject对象
return obj;
}
这里原封不动地将obj
返回给方法的调用者,使得方法的调用者也持有该对象。
我们考虑另一种情况:
-(void)object
{
id obj = [[NSObject alloc] init]; //生成并持有了NSObject对象
[obj autorelease]; //对象存在但不再持有
return obj
}
这里使用autorelease
方法将obj
对象注册到autoreleasepool
中,在对象超出指定的生存范围的时候能够自动正确地释放(调用release
方法)
autorelease
autorelease
类似于C语言的自动变量的特性,当自动变量超出其作用域时,就会被自动释放。但是autorelease
与C语言不通的是,你可以自己定义自动变量的作用域。
autorelease
的使用方法:
① 生成并持有NSAutoreleasePool
对象
② 调用已分配的对象的autorelease
实例方法
③ 释放NSAutoreleasePool
对象
当我们在调用autorelease
方法时,其内部其实是调用了[NSAutoreleasePool addObject:self]
方法,从而将对象放入自动释放池中,当自动释放池被释放时,自动释放池里的对象也被一并释放了。
权限修饰符
__strong修饰符
__strong
修饰符是id类型和对象类型的默认修饰符
id obj = [[NSObject alloc] init]
与id __strong obj [[NSObject alloc] init]
等价
__strong
表示对对象的强引用,当变量超出其作用区域的时候,强引用会失效,此时对象不再被持有,就会被释放。
{
id __strong obj = [[NSObject alloc] init];
//此时obj对NSObject对象强引用
}
//超出作用区域,强引用失效,NSObject对象不再被持有,于是被释放
使用__strong修饰的变量可以互相赋值
id __strong obj0 = [[NSObject alloc] init]; //对象A
id __strong obj1 = [[NSObject alloc] init]; //对象B
id __strong obj2 = nil
obj0 = obj1
/* obj0被赋值,所以其对对象A的强引用失效,对象A不再被
* 持有,于是被释放
* obj1对对象B有一个强引用,现在obj1赋值给obj0,于是
* obj0也有对象B的强引用,此时对象B的强引用对象为
* obj1、obj0
*/
obj2 = obj0
//同样的此时的obj0对对象B有强引用,当obj0赋值给obj2
//之后obj2也有对对象B的强引用,此时对象B的强引用变量
//为obj0、obj1、obj2
obj1 = nil
//因为obj1被置为nil,所以obj1对对象B的强引用失效
//此时对象B的强引用对象为obj0、obj2
obj0 = nil
//因为obj0被置为nil,所以obj0对对象B的强引用失效
//此时对象B的强引用对象为obj2
obj2 = nil
//因为obj0被置为nil,所以obj0对对象B的强引用失效
//此时对象B不再被任何人持有,于是被释放
循环引用
下面我们用代码来重现循环引用,声明一个Test类
@interface Test:NSObject
{
id __strong obj_;
}
-(void)setObject:(id __strong)obj;
@end
@implemnetaion Test
-(id)init
{
self = [super init];
return self
}
-(void)setObject:(id __strong)obj
{
obj_=obj;
}
@end
Test类有一个成员变量obj_
,并且实现了obj_
的setter
方法setObject:
下面为循环引用发生的代码
{
id test0 = [[Test alloc] init]; //Test对象A
//test0对对象A有强引用
id test1 = [[Test alloc] init]; //Test对象B
//test1对对象B有强引用
[test0 setObject:test1];
//对象A的成员变量obj_对对象B有强引用
//此时对象B的强引用变量有test1、对象A的成员变量obj_
[test1 setObject:test0]
//对象B的成员变量obj_对对象A有强引用
//此时对象A的强引用变量有test0、对象B的成员变量obj_
}
/* 出了作用域
* test0对对象A的强引用失效,释放对象A,但是此时对象B的成员
* 变量obj_还持有对对象A的强引用,导致对象A无法释放
* test1对对象B的强引用失效,释放对象B,但是此时对象A的成员
* 变量obj_还持有对对象B的强引用,导致对象B无法释放
* 于是内存泄漏
*/
当只有一个对象时,也可以发生循环引用,例如
id test = [[Test alloc] init];
[test setObject:test];
此时
__weak修饰符
__weak
修饰符与__strong
修饰符恰好相反,其提供弱引用,弱引用不能持有对象实例。
id __weak obj = [[NSObject alloc] init];
在变量obj
前加上__weak
修饰符,此时编译器会报错,这是因为__weak
修饰符使得obj
变量持有NSObject
对象的弱引用(并不持有NSObject对象),赋值过后,NSObject
对象不再被任何人持有,故而release
。
{
id __strong obj0 = [[NSObject alloc] init];
//obj0 对NSObject对象请引用,并且持有该对象,引用计数+1
id __weak obj1 = obj0
//obj1弱引用obj0,对NSObject对象的引用计数没有贡献
}
//出作用域之后 obj0对NSObject对象的强引用失效
//此时没有任何变量持有NSObject对象,故而被释放
所以前文中,我们将Test
类中的obj
变量用__weak
来修饰,就可以规避循环引用现象的出现。
{
id test0 = [[Test alloc] init]; //Test对象A
//test0对对象A有强引用
id test1 = [[Test alloc] init]; //Test对象B
//test1对对象B有强引用
[test0 setObject:test1];
//对象A的成员变量obj_对对象B有弱引用
//此时对象B的强引用变量仅有test1
[test1 setObject:test0]
//对象B的成员变量obj_对对象A有弱引用
//此时对象A的强引用变量仅有test0
}
/* 出了作用域
* test0对对象A的强引用失效,释放对象A,此时对象B的成员
* 变量obj_持有对对象A的弱引用,但是对对象A的引用计数并无贡献
* 因此对象A不再被任何变量持有,遂即被释放
* test1对对象B的强引用失效,释放对象B,此时对象A的成员
* 变量obj_持有对对象B的弱引用,但是对对象B的引用计数并无贡献
* 因此对象B不再被任何变量持有,遂即被释放
*/
__weak
的另一个特点就是,在持有某个对象的弱引用时,若对象被废弃,则此弱引用自动失效切被置为nil
__unsafe_unretained修饰符
__weak
只用用于iOS5以及OSX Lion以上版本,在之前的版本中可以使用__unsafe_unretained
来修饰,两者的作用几乎一样只是__unsafe_unretained
会出现悬垂指针的现象。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A:%@",obj1);
}
NSLog(@"B:%@",obj1);
打印输出:
A:<NSObject: 0x753e180>
B:<NSObject: 0x753e180>
按道理说出了作用域之后打印,因为对象已经被释放,应该为nil
,此时为什么还能打印出NSObject对象的内存地址呢?这里就是所说的悬垂指针,理论上讲应该输出B:(null)
,这是因为此时的内存地址还未被操作系统回收,只是恰巧我们还可以访问。
__autoreleasing修饰符
在ARC无效的情况下,我们使用autorelease:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]
而在ARC有效的情况下我们这样使用autorelease:
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
属性修饰符
属性修饰符与所有权修饰符的对应关系
属性声明的属性 | 所有权修饰符 |
assign | __unsafe_unretained修饰符 |
copy | __strong修饰符(新建一块内存,对源数据进行拷贝) |
retain | __strong修饰符 |
strong | __strong修饰符 |
Unsafe_unretained | __unsafe_unretained修饰符 |
weak | __weak修饰符 |
assign修饰符
assign可以修饰基本数据类型,也可修饰对象类型,但是在修饰对象类型的时候,可能会导致野指针的现象发生。而修饰基本数据类型时不会,这是因为值类型的数据是存储在栈空间中的,而栈空间的内存调度由操作系统管理;而对象类型的数据则是放入堆中需要程序员手动或通过ARC进行管理的。
copy修饰符
copy一般用来修饰有可变子类的对象,例如
- NSString --> NSMutableString
- NSArray --> NSMutableArray
- NSDictionary --> NSMutableDictionary
下面比较一下用strong
修饰NSString
和用copy
修饰NSString
它们的区别在什么地方:
@property(nonatomic,strong) NSString *strongImmutableString;
@property(nonatomic,copy) NSString *copyImmutableString;
NSMutableString *mutableStr = [NSMutableString stringWithString:@"BUSYLIFE"];
self.immutableStrongStr = mutableStr;
self.immutableCopyStr = mutableStr;
NSLog(@"StrongStr:%@",_immutableStrongStr);
NSLog(@"CopyStr:%@",_immutableCopyStr);
[mutableStr appendString:@"1987"];
NSLog(@"mutableStr:%@",mutableStr);
NSLog(@"ChangedStrongStr:%@",_immutableStrongStr);
NSLog(@"ChangedCopyStr:%@",_immutableCopyStr);
NSLog(@"StrongStrAddress:%p",_immutableStrongStr);
NSLog(@"CopyStrAddress:%p",_immutableCopyStr);
NSLog(@"mutableStrAddress:%p",mutableStr);
*********************运行结果************************
StringStr: BUSYLIFE
CopyStr: BUSYLIFE
mutableStr: BUSYLIFE1987
changedStrongStr: BUSYLIFE1987
changedCopyStr: BUSYLIFE
StrongStrAddress: 0x608000007b9c0
CopyStrAddress: 0xa00be653e9cdcf58
mutableStrAddress: 0x608000007b9c0
通过上面的代码我们可以知道,通过strong
修饰的strongImmutableString
,当我们通过一个可变对象(NSMutableString
)去进行赋值之后,对于可变对象的更改会直接修改strongImmutableString
,并且通过打印的内存地址可以发现,修改前后的内存地址一致。而通过copy
修饰的copyImmutableString
在被可变对象赋值之后的修改,并未对原来的数据有所影响,通过打印的内存地址可以看出,此时的值是被赋值到了一块新的内存当中。
因此,使用copy的目的是为了对象有更好的封装性,不受外部影响。无论外部传入可变或不可变对象,本身持有一个不可变的副本。
参考资料
- Objective-C高级编程 iOS与OSX多线程和内存管理