Skip to content

Instantly share code, notes, and snippets.

@tedzhou
Last active January 29, 2017 07:06
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tedzhou/8899519 to your computer and use it in GitHub Desktop.
Save tedzhou/8899519 to your computer and use it in GitHub Desktop.
我要学OC:objective-c的内存管理.md

卤煮刚被web大潮淘汰到ios,当听说写oc要手动释放内存的时候如丧考妣,不过幸运的是ARC来了,妈妈再也不用担心我内存泄漏了。不过根据卤煮多年开发经验,和内存相关的不可能没有坑,开心之余还是得了解下oc是怎么搞内存的。

[TOC]

MRR和retainCount

OC对象都有一个属性 retainCount 表明这个对象还有几个引用, 当retainCount为0, 说明这个对象没人用了,runtime就会回收掉。而所谓的内存泄漏就是明明没人用了,retainCount却不是0,runtime没有回收掉这个对象。

retain and release

当对象被带下面前缀的方法创建时,retainCount自动被设成 1:alloc, new, copymutableCopy.

对象可以通过调用ratain方法来增加retainCount, release来减少retainCount. 正如下面的代码,我们需要手动维护好对象的retainCount,不然runtime把我们需要的对象回收掉。

    @interface A
    - (NSNumber*)getCount;
    - (void)setCount:(NSNumber *)newCount;
    @end
    @implement A 
    {
        NSNumber *_count;
    }
    - (NSNumber*)getCount{return _count;}
    - (void)setCount:(NSNumber *)newCount {
        [newCount retain]; // 被引用了
        [_count release]; // 旧的对象不用了
        _count = newCount;
    }
    @end

而坑的是retainCount不总是等于实际有多少对象引用着它。就如上面的代码,class A写的好好的,如果遇到下面的代码,还是会有问题。

    A *a = [A new];
    NSNumber *n = [[NSNumber alloc] initWithInt:1000]; // n的retainCount是1
    [a setCount:n];// n的retainCount是2,setCount里面retain了一次
    [n release]; // 用完了n的retainCount是1
    
    [n release]; // 我手贱,多执行了一次release,n的retainCount是0, n被释放了。
    
    [a count]; // crashed~

当代码层次结构多起来的时候, 要找出来哪里多release了一次, 多retain了一次,绝壁是坑。

以上手动写retain-release的内存管理方式叫MRR。

ARC

ARC是没有改变retainCount为0回收对象的原则。ARC做的事情是编译的时候,在需要retain和release的地方,自动加上retain和release,不需要手动去加这样的代码了。

为了能让编译器准确地加入retain和release等代码,ARC引入了几种对象的分类。 还是上面的代码,考虑下面这行语句:

    [a setCount:n];

编译器需要像MRR的时候在setCount里面加入[newCount retain];这样的代码么?要看情况:

  1. 如果我的意图是只要a没被销毁,n就不能被销毁,那么是需要加入retain的。
  2. 如果a对n是否被销毁无所谓,那么就不用了。

所以ARC下引入了几种标识符让程序员来区分上面的情况,如上面的#1为__strong, 而#2的情况就有点复杂,可以是__weak或者__unsafe_unretained.

strong and weak

默认情况下对象都是__strong的。 对于__strong,__weak, __unsafe_unretained几种类型在read, assignment操作下ARC是怎么加retain/release的请戳这里

还是这段代码

    [a setCount:n];

上面已经说了,如果我的意图是只要a没被销毁,n就不能被销毁, 那么把_count设成__strong就好了,那如果a对n是否被销毁无所谓, 就要讨论下weak,unsafe_unretained的区别了.

###__weak和__unsafe_unretained

oc是完全兼容c的,看看下面的c代码

    char *p = (char *)malloc(10 * sizeof(char));
	p[0] = '1';
	free(p);
	
	// 后面有好多分配内存的代码啊
	// ...
	// ...
	char *q = (char *)malloc(10 * sizeof(char));
	q[0] = '2';
	
	// 这里使用被free掉的空间
	printf("%c",p[0]);

这段代码p先指向了一块空间,然后free掉这块空间,但p的值还是那里的地址,那么这里再使用这个地址是不安全的,因为你不知道这个空间有没有被别人写过了。printf出来的值是不定的。 而oc的unsafe_unretained指的就是这种情况。

而weak

@interface A : NSObject
-(id) weakObject ;
-(void)setWeakObject:(id)weakObject;
-(id) unsafeUnretainedObject ;
-(void)setUnsafeUnretainedObject:(id)unsafeUnretainedObject;
@end

@implementation A
{
	__weak id _weakObject;
	__unsafe_unretained id _unsafeUnretainedObject;
}
- (id)weakObject {
	return _weakObject;
}
- (void)setWeakObject:(id)weakObject {
	_weakObject = weakObject;
}

- (id)unsafeUnretainedObject {
	return _unsafeUnretainedObject;
}
- (void)setUnsafeUnretainedObject:(id)unsafeUnretainedObject {
	_unsafeUnretainedObject = unsafeUnretainedObject;
}

@end

@interface B : NSObject
- (NSString *)sayHi;
@end

@implementation B
- (NSString *)sayHi {
	return @"hi";
}

- (void)dealloc {
	NSLog(@"dealloc");
}

@end


int main(int argc, const char *argv[]) {

	A *a = [[A alloc] init];
	{
		B *weakObject = [[B alloc] init];
		B *unsafeUnretainedObject = [[B alloc] init];
		a.weakObject = weakObject;
		a.unsafeUnretainedObject = unsafeUnretainedObject;
		// 块作用域
		// 这里weakObject和unsafeUnretainedObject都会被释放
	}
	NSLog(@"%@", [a.weakObject sayHi]);
	if(a.unsafeUnretainedObject){ // crashed!
		NSLog(@"%@", [a.unsafeUnretainedObject sayHi]);
	}
	return 0;
}

当load一个weak的pointer, runtime会判断这个空间是否被释放,释放了就返回nil。 而unsafe_unretained如果引用了被释放的空间, load的时候就crashed了,真是unsafe咯。

所以,在低版本的项目里只能使用unsafe_unretained不能使用weak的时候,就非常蛋疼。因为你根本不知道你现在要用的这个指针是不是指向一个合法的空间,无解。

这又是一个坑。

weak和unsafe_unretained是用来避免循环引用的。

作为一个以前写js的,实在无法理解为毛循环引用的对象runtime干嘛不直接释放掉。 先看一段我们经常写的js闭包。

var retain = (function a(){
    var w = "";
    for(var i=0; i<100000; i++){w+="1"}
    return function b(){
        return w;
    };
})();

js是词法作用域,所以function a和w之间是循环引用,function a和b之间也是循环引用,但因为retain作为个全局变量引用这function b, 所以function a不被销毁,function a不被销毁,那么w也不被销毁。

当只要执行retain = null;之后,function b就只和function a循环引用了,那么js的vm是会把b和a和w都释放掉。

但oc并不会释放循环引用的。。

{
    // strong reference
    Doc * doc = [[Doc alloc]init]; // doc retainCount 1
    Page * page = [[Page alloc]init]; // page retainCount 1
    
    doc.page = page; // page retainCount 2
    page.doc = doc; // doc retainCount 2
    
    // arc 会自动做[doc release]; // doc retainCount 1
    // arc 会自动做[page release]; //page retainCount 1
}

最后doc和page的retainCount都是1, 这样runtime是不会释放掉这两个对象的, 而这两个对象已经不用了,理应释放才对。

官方的解法是,doc.page和page.doc之间至少要一个使用weak或者unsafe_unretained,我们假设page.doc是weak的。那么上面的代码执行结果就不一样了

{
    // strong reference
    Doc * doc = [[Doc alloc]init]; // doc retainCount 1
    Page * page = [[Page alloc]init]; // page retainCount 1
    
    doc.page = page; // page retainCount 2
    page.doc = doc; // doc retainCount 1, weak不执行retain
    
    // arc 会自动做[doc release]; 导致 doc retainCount 0, doc会 dealloc, dealloc的时候还要执行一次 [_page release], 所以page retainCount 也是1.
    // arc 会自动做[page release]; //page retainCount 0
}

所以我们要在正确时机使用weak来避免循环引用,防止内存泄漏。

因为runtime只认retainCount, 而不是在被每次引用的时候都把引用方记录下来,所以runtime根本不知道是否循环引用了。这个,坑嘛。

结论

结论其实很简单,ARC给了我们写OC有了写js的感觉(就不用管内存这点啦), 但要注意unsafe_unretained也是个巨坑,和不要循环引用。

assign 其实就是weak, retain就是strong

  1. assign implies __unsafe_unretained ownership.
  2. copy implies __strong ownership, as well as the usual behavior of copy semantics on the setter.
  3. retain implies __strong ownership.
  4. strong implies __strong ownership.
  5. unsafe_unretained implies __unsafe_unretained ownership.
  6. weak implies __weak ownership.

参考这里

补充说下autorelease

@autoreleasepool{ 
    NSObject * o = [[NSObject alloc] init]; 
}

o会在离开当前作用域后收到一个release。

以前是一个使用autoreleasepool的例子,当你在一个循环内创建很多临时对象,你可以把对象放入一个autoreleasepool里面,那么在这个pool的最后这些对象就会被释放,不用等到整个循环执行完。

    for(NSUrl *url in urls){
    
        @autoreleasepool{
            NSError *error;
            NSString * filecontents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
            
            // Processing ...
        
        }
    
    }

Written with StackEdit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment