您的位置:首页 > 移动开发 > IOS开发

ARC下循环引用的问题 ++ 整理

2015-10-14 12:12 495 查看
iOS]ARC下循环引用的问题

最初

最近在开发应用时碰到使用ASIHttpRequest后在某些机器上发不出请求的问题,项目开启了ARC,代码是这样写的:

@implement

MainController

-

(void)

fetchUrl{

ASIHTTPRequest


*request = [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

[request


setCompletionBlock:^{

    NSLog(@"completed");

}];

[request


startAsynchronous];

}

@end

后来发现原因是request这个变量在退出这个函数后就被释放了,自然发不出请求。因为用了ARC,没法手动调用[request retain]让这个变量不被释放,所以只能把这个变量变成实例变量,让Controller实例存在的过程中一直持有这个变量不释放。

@interface

MainController {

 ASIHTTPRequest


*request;

}

@end

@implement

MainController

-

(void)

fetchUrl{

request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

[request


setCompletionBlock:^{

[self


complete];

}];

[request


setFailedBlock:^{

      NSLog(@"failed");

}];

[request


startAsynchronous];

}

@end

问题一

这下发送请求没问题了,但出了另一个问题,XCode编译后提示[self complete]这一行可能会导致循环引用。因为MainController实例持有request, request持有completionBlock,completionBlock又持有MainController,导致循环引用,MainController实例在外界引用计数为0时仍无法被释放,因为自身的变量request里持有MainController实例的引用,其引用计数永远大于1。

导致这样循环引用的原因是在completionBlock里调用的self是一个strong类的引用,会使self引用计数+1,可以保证在调用过程self不会被释放,但在这里不需要这样的保证,可以声明另一个__weak变量指向self,这样在block使用这个变量就不会导致self引用计数+1,不会导致循环引用。

@implement

MainController

-

(void)

fetchUrl{

 request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

__weak


id

this

= self;

[request


setCompletionBlock:^{

[this


complete];

}];

[request


startAsynchronous];

}

@end

这样循环引用问题就解决了,不过__weak只支持iOS5.0以上,5以下的要用__unsafe_unretain代替__weak,区别是对象被释放后__weak声明的变量会指向nil,安全点,__unsafe_unretain不会,变成野指针容易导致应用crash。

问题二

如果在block只是调用下MainController的方法,上面的解决方法就够了,但我的需求是在block里要调用到很多实例变量,包括赋值:

@interface

MainController {

 ASIHTTPRequest


*request;

BOOL


isLoading;

UIView


*loadingView;

}

@end

@implement

MainController

-

(void)

fetchUrl{

request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

[request


setCompletionBlock:^{

isLoading


= NO;

loadingView.hidden


= NO;

}];

[request


startAsynchronous];

}

@end

XCode提示说isLoading = NO和loadingView.hidden = NO两行都可能导致循环引用,这下难办了,对于loadingView,是可以跟self一样再声明一个__weak引用给block用,但像isLoading这样需要赋值的没法这样做,而且使用的实例变量多的情况下每个都另外声明__weak变量也是很烦。想半天想到三个办法:

1

实例变量全部加上get set方法,通过声明的__weak变量访问,缺点是破坏了封装性,把原本私有的实例变量变成公有。

@interface

MainController {

 ASIHTTPRequest


*request;

}

@property

(nonatomic,

strong) UIView *loadingView;

@property

(nonatomic,

assign)BOOL
isLoading;

@end

@implement

MainController

@synthesize

loadingView, isLoading;

-

(void)

fetchUrl{

 request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

__weak


id

this

= self;

[request


setCompletionBlock:^{

this.isLoading


= NO;

this.loadingView.hidden


= NO;

}];

[request


startAsynchronous];

}

@end

2

在类里声明一个方法专门处理,缺点是麻烦,每一个回调都要另外声明一个实例方法,代码变丑。

@interface

MainController {

 ASIHTTPRequest


*request;

BOOL


isLoading;

UIView


*loadingView;

}

@end

@implement

MainController

-

(void)

complete:(ASIHttpRequest *)request{

isLoading


= NO;

loadingView.hidden


= NO;

}

-

(void)

fetchUrl{

 request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

__weak


id

this

= self;

__weak


ASIHttpRequest *_request = request;

[request


setCompletionBlock:^{

[this


complete:request];

}];

[request


startAsynchronous];

}

@end

3

在block结束手动释放request。在循环引用里出现的问题是MainController外部引用计数为0时它仍不能释放,但如果我们通过手动设置request=nil,导致request变量指向的对象引用计数为0被释放,它对MainController的引用也就释放了,MainController在外部引用计数为0时就可以正常释放了,解决了循环引用的问题。这个做法的缺点是XCode的警告提示还存在着。

NO_ARC 下这个情况可以使用__autoreleasing关键字放在ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:currUrl]];

@interface

MainController {

 ASIHTTPRequest


*request;

BOOL


isLoading;

UIView


*loadingView;

}

@end

@implement

MainController

-

(void)

fetchUrl{

request


= [ASIHTTPRequest requestWithURL:[NSURL

URLWithString:currUrl]];

[request


setCompletionBlock:^{

isLoading


= NO;

loadingView.hidden


= NO;

    request


= nil;

<
4000
code>}];

[request


startAsynchronous];

}

@end

ARC 整理

一,注意事项

1.不可以使用retain,retainCount,release,autorelease 用@select()这样的调用也不行.属性命名不能以new开头。

2.若重写一个类子类的dealloc,不应调用[super dealloc],当然也不用写什么release释放一些什么对象,只是处理一些你觉得必要处理的事情吧,比如中止一个还没有完成的网络请求.

3.不能使用NSAllocateObject和NSDeallocateObject

4.你不能在c结构中使用对象,更好的方式是使用Objective-c类来代替.

5.在id和void*之间不能隐士转换,必须指明相应转换的生命周期。

6.不能使用NSAutoreleasePool对象,ARC使用@autoreleasepool{}块代替。

7.不能使用内存块,NSZone已经不需要使用了,现在运行时已经忽略之。

二.基本关键字

声明变量就会默认赋值nil的.

strong// 默认的

相当于retain,在不再使用的时候被释放

weak

与assign很像,不同在于如果指向的数据被释放了,那么这个指向nil

unsafe_unretained

相当于assign,指向的数据如果被释放,这个指向原来的地址

这些可以定义局部变量__strong __weak __unsage_unretained

__autor eleasing

标明传给函数的(id*)型参数是自动释放的,(函数中(id*)型参数默认的也是这种类型)

1.__autoreleasing不能修饰全局变量,不能修饰类的属性.只能再函数块中用来定义变量,相当与__strong,一旦赋值,直到生命周期结束释放.

2.对于(id*)型函数参数,默认是__autoreleasing型的,这是由1知道只能传局部变量给函数做实参;如果用__weak修饰,那么会在结束时使的实参为nil,没有意义;__strong修饰可以用全局变量,类的属性值作为参数,其他与__autoreleasing没有区别

list1

NSString__weak
*string = [[NSString alloc]

initWithFormat:@”First

Name: %@”,

@”aaa”];

NSLog(@”string:

%@”,

string);

打印:(null)

编译器会给出警告.程序第一句申请了一个内存,赋值给string,然后weak引用的string并没有retain这个内存,这个内存赋值后在块内并没有再次使用,他的周期就存在与赋值语句内部,所以释放,然后string指向的内存被释放所以他是nil.

list2

@property(nonatomic,strong)

NSString*

stringA;

@property(nonatomic,weak)

NSString*

stringB;

(void)a

{

self.stringA

= @”string

A”;

self.stringB

= stringA;

self.stringA

= nil;

NSLog(@”%@”,stringB);

}

-(void)b{

self.stringA

= [NSString stringWithFormat:@”%@”,@”string

A”];

self.stringB

= stringA;

self.stringA

= nil;

NSLog(@”%@”,stringB);

}

-

(void)c

{

self.stringA

= [[NSString alloc]

initWithFormat:@”%@”,@”string

A”];

self.stringB

= stringA;

self.stringA

= nil;

NSLog(@”%@”,stringB);

}

这三个函数分别输出什么呢?都是”string A”.那么在函数执行完之后呢?stringB对应的值是什么?

a:string A// 常字符串存储在静态存储区,stringB指向的那个不会被释放

b:(null)// stringA指向那块自动释放的内存再块结束时释放

c:(null)// stringA就是指向分配字符串的内存,赋予nil会释放那块内存

btw:第一次试验的时候块内就已经是块外的值了.囧..

三.Toll-Free Bridging

Core Foundation 对象与Objective-c对象之间的赋值,函数调用参数相互转化时需要用到的关键字

__bridge

简单赋值,不会影响两边对象的retain count.

__bridge_transfer

赋值后释放右边的对象

__bridge_retained

赋值后也保留不释放右边的对象

举例:

复制代码

-(void)test

{

CFStringRef coreFoundationString = CFStringCreateWithCString(CFAllocatorGetDefault(),”C String”, kCFStringEncodingUTF8); // 创建 retainCount = 1

id unknownObjectType = (__bridge id)coreFoundationString; // 简单赋值,不变,retainCount = 1

CFStringRef anotherString = (__bridge_retained CFStringRef)unknownObjectType; // 保留赋值,加一,retainCount = 2

NSString objCString = (__bridge_transfer NSString )coreFoundationString; // 释放赋值,减一,retainCount =1;由于NSString*默认strong,加一,retainCount = 2

NSLog(@”String = %@”, objCString);

objCString = nil; // 不再指向原内存,原内存减一,retainCount = 1

CFRelease(anotherString); // 释放,减一,retainCount = 0

}

复制代码

四.其他

1.开关 :-fobjc-arc 和 -fno-objc-arc 编译标识来指明单个文件的方式.

整个工程转向ARC可以选中工程->菜单edit->refactor->convert to Objective-c ARC

2.除了nib的Top-Level(main nib文件里面的对象,除了File’s owner

和 Application)对象,其他的IBOutlet最好都是weak型的.

3.如果一定要实现类的自定义的retain和release,那么同时也要在类中加入

-(BOOL)supportsWeakPointers { return NO; }

4.在c型的结构中使用objective-c对象

使用void*代替id;或者使用__unsage_unretained 修饰objective-c对象

5.不能zeroing-weak引用的类有

SATSTypesetter, NSColorSpace, NSFont, NSFontManager, NSFontPanel, NSImage, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, NSTableCellView, NSTextView, NSViewController, NSWindow, and NSWindowController.

他们的对象,作为属性,使用assign代替weak;作为变量,使用__unsafe_unretained代替__weak。

ARC下也不能weak引用NSHashTable, NSMapTable, or NSPointerArray。

__block修饰的变量默认也是__strong引用的.

推荐学习:

http://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1

http://www.raywenderlich.com/5773/beginning-arc-in-ios-5-tutorial-part-2

参考:

https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html#//apple_ref/doc/uid/TP40011226

iOS5 programming cook book

异常

原因:缺少一个关键字双下划线block

解决方法:为block加上双下划线

当在block内部使用block外部定义的局部变量时,如果变量没有被__block修饰,则在block内部是readonly(只读的),

不能对他修改,如果想修改,变量前必须要有__block修饰

__block的作用告诉编译器,编译时在block内部不要把外部变量当做常量使用,还是要当做变量使用.

如果再block中访问全局变量,就不需要__block修饰.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios