内存管理(一)
- 内存泄漏:该释放的对象, 没有被释放(已经不再使用的对象, 没有被释放)
- 无效内存引用:内存已经被释放了,我们还强行调用。会报
EXC_BAD_ACCESS
错误。基本原则
- 如果是
init
、new
、copy
这些方法产生出来的对象,用完就该调用release
.- 如果是其他一般方法产生出来的对象,就会回调
auto-release
对象、或是singleton
对象(稍晚会解释什么是singleton
),就不需要另外调用release
.而调用retain与release的时机包括:
- 如果是在一般代码中用了某个对象,用完就要
release
或是auto-release
- 如果是要将某个
Objective-C
对象,变成是另外一个对象的成员变量,就要将对象retain
起来。但是delegate
对象不该retain
。- 在一个对象被释放的时候,要同时释放自己的成员变量,也就是要在实现
delloc
的时候,释放自己的成员变量。
- 要将某个对象设为另外一个对象的成员变量,需要写一组
getter/setter
。
Getter/Setter
与Property
语法
- 基本数据类型
@interface MyClass:NSObject
{
int number;
}
- (int)number;
- (void)setNumber:(int)inNumber;
@end
> * 实现部分 > ```swift - (int)number
{
return number;
}
- (void)setNumber:(int)inNumber
{
number = inNumber;
}
>* 如果是 OC 对象,我们则是要将原本成员变量已经指向的内存释放,然后将传入的对象`retain`起来。该写法并不安全 >```swift - (id)myVar {
return myVar;
}
- (void)setMyVar:(id)inMyVar
{
[myVar release];
myVar = [inMyVar retain];
}
* 假如今天我们在开发中用到很多个线程,而在不同的线程中同时会用到`myVar`,在某某个线程中调用了`[myVar release]`之后,到`myVar`指定到`inMyVar`的位置之间,假如另外一个线程刚好用到了`myVar`,这时候`myVar`刚好指到了一个已経被释放的内存,这就造成了
EXC_BAD_ACCESS
错误.
- 更安全的写法:加锁,让程序在调用
setMyVar:
的时候,不让其他线程调用myVar
;另外一种简单的方法如下:
- (void)setMyVar:(id)inMyVar
{
id tmp = myVar;
myVar = [inMyVar retain];
[tmp release];
}* 上面的例子,用`property`语法可以写成: ```swift @interface MyClass:NSObject
{
id myVar;
int number;
}
@property (retain, nonatomic) id myVar;
@property (assign, nonatomic) int number;
@end
@implementation MyClass
- (void)dealloc
{
[myVar release];
[super dealloc];
}
@end
**`myVar = nil`与`self.myVar = nil`的区别?** * 前者只是单纯的将`myVar`的指针指向`nil`,但是并没有释放原本所指向的内存位置,所以会造成内存泄漏,但后者却等同于调用`[self setMyVar:nil]`,会先释放`myVar`原本指向的位置,然后将`myVar`设成`nil`。
内存管理(二)
- ARC 是通过静态分析,在编译时决定应该要在代码的哪个地方加入
retain
、release
。循环retain
- 错误情况:
- 把代理设置为
strong
- 某对象的某
property
是一个block
,但是在这个block
里面把对象自己给retain
了一份。
- 使用
timer
的时候,到了dealloc
的时候才停止timer
。- 假如我们在有一控制器,我们希望这个控制器可以定时更新,
那么,我可能会使用+scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
方法建立timer
对象,指定定时执行某个selector
.要特别注意,在建立这个timer
的时候,我指定给timer
的target
,也曾被timer
retain
一份,因此,我们想要在控制器在dealloc
的时候,才停止timer
就曾有问题:因为控制器已经被timer
retain
起来了,所以只要timer
还在执行,控制器就不
可能走到dealloc
的地方。对象桥接
- 什么是对象桥接:
Foundation
库里面的每一个对象,都有对应的 C 实现,这 C 的实现叫作Core Foundation
,当我们在使用Core Foundation
里面的 C 形态时,像CFString
、CFArray
等,我们可以让这些形态变成可以接受 ARC 的管理.这种让 C 形态也可以被当做 OC 对象,接受 ARC 管理的方式,就叫对象桥接。
- 有三个关键字:
__bridge
、__bridge_retained
、__bridge_transfer
__bridge
:会把Core Foundation
的 C 资料形态转换成 OC 对象,但是不会多做retain
与release
。__bridge_retained
:会把Core Foundation
的 C 资料形态转换成 OC 对象,并且会做一次retain
,但是之后必须由我们手动调用CFRelease
,释放内存。__bridge_transfer
:会把Core Foundation
转换成 OC 对象,并且会让 ARC 主动添加retain
与release
。
- 但不一定每个
Core Foundation
型态都有办法转换成OC
对象。详见苹果文档详细说明
内存管理(三)
- 略 (关于控制器的内存管理)
代理
Delegate属性应该要用Weak,而非strong
- 原因是:需要设置代理对象的这个对象,往往是其代理对象的成员变量,A 的实例是 A 对象,是 B 的成员变量,可能已经被 B
retain
了一份,如果 A 又retain
了一次 B,就会出现循环retain
的问题 -- 已经被别人retain
,又把别人retain
一次。命名规范
- 至少传入一个参数,就是代理调用者本身;往往以传入的类名开头,让我们可以辨别这是哪各类的代理方法。以代理
UITableViewDelegate
为例- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
我们曾经犯过的低级错误
- 源代码
@class MyClass;
@protocol MyClassDelegate <NSObject>
- (void)myClassWillBegin:(MyClass *)myClasss;
- (void)myClassDidBegin:(MyClass *)myClasss;
- (void)myClassWillStop:(MyClass *)myClasss;
- (void)myClassDidStop:(MyClass *)myClasss;
@end
@interface MyClass : NSObject
{
id <MyClassDelegate> delegate;
} - (void)begin;
- (void)stop;
@property (assign, nonatomic) id <MyClassDelegate> delegate;
@end
@implementation MyClass - (void)begin
{
[delegate myClassWillBegin:self];
// Do something
[delegate myClassDidBegin:self];
} - (void)stop
{
[delegate myClassWillStop:self];
// Do something
[delegate myClassDidStop:self];
}
@synthesize delegate;
@end
* 问题:在`myClassWillBegin:`里面想要做一些检查,如果在某些条件下,这件事情不该跑起来,而应该停止,所以在`myClassWillBegin:`里面调用了`stop`。但这么做,并不会让这件事情结束,因为`begin`这个方法在对代理调用完`myClassWillBegin:`之后,程序还是会继续走下去,所以还是把`begin`整个做完了。 * 优化后: ```swift @class MyClass; ####Delegate属性应该要用Weak,而非strong >* **原因是:**需要设置代理对象的这个对象,往往是其代理对象的成员变量,A 的实例是 A 对象,是 B 的成员变量,可能已经被 B `retain`了一份,如果 A 又 `retain`了一次 B,就会出现循环 `retain`的问题 -- 已经被别人`retain`,又把别人`retain`一次。 > >####命名规范 >* 至少传入一个参数,就是代理调用者本身;往往以传入的类名开头,让我们可以辨别这是哪各类的代理方法。以代理`UITableViewDelegate`为例 > * `- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath` > >####我们曾经犯过的低级错误 >>* 源代码 >> >>```swift >>@class MyClass; @protocol MyClassDelegate <NSObject> - (void)myClassWillBegin:(MyClass *)myClasss; - (void)myClassDidBegin:(MyClass *)myClasss; - (void)myClassWillStop:(MyClass *)myClasss; - (void)myClassDidStop:(MyClass *)myClasss; @end @interface MyClass : NSObject { id <MyClassDelegate> delegate; } - (void)begin; - (void)stop; @property (assign, nonatomic) id <MyClassDelegate> delegate; @end @implementation MyClass - (void)begin { [delegate myClassWillBegin:self]; // Do something [delegate myClassDidBegin:self]; } - (void)stop { [delegate myClassWillStop:self]; // Do something [delegate myClassDidStop:self]; } @synthesize delegate; @end >>``` >>* 问题:在`myClassWillBegin:`里面想要做一些检查,如果在某些条件下,这件事情不该跑起来,而应该停止,所以在`myClassWillBegin:`里面调用了`stop`。但这么做,并不会让这件事情结束,因为`begin`这个方法在对代理调用完`myClassWillBegin:`之后,程序还是会继续走下去,所以还是把`begin`整个做完了。 >> >>* 优化后: >> >>```swift >>@class MyClass; @protocol MyClassDelegate <NSObject>
- (BOOL)myClassShouldBegin:(MyClass *)myClasss;
- (void)myClassDidBegin:(MyClass *)myClasss;
- (BOOL)myClassShouldStop:(MyClass *)myClasss;
- (void)myClassDidStop:(MyClass *)myClasss;
@end
@interface MyClass : NSObject
{
id <MyClassDelegate> delegate;
} - (void)begin;
- (void)stop;
@property (assign, nonatomic) id <MyClassDelegate> delegate;
@end
@implementation MyClass - (void)begin
{
if (![delegate myClassShouldBegin:self]) {
return;
}
// Do something
[delegate myClassDidBegin:self];
} - (void)stop
{
if (![delegate myClassShouldStop:self]) {
return;
}
// Do something
[delegate myClassDidStop:self];
}
@synthesize delegate;
@end