For the property modifiers of XIB controls, should we use strong or weak?

In our current code, the properties of XIB controls are added with weak. However, we encounter some crashes due to accessing wild pointers of controls when a memory warning occurs. Colleagues from our architecture team said that they had specifically consulted Apple's technical staff before, and the reply was that strong should be used for modification, as it would make the memory more stable. They also mentioned that the statement in the official documentation is incorrect and hasn't been updated. May I ask whether strong or weak should be used for XIB control properties? What is your latest standard answer?

Answered by DTS Engineer in 851658022

The standard answer is "it depends." Without more context about your crash, I can't know which recommendation is correct for your needs, but the right choice for any situation comes down to understanding the fundamentals of ARC and UIKit view composition. Xcode uses weak when you drag out an outlet as its default, so that's a hint as to a good default, but let's look at some common examples to understand things in terms of memory references.

Important: In the diagrams that follow, each solid arrow represents a strong reference and dotted arrows are weak references. Consider these diagrams conceptual — they are not meant to confer specific system implementation details.

The most common case is that a subview is added to the parent view when designing the view in Interface Builder. When that view is created in memory by instantiating the XIB, the parent view's subviews array contains that child view, and thus has a strong reference to it, since it is in the view hierarchy by virtue of being in the subviews array. If the IBOutlet property for that child view is weak (the left diagram), the parent view has one strong memory reference to that child view. If all you do is twiddle properties to customize the subview — say to set the text or color of a label — then that child view remains in memory because the subviews array having a strong reference to it. Using weak is fine in this case.

What happens if this weakly-held view is used in a way that means this child view is sometimes removed and then added back to the view hierarchy? When it's removed from the view hierarchy, the references between child and parent views are removed, so nothing has a strongly held reference to it, so the subview is deallocated. Perhaps that's what you're encountering, or why you received that specific recommendation from Apple in the past.

Is that subview deallocating after removing from the view hierarchy good or bad? If it's weakly held, that might help keep your overall app memory footprint down, because you're not holding potentially large view hierarchies in memory when they're not needed. But if that subview is often added back to the view hierarchy later or is expensive to instantiate, then there's a cost to recreating it, and perhaps managing your own strong reference to it is the better choice (the right side of the diagram).

Note that what I write above is specifically for views coming from an XIB. Storyboards add view controllers into the mix, so there's a bit more to consider because it's easier to accidentally create strong reference cycles, and so weak is often the better choice here. Let's consider this second diagram so we understand how a storyboard can be different:

On the left, we have a view controller with its view property set up, and that view controller also has a direct reference to a subview, strongly referenced. What happens if there's a reason for the view controller to nil its view, such as when a memory pressure notification is received? That's the right diagram, where the strong reference to a nested subview accidentally causes an entire view hierarchy to be retained in memory, which defeats the purpose of the view controller removing its view in response to memory pressure, and thus is a clear bug in the view controller's implementation. Thus with storyboards, using weak properties for IBOutlet view properties is often the better default choice, as it avoids unexpected retain cycles.

— Ed Ford,  DTS Engineer

Accepted Answer

The standard answer is "it depends." Without more context about your crash, I can't know which recommendation is correct for your needs, but the right choice for any situation comes down to understanding the fundamentals of ARC and UIKit view composition. Xcode uses weak when you drag out an outlet as its default, so that's a hint as to a good default, but let's look at some common examples to understand things in terms of memory references.

Important: In the diagrams that follow, each solid arrow represents a strong reference and dotted arrows are weak references. Consider these diagrams conceptual — they are not meant to confer specific system implementation details.

The most common case is that a subview is added to the parent view when designing the view in Interface Builder. When that view is created in memory by instantiating the XIB, the parent view's subviews array contains that child view, and thus has a strong reference to it, since it is in the view hierarchy by virtue of being in the subviews array. If the IBOutlet property for that child view is weak (the left diagram), the parent view has one strong memory reference to that child view. If all you do is twiddle properties to customize the subview — say to set the text or color of a label — then that child view remains in memory because the subviews array having a strong reference to it. Using weak is fine in this case.

What happens if this weakly-held view is used in a way that means this child view is sometimes removed and then added back to the view hierarchy? When it's removed from the view hierarchy, the references between child and parent views are removed, so nothing has a strongly held reference to it, so the subview is deallocated. Perhaps that's what you're encountering, or why you received that specific recommendation from Apple in the past.

Is that subview deallocating after removing from the view hierarchy good or bad? If it's weakly held, that might help keep your overall app memory footprint down, because you're not holding potentially large view hierarchies in memory when they're not needed. But if that subview is often added back to the view hierarchy later or is expensive to instantiate, then there's a cost to recreating it, and perhaps managing your own strong reference to it is the better choice (the right side of the diagram).

Note that what I write above is specifically for views coming from an XIB. Storyboards add view controllers into the mix, so there's a bit more to consider because it's easier to accidentally create strong reference cycles, and so weak is often the better choice here. Let's consider this second diagram so we understand how a storyboard can be different:

On the left, we have a view controller with its view property set up, and that view controller also has a direct reference to a subview, strongly referenced. What happens if there's a reason for the view controller to nil its view, such as when a memory pressure notification is received? That's the right diagram, where the strong reference to a nested subview accidentally causes an entire view hierarchy to be retained in memory, which defeats the purpose of the view controller removing its view in response to memory pressure, and thus is a clear bug in the view controller's implementation. Thus with storyboards, using weak properties for IBOutlet view properties is often the better default choice, as it avoids unexpected retain cycles.

— Ed Ford,  DTS Engineer

以上内容翻译成中文: 标准答案是 “视情况而定”。如果没有更多关于你遇到的崩溃的背景信息,我无法确定哪种建议最适合你的需求,但任何情况下的正确选择都取决于对 ARC(自动引用计数)和 UIKit 视图组合基础原理的理解。Xcode 在拖出 Outlet 时默认使用 weak 修饰符,这暗示了一个不错的默认选择,但我们还是通过一些常见示例,从内存引用的角度来理解相关概念。 重要提示:在接下来的图示中,每一条实线箭头代表强引用,虚线箭头代表弱引用。这些图示仅为概念示意,并非旨在传达特定的系统实现细节。 最常见的情况是,在 Interface Builder 中设计视图时,子视图会被添加到父视图中。当通过实例化 XIB 在内存中创建该视图时,父视图的 subviews 数组会包含这个子视图,因此父视图对其持有强引用 —— 这是因为子视图通过加入 subviews 数组而存在于视图层级中。如果该子视图的 IBOutlet 属性是 weak 的(左图),那么父视图对这个子视图只有一个强内存引用。如果你所做的只是通过调整属性来定制子视图(比如设置标签的文本或颜色),那么这个子视图会一直存在于内存中,因为 subviews 数组对它持有强引用。在这种情况下,使用 weak 是没问题的。 如果这个被弱引用的视图存在这样的使用场景:有时会从视图层级中移除,之后又重新添加回来,会发生什么呢?当它从视图层级中被移除时,子视图和父视图之间的引用关系会被解除,因此没有任何对象对它持有强引用,子视图就会被销毁。或许这就是你遇到的情况,或者是过去苹果给你那个特定建议的原因。 子视图从视图层级移除后被销毁,这到底是好是坏呢?如果它是被弱引用的,这或许有助于降低应用的整体内存占用,因为当那些可能较大的视图层级不再需要时,就不会再占用内存。但如果这个子视图之后经常需要重新添加到视图层级中,或者实例化成本很高,那么重新创建它就会产生消耗,这种情况下,自己管理一个对它的强引用可能是更好的选择(图示右侧)。 需要注意的是,上面所说的内容专门针对来自 XIB 的视图。故事板(Storyboards)会引入视图控制器,因此需要考虑的因素更多 —— 因为在故事板中更容易意外创建强引用循环,所以 weak 通常是更合适的选择。我们来看第二个图示,理解故事板的不同之处: 左图中,我们有一个视图控制器,其 view 属性已设置,并且该视图控制器还直接持有一个对子视图的强引用。如果由于某种原因(比如收到内存压力通知),视图控制器需要将其 view 设为 nil,会发生什么呢?如右图所示,对嵌套子视图的强引用会意外导致整个视图层级被保留在内存中,这就违背了视图控制器为响应内存压力而移除其视图的初衷,显然是视图控制器实现中的一个 bug。因此,在故事板中,对 IBOutlet 视图属性使用 weak 修饰符通常是更好的默认选择,这样可以避免意外的引用循环。

Our code is like this:

@interface LLLineMoreView() <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@property (strong, nonatomic) IBOutlet UIView *topLineView;

@end

Exception Category: mach

Exception Type: EXC_BAD_ACCESS (SIGSEGV)

Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000008

Crashed Thread: 0

CrashDoctor Diagnosis: Attempted to dereference garbage pointer 0x8.

Thread 0 Crashed:

0 CoreFoundation 0x000000018bb3ea7c _CFXNotificationRegistrarAddObserver + [ : 368]

1 CoreFoundation 0x000000018bb3ea0c _CFXNotificationRegistrarAddObserver + [ : 256]

2 CoreFoundation 0x000000018bb3e890 CFXNotificationRegistrarAdd + [ : 576]

3 CoreFoundation 0x000000018bb3e070 _CFXNotificationRegisterObserver + [ : 248]

4 UIKitCore 0x000000018e3a39ac UILabelCommonInit + [ : 188]

5 UIKitCore 0x000000018e8133cc -[UILabel initWithCoder:] + [ : 212]

6 UIFoundation 0x000000019747aaa4 UINibDecoderDecodeObjectForValue + [ : 684]

7 UIFoundation 0x000000019747ac30 UINibDecoderDecodeObjectForValue + [ : 1080]

8 UIFoundation 0x000000019747a6f8 -[UINibDecoder decodeObjectForKey:] + [ : 308]

9 UIKitCore 0x000000018e6c6550 -[UIView initWithCoder:] + [ : 800]

10 UIFoundation 0x000000019747aaa4 UINibDecoderDecodeObjectForValue + [ : 684]

11 UIFoundation 0x000000019747ac30 UINibDecoderDecodeObjectForValue + [ : 1080]

12 UIFoundation 0x000000019747a6f8 -[UINibDecoder decodeObjectForKey:] + [ : 308]

13 UIKitCore 0x000000018e6c6550 -[UIView initWithCoder:] + [ : 800]

14 UIKitCore 0x000000018eccd94c -[UICollectionReusableView initWithCoder:] + [ : 68]

15 UIKitCore 0x000000018eccde3c -[UICollectionViewCell initWithCoder:] + [ : 68]

16 UIKitCore 0x000000018e6c9700 -[UIClassSwapper initWithCoder:] + [ : 1544]

17 UIFoundation 0x000000019747aaa4 UINibDecoderDecodeObjectForValue + [ : 684]

18 UIFoundation 0x000000019747a6f8 -[UINibDecoder decodeObjectForKey:] + [ : 308]

19 UIKitCore 0x000000018e6c6034 -[UIRuntimeConnection initWithCoder:] + [ : 92]

20 UIFoundation 0x000000019747aaa4 UINibDecoderDecodeObjectForValue + [ : 684]

21 UIFoundation 0x000000019747ac30 UINibDecoderDecodeObjectForValue + [ : 1080]

22 UIFoundation 0x000000019747a6f8 -[UINibDecoder decodeObjectForKey:] + [ : 308]

23 UIKitCore 0x000000018e6c9030 -[NSCoder(UIIBDependencyInjectionInternal) _decodeObjectsWithSourceSegueTemplate:creator:sender:forKey:] + [ : 292]

24 UIKitCore 0x000000018e76f1cc -[UINib instantiateWithOwner:options:] + [ : 768]

25 UIKitCore 0x000000018e5eda0c -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + [ : 984]

26 UIKitCore 0x000000018e5ed5b8 -[UICollectionView dequeueReusableCellWithReuseIdentifier:forIndexPath:] + [ : 84]

27 lbank-iOS-LBank 0x0000000102bf1270 -[LBKBtmLineMoreView collectionView:cellForItemAtIndexPath:] + [LBKBtmLineMoreView.m : 255]

28 UIKitCore 0x000000018e6678e4 __112-[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:]_block_invoke.394 + [ : 52]

29 UIKitCore 0x000000018e33c828 +[UIView(Animation) performWithoutAnimation:] + [ : 76]

30 UIKitCore 0x000000018e5ee9dc -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + [ : 1208]

31 UIKitCore 0x000000018e5ec178 -[UICollectionView _createVisibleViewsForSingleCategoryAttributes:limitCreation:fadeForBoundsChange:] + [ : 524]

32 UIKitCore 0x000000018e4bcbc8 -[UICollectionView _createVisibleViewsForAttributes:fadeForBoundsChange:notifyLayoutForVisibleCellsPass:] + [ : 300]

33 UIKitCore 0x000000018e4b8d9c -[UICollectionView _updateVisibleCellsNow:] + [ : 3092]

34 UIKitCore 0x000000018e652bc8 -[UICollectionView layoutSubviews] + [ : 288]

35 UIKitCore 0x000000018e34b814 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + [ : 2424]

36 QuartzCore 0x000000018d659498 CA::Layer::layout_if_needed(CA::Transaction*) + [ : 496]

37 QuartzCore 0x000000018d659024 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + [ : 148]

38 QuartzCore 0x000000018d6ae0c4 CA::Context::commit_transaction(CA::Transaction*, double, double*) + [ : 472]

39 QuartzCore 0x000000018d624d8c CA::Transaction::commit() + [ : 648]

40 UIKitCore 0x000000018e3b33f0 _UIApplicationFlushCATransaction + [ : 84]

41 UIKitCore 0x000000018e3b089c __setupUpdateSequence_block_invoke_2 + [ : 332]

42 UIKitCore 0x000000018e3b0710 _UIUpdateSequenceRun + [ : 84]

43 UIKitCore 0x000000018e3b3040 schedulerStepScheduledMainSection + [ : 172]

44 UIKitCore 0x000000018e3b0c5c runloopSourceCallback + [ : 92]

45 CoreFoundation 0x000000018bb64f4c CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + [ : 28]

A crash report is a point-in-time capture of the call stack leading up to the crash. While useful as a starting point to making are you understand the call path that got you to the crash, it doesn't contain the information about how your views were either strongly or weakly referenced in memory, and how those references changed over the lifetime of your view, which then lead to the view being released unexpectedly. You'll need to do further debugging of your code to understand the references you have, according to the rules of strong and weak references and the information I wrote above. The Allocations instrument in Instruments is one tool that is helpful, and the Memory Graph debugger with MallocStackLogging enabled is another useful tool for debugging these types of issues.

24 UIKitCore 0x000000018e76f1cc -[UINib instantiateWithOwner:options:] + [ : 768]
25 UIKitCore 0x000000018e5eda0c -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + [ : 984]
26 UIKitCore 0x000000018e5ed5b8 -[UICollectionView dequeueReusableCellWithReuseIdentifier:forIndexPath:] + [ : 84]
27 lbank-iOS-LBank 0x0000000102bf1270 -[LBKBtmLineMoreView collectionView:cellForItemAtIndexPath:] + [LBKBtmLineMoreView.m : 255]

These are useful frames to start from, and imply that you have a collection view cell that is being instantiated from an Interface Builder file. How the Interface Builder file is constructed, along with the associated source code file for the view is all pertinent information for you to consider here.

Since you also have a code-level support request open, if you could send a small test Xcode project as an attachment to that ticket that includes just these files that demonstrates the crash, I can be more specific to your situation.

— Ed Ford,  DTS Engineer

For the property modifiers of XIB controls, should we use strong or weak?
 
 
Q