ARC(Automatic Reference Counting)是一种由Objective-C编译器提供的自动引用计数机制。__block和__weak是两个在ARC中用于修饰变量的关键字。
ARC(Automatic Reference Counting)是一种由Objective-C编译器提供的自动引用计数机制。__block和__weak是两个在ARC中用于修饰变量的关键字。
假设我试图从代码块中访问self
:
[someObject successBlock:^(NSArray *result) { [self someSuccessMethod]; } failure:^(NSString *errorMessage, int status) { [self someFailureMethod]; }];
我理解这会创建一个保留循环,而且someObject
和self
永远不会被释放。
让我困惑的是,带有/不带有__block
关键字会发生什么。我可以通过创建对self
的__weak
引用来修复保留循环:
__weak MyClass* me = self; [someObject successBlock:^(NSArray *result) { [me someSuccessMethod]; } failure:^(NSString *errorMessage, int status) { [me someFailureMethod]; }];
我在这里不需要使用__block
,因为我不打算从块内部修改me
。据我所了解,如果我不使用__block
,那么在块内部引用的是对象的副本。我的问题是:如果在块内部引用的只是对象的副本,为什么原始代码块会创建保留循环?我猜想对self
的引用只是一个副本,因为我从未使用过__block
关键字。我是否对此理解错误?
retain cycle是指两个对象相互强引用对方,导致内存无法释放的情况。在Objective-C中,retain cycle是一个问题,因为即使这些对象没有其他地方引用,ARC仍然认为它们一直在使用中。
有两种常见的情况会导致retain cycle。第一种情况是对象a强引用对象b,而对象b也强引用对象a。如果对象a和对象b一开始就产生了retain cycle,那么对象a和对象b将无法被释放,会导致内存泄漏。第二种情况是对象a强引用并创建了对象b,而对象b也强引用对象a。在对象图中,许多较小的对象可能需要访问它们的父对象,这也会导致retain cycle的发生。
解决这些问题的常见方法是确保包含的对象对其容器对象只有弱引用,并且确保兄弟对象之间不相互强引用。另一种解决方法是在对象a中定义一个自定义的cleanup方法,将其对对象b的引用设置为nil。这样,当调用cleanup方法时,对象b会被释放(如果对象b在其他地方没有被强引用)。然而,这种方法比较麻烦,因为无法在对象a的dealloc中调用cleanup方法(如果存在retain cycle,dealloc方法将不会被调用),而且需要记住在适当的时机调用cleanup方法。
需要注意的是,retain cycle也可以是传递性的,例如对象a强引用对象b,对象b强引用对象c,对象c强引用对象a。
与此同时,对于块(block)的内存管理也是相当棘手的问题。
在第一个例子中,如果self对象对someObject对象存在强引用,那么可能会创建一个临时的retain cycle。这个临时的retain cycle在块执行完成并被释放时会消失。在执行过程中,self对象会对someObject对象进行引用,someObject对象对块进行引用,而块又对self对象进行引用。但这只是临时的,因为块没有永久存储在任何地方(除非[someObject successBlock:failure:]的实现这样做,但对于完成块来说,这种情况并不常见)。
一般来说,只有当某个对象存储块而不是直接执行块时,块内的retain cycle才是一个问题。这时很容易看出self对象对块的强引用以及块对self对象的强引用。需要注意的是,在块内从ivar访问任何对象会自动在块中生成对self对象的强引用。
解决块中的retain cycle问题的方法是使用__weak关键字来访问方法和ivar。通过将块对self对象的引用设置为弱引用,当self对象不再被强引用时,self对象可以被释放。一般来说,总是在所有块中使用weakSelf是一个好的做法,以防万一。但奇怪的是,苹果为什么没有将这个设为默认行为。实际上,这样做不会对块的代码产生任何有害影响,即使实际上是不需要的。
对于指向对象的指针,__block关键字很少用于其上,因为Objective-C不会强制对象的不可变性。如果你有一个指向对象的指针,你可以调用其方法,这些方法可以修改它,无论是否使用__block。__block对于基本类型的变量更有用。关于在对象指针中使用__block的情况,可以参考这里。你也可以在苹果的Blocks编程主题中了解更多关于__block的信息。
总结起来,retain cycle是由于两个对象相互强引用而导致内存无法释放的问题。解决方法包括确保包含对象只对其容器对象有弱引用,以及确保兄弟对象之间没有相互强引用。对于块中的retain cycle问题,可以使用__weak关键字来访问方法和ivar,以允许对象在不再被强引用时被释放。__block关键字很少用于对象指针,更适用于基本类型的变量。
在使用ARC和block的情况下,可能会出现retain cycle(强引用循环)的问题。如果不加以处理,可能会导致内存泄漏。
retain cycle指的是两个或多个对象之间互相持有对方的强引用,导致它们无法被释放。在上面的例子中,如果不进行处理,blocks会持有对someObject的强引用,而someObject又持有对blocks的强引用,从而形成了一个retain cycle。
解决这个问题的方法是使用__weak关键字来声明对someObject的引用,以避免形成循环引用。__weak关键字创建一个弱引用,不会增加someObject的引用计数,当someObject被释放时,该弱引用会自动变为nil。
另外一种解决方法是使用__block关键字来修饰对someObject的引用。__block关键字可以解决block内部无法修改外部变量的问题。在上述例子中,使用__block关键字修饰someObject,可以在block内部修改someObject的值,而不会形成retain cycle。
总结起来,当使用ARC和block时,需要注意避免retain cycle的问题。解决这个问题的方法是使用__weak关键字声明对对象的引用,或者使用__block关键字修饰对对象的引用。这样可以避免内存泄漏和循环引用的问题。
在第一个案例中,block捕获了self,即将self保存为另一个强指针。这增加了指向对象的保留计数,导致了保留循环。
在第二个案例中,block捕获了me,即将me保存为另一个弱指针。这不增加保留计数,因此不会导致保留循环。
(如果在block外部和内部打印me的地址,你会发现地址是不同的。block有自己的对该对象的弱引用。)
如果指向的对象被释放,Objective-C运行时会将所有弱引用(包括block保存的引用)设置为nil。
假设MyCLass实现了一个真正的拷贝...因为-copyWithZone:只是保留...这在任何不可变对象中都是合法的并且通常被执行。
:也许我表达得不好,但我的意思是block使用self(或me)的当前内容在其block上下文中保存了一个强(或弱)指针。不涉及copy方法。
是的,有时SO会将问题重新推到顶部,当有人对它们进行某些操作时...有时我必须在几个月或几年后进行修正...但对象可以在block捕获时进行复制,所以我认为这是正确的...
:你认为这不正确吗?还是你认为这是正确的?
我认为捕获对象可能会但不一定会导致block中的对象具有新的地址。
:你是在考虑指针本身的地址,还是指向对象的地址?我认为捕获(指向)一个对象不会导致对象被复制(在copy方法的意义上)。- 但当然我可能是错的...