Swift, why this local var is not released? - swift

I create a local var in a awakeFromNib function, use it inside a UIView animation block, but it is never released, why?
Here is the code (inside awakeFromNib in a UITableViewCell)
var fullPhotoDisposeBag = DisposeBag()
fullScreenImageView.rx.tapGesture().when(UIGestureRecognizerState.recognized).subscribe(onNext: { (tap) in
UIView.animate(withDuration: 0.15, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
fullScreenImageView.frame = oldFullScreenFrame
}, completion: { (finished) in
fullScreenImageView.removeFromSuperview()
let _ = fullPhotoDisposeBag //This line avoid early release, but retains too much...
})
}, onDisposed:{
print("disposed")
}).addDisposableTo(fullPhotoDisposeBag)
One clue is that the tableView is at the root of one tab of my app, so the UITableView is never deallocated thus the UITableViewCell is never deallocated due to the reusability.
But why would ARC keeps that variable? Used only in a function and a completion block?
PS: I currently use an optional DisposeBag that I set to nil in the completion block, but I don't understand why I need to do that...

Looks like a retain cycle. My guess is the garbage collector won't release the local variable until the completion closure stops referencing it, and the completion closure is never released because it is owned by your local function.
My advise would be to move away this code from your UIView to your UIViewController, since that is where this sort of behavior should be defined.

Related

Weak view reference won't get deallocated after temporarily added to view hierarchy

I ran into the weirdest thing, maybe someone has an explanation.
Steps:
Create a UIView A
Create a weak reference to A
Add A to the view hierarchy
Remove A from the view hierarchy
Set A to nil
The weak reference still exists.
If you skip step 3 and 4 the weak reference becomes nil as expected.
Code to test:
TestView to check deinit
class TestView: UIView {
deinit {
print("deinit")
}
}
Unit test
class RetainTests: XCTestCase {
func testRetainFails() {
let holderView = UIView()
var element: TestView? = TestView()
holderView.addSubview(element!)
element?.removeFromSuperview()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement) // fails!
}
func testRetainPasses() {
var element: TestView? = TestView()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement)
}
}
Both tests will print out deinit on the console, but in the case of the failing test the weakElement still holds the reference if element was in a view hierarchy for any time, although element got successfully deallocated. How can that be?
(Edit: This is inside an app, NOT a playground)
There's an autorelease attached to TestView when you add and remove it to the superview. If you make sure to drain the autorelease pool, then this test behaves as you expect:
autoreleasepool {
holderView.addSubview(element!)
element?.removeFromSuperview()
}
That said, you cannot rely on this behavior. There is no promise about when an object will be destroyed. You only know it will be after you release your last strong reference to it. There may be additional retains or autoreleases on the object. And there's no promise that deinit will ever be called, so definitely make sure not to put any mandatory logic there. It should only perform memory cleanup. (Both Mac and iOS, for slightly different reasons, never call deinit during program termination.)
In your failing test case,deinit is printed after the assertion fails. You can demonstrate this by placing breakpoints on the failing assertion and the print. This is because the autorelease pool is drained at the end of the test case.
The underlying lesson is that it is very common for balanced retain/autorelease calls to be be placed on an object that is passed to ObjC code (and UIKit is heavily ObjC). You should generally not expect UIKit objects to be destroyed before the end of the current event loop. Don't rely on that (it's not promised), but it's often true.

Swift: keeping a strong reference to a weak variable

I have a class with a weak reference to its delegate. In a background operation, I need to set the delegate, perform an operation on the class, and then have the delegate released.
The code below works in Debug mode, but fails in Release mode, because in Release mode the delegate is released right away.
protocol DocumentDelegate:class { ... }
class MyDocument {
weak var delegate:DocumentDelegate?
func save() {
assert(self.delegate =! nil)
}
}
// Later:
// (#1) Using "var" does not work:
var delegate:DocumentDelegate? = InterimDelegate()
let document = MyDocument()
document.delegate = delegate
// Compiled in Release mode, at this time the delegate is already nil!
document.save()
delegate = nil
// (#2) Using "let" does work:
let delegate:DocumentDelegate = InterimDelegate()
let document = MyDocument()
document.delegate = delegate
// Compiled in Release mode, at this time the delegate is already nil!
document.save()
I assumed that the last instruction delegate = nil would cause the compiler to keep the delegate around until then (i.e. the "last" time the variable is used). However, thinking about it, it does make sense that the compiler optimizes the code and releases the delegate instance right away, since there are no other strong references.
However, I do not understand why the compiler does not behave the same way in the second case when using "let". Here as well the compiler could see that the delegate is not referenced via a strong reference anywhere else, but it does keep it around until the end of the block.
What would be a good way to think about this and what is a good way to keep a strong reference to the weak delegate?
While I wholeheartedly agree with Rob Napier’s analysis, for the sake of completeness, I should note that you can also make the lifetime of the object explicit:
let delegate = InterimDelegate()
withExtendedLifetime(delegate) {
let document = MyDocument()
document.delegate = delegate
document.save()
}
What you're describing is just a strong delegate. Get rid of weak, you don't mean it.
If you need an object to exist as long as you have a reference to it, and you plan to manually remove that reference at some point, that's a strong reference. The important thing for a delegate is that it at some point release its reference to avoid cycles. That's typically done with a weak reference because it makes things easy. But it can be done manually with strong reference. This is how URLSession's delegate works, for example.
If you intend the delegate to always be released after save, however, you may want to set it to nil in save. That can be nicer than having the caller do it (and matches how URLSession works; it automatically releases its delegate when it completes).
Just to explain what's happening in your code, ARC is allowed to release a reference after its last use.
// Example 1
// delegate is a strong reference
var delegate:DocumentDelegate? = InterimDelegate()
let document = MyDocument()
// Last read of delegate.
document.delegate = delegate
// delegate is released here. `document.delegate` is weak,
// so object is deallocated and set to nil.
// Compiled in Release mode, at this time the delegate is already nil!
document.save()
// This assignment is a no-op. The system was allowed to set it
// to nil earlier, so this doesn't matter.
delegate = nil
// Example 2
// This creates a strong reference
let delegate:DocumentDelegate = InterimDelegate()
let document = MyDocument()
// Last read of delegate.
document.delegate = delegate
// delegate is released here. `document.delegate` is weak,
// so object is deallocated and set to nil.
// Compiled in Release mode, at this time the delegate is already nil!
document.save()

Weak var outlet is lost (=nil) when referred to in a delegate method

I have a UICollectionView in my class declared as
#IBOutlet weak var artworkCollectionView: UICollectionView!
Inside this class there is one delegate method called by two other View Controllers, one of these VC is a pop up, the other one is a normal VC.
The delegate method gets some data from the database and then updates the collection view calling inside a closure:
self.artworkCollectionView.reloadData()
When the delegate method is called by the pop up VC, then all works great. BUT when the delegate method is called by the normal VC when it gets to self.artworkCollectionView.reloadData() it gets the infamous Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.
I have checked all the references to the cell reuseIdentifier and all is correct. I suspect that since the UICollectionView is declared as weak var, when I go from the current class to the pop up and then the pop up calls the delegate methods, the reference is not lost, but when I go from the current class to the normal VC and then the normal VC calls the delegate method the reference to my weak var is lost and so it is "seen" as nil.
#IBOutlet weak var artworkCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Set up
artworkCollectionView.dataSource = self
artworkCollectionView.delegate = self
artworkCollectionView.isUserInteractionEnabled = true
artworkCollectionView.allowsSelection = true
artworkCollectionView.register(UINib(nibName:
"MyCollectionViewCell", bundle: nil),
forCellWithReuseIdentifier: "cell")
}
// delegate method
func reloadCollections() {
retrieveAlbumRatings { (isAlbum) in
if isAlbum {
self.retrieveAlbumData(completion: { (isFinished) in
if isFinished {
// Reload collection views
self.artworkCollectionView.reloadData()
}
})
}
}
}
If I am right, my question is: how can I give weak var artworkCollectionView: UICollectionView! a STRONG reference so that it does not get lost in the flow from the current class to the normal VC and back?
EDIT: here is what I tried so far:
Remove “weak” from the outlet declaration so making it: #IBOutlet var artworkCollectionView: UICollectionView!
But I got the same error
I passed artworkCollectionView to the normal VC via override performSegue and then passed it back as an argument of the delegate method. This does not give me the fatal error but also it does not reload the UICollectionView because I think that anyway the weak reference to the UICollectionView outlet is lost.
Thanks for your help (disclaimer: I am pretty new to Swift..)
Inside this class there is one delegate method called by two other
View Controllers, one of these VC is a pop up, the other one is a
normal VC.
The delegate method gets some data from the database and then updates
the collection view calling inside a closure:
self.artworkCollectionView.reloadData()
The flow appears to be that you have a VC containing the code above, the VC can either open a pop-up or just do a standard push segue to the "normal VC".
You want some operation to occur in either the pop-up VC or normal VC, load some data and then when the user is directed back to the originating VC, the UICollectionView is updated with that data.
Your problems are the following:
I passed artworkCollectionView to the normal VC via override
performSegue and then passed it back as an argument of the delegate
method. This does not give me the fatal error but also it does not
reload the UICollectionView because I think that anyway the weak
reference to the UICollectionView outlet is lost.
You shouldn't be passing anything around like this in most cases unless you have a really good reason to do so (I don't see one).
You want a separation of concerns here. You have to think carefully about what you wanjt to pass between VCs to avoid making weird dependencies between them. I wouldn't pass outlets for multiple reasons, the first being that you now have to keep track of the outlet in multiple VCs if you ever decide to change it. The second being that it requires too much mental gymnastics to keep track of the state of the outlet since it's being passed around everywhere. The outlets are also only guaranteed to be set at certain phases of the lifecycle. For example if you retrieve the destination VC from the segue in prepareForSegue:sender: and attempt to reference the outlets at that time, they will all be nil because they haven't been set yet.
These are all good reasons why the VC that contains the code above should be the one (and the only one) maintaining control over what gets displayed in artworkCollectionView and when. The problem here is how you're approaching this, rather than having the pop-up or normal VC call the delegate method or doing weird things like passing outlets from one VC to another, just pass the data around instead.
The simplest example is:
The pop-up VC and normal VC call some code to actually fetch the
data.
Then depending on how you actually segued to the pop-up VC or
normal VC from original VC, use either parentViewController or
presentingViewController to get the reference to the original VC.
Set the data into the original VC through that reference.
Dismiss the pop-up VC or normal VC if necessary (depends on your specific app, maybe you want the user to push a UIButton to dismiss instead of doing it for them).
When the original VC comes back into view, add some code to a lifecycle method like
viewWillAppear to have it load the contents of the data into the
UICollectionView at that time.
I see no reason why you should be passing any outlets outside of the original VC that should be the one managing it.
Short answer: Don't do that. You should treat a view controller's views as private. You should add a method to your view controller that other objects call to tell it to reload it's collection view.
The longer answer is that your view controller's collection view should stick around as long as the view controller is on-screen. It sounds like you don't have a very strong understanding of object lifecycle and how ARC works. You should read up on that and do some exercises until you understand it better.
Try something like this:
//Make artworkCollectionView a normal weak var, not implicitly unwrapped.
//You'll need to change your other code to unwrap it every time you use it.
#IBOutlet weak var artworkCollectionView: UICollectionView?
...
func reloadCollections() {
retrieveAlbumRatings { (isAlbum) in
if isAlbum {
//The construct `[weak self]` below is called a capture list
self.retrieveAlbumData(completion: { [weak self] (isFinished) in
guard let weakSelf = self else {
print("self is nil");
return
}
}
if isFinished {
// Reload collection views
guard let collectionView = weakSelf.artworkCollectionView else {
print("collectionView is nil!")
return
}
collectionView.reloadData()
})
}
}
}

UIView animate with completion block not working

I have the following small function wich contains two primary actions which I would like to occur. A three second animation, and then upon completion of that animation, another function call. What is happening, however, is that the line of code that calls another function is being executed before the UIView animation has completed.
func switchToNormalState() {
print("Switching to Normal State")
currentState = "normal"
intervalCounter = intervalCounter + 1
setVisualsForState()
UIView.animateWithDuration(Double(normalDuration), animations: { () -> Void in
self.ProgressBar.setProgress(1.0, animated: true)
}, completion: { _ in
self.switchToFastState()
})
}
I have studied other similar posts looking for the solution but none of the solutions have solved my puzzle. What am I doing wrong in the completion block in order to make these actions a sequence and not simultaneous?
Assuming that ProgressBar is a UIProgressView, the setProgress method animates itself and doesn't need to be contained in a UIView.animateWithDuration call.
This means the setProgress animation is probably longer than the duration you're putting in the UIView.animateWithDuration call, so self.switchToFastState() is being called when the UIView animation ends- before the setProgress animation completes.
Solutions
Change the duration. You can hack around this by finding/guessing the duration of the setProgress animation and using that as your UIView animation duration. This should mimic the behavior you are going for.
Change the duration but use GCD. This is the same idea as above, but doesn't use the UIView animation method. You can see this question for how to use GCD.
NSTimer or Subclassing. There are solutions here that use NSTimer or subclass UIProgressView.
I am not sure if this is the best solution, but this is what I ended up going with:
ProgressBar.progress = 1.0
UIView.animateWithDuration(Double(normalDuration), animations: { () -> Void in
self.ProgressBar.layoutIfNeeded()
}, completion: { (_) -> Void in
self.switchToFastState()
})
This solution removes the setProgress (which may be animated) from the animation block and this allows the UIView animation to execute without interuption.

how can the UILabel become a CALayer and cause a crash?

I've got a custom UIView of which several instances are created inside a loop:
let models = CDMyModel.MR_findAllSortedBy("position", ascending: true) as! [CDMyModel]
for view in myViews {
view.removeFromSuperview()
}
self.myViews.removeAll(keepCapacity: true)
for model in models {
let myView = MYFaqView(width: CGRectGetWidth(self.view.frame))
myView.titleLabel.text = model.title
myView.content.text = model.content
myView.titleLabel.sizeToFit()
self.scrollView.addSubview(myView)
self.myViews.append(myView)
}
I do sometimes see crashes in Crashlytics in the line with myView.content.text = model.content:
According to the crash I assume it has something to do with memory, but I really don't know how the myView could have been released at that point.
All this happens in viewWillAppear:. Could the removing before has to do something with this? But I assume everything happens on the main thread, so this shouldn't be a problem as well - I'm really stuck here.
The crash happens on iOS 9.
EDIT
MyFaqView init method:
init(width:CGFloat) {
self.width = width
super.init(frame: CGRectZero)
self.addSubview(self.titleLabel)
self.addSubview(self.toggleImageView)
self.addSubview(self.separatorView)
self.content.clipsToBounds = true
self.addSubview(self.content)
self.translatesAutoresizingMaskIntoConstraints = false
self.clipsToBounds = true
}
EDIT
let content:UILabel = {
let l = UILabel()
l.numberOfLines = 0
if let font = UIFont(name: "OpenSans", size: 14) {
l.font = font
}
return l
}()
These problems are always very tricky to track down.
Basically, what is happening is memory corruption. The address 0x14f822a0 that was previously occupied by your UILabel content has been used by something else, in this case a CALayer. You can verify this if the crash happens locally by entering po 0x14f822a0 in lldb and sure enough it will output that address to be of type CALayer.
With these errors, although the crash line can provide a clue, it is not always the cause of the error. Something has already happened elsewhere.
Although Swift is mostly memory managed, there are still pitfalls for the unwary. Personally I have seen two principal causes of memory corruption. The first is with retain cycles caused by self referencing closures. The second - more pertinent to your problem - is with views related to Storyboards and Xibs.
If we follow this through logically, we can consider that the CALayer now occupies the address space previously taken by your UILabel content. The runtime attempts to send a message to the object is thinks occupies that address, and that is caught by a Swift runtime assert which then triggers a EXC_BAD_INSTRUCTION crash.
Now, for some other object to have taken up residence at that address, the original inhabitant the UILabel content must have been released. So why would the runtime release content? Because it is no longer required, i.e. it is not a subview or property of any view that it still required.
I would bet that if you change content to be a subclass UILabel and add a deinit method that you then breakpoint, you will be surprised to see that it is being unexpectedly deinitialised early on. To test this create a type as follows:
class DebugLabel: UILabel
{
func deinit
{
NSLog("Breakpoint on this line here!")
}
}
Then change content's type to be the DebugLabel above.
So why is all this happening? My money is on you having one of your view properties that has been created programmatically as being either weak or unowned. Perhaps you had these set up previously using an IBOutlet that you then removed but forgot to remove the weak designator?
Check through each and every one carefully and I am sure you will find the cause of the problem above. Nothing that is created programatically either by using an initialiser or UINib should be designated weak or unowned.
Brief viewing shows me two potential problems:
You can break iterator here, that cause undefined behavior -
removeFromSuperview() really remove the view from the hierarchy and release it and reduce the number of elements in myViews.
for view in myViews {
view.removeFromSuperview() }
What do you do here? Seems you repeat the previous step.
self.myViews.removeAll(keepCapacity: true)