Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.

Break retain cycles between button onTap action and view controllers#775

Merged
staskus merged 10 commits intotrunkfrom
fix/21075-fix-retain-cycles-between-button-actions-and-view-controllers
Jul 17, 2023
Merged

Break retain cycles between button onTap action and view controllers#775
staskus merged 10 commits intotrunkfrom
fix/21075-fix-retain-cycles-between-button-actions-and-view-controllers

Conversation

@staskus
Copy link
Copy Markdown
Contributor

@staskus staskus commented Jul 12, 2023

Part of: wordpress-mobile/WordPress-iOS#21075

Assigning a function directly to a closure creates a retain cycle between buttonViewController and viewController.

Mmory Leak - WordPressAuthenticator Pod

Solution

Pass a closure and use a weak self instead. Explaining each fix in the commits.


  • Bump version.
  • I have considered if this change warrants release notes and have added them to the appropriate section in the CHANGELOG.md if necessary.

Assigning function directly to a closure create a retain cycle between buttonViewController and viewController. Breaking it by using weak self.
@staskus staskus added the bug Something isn't working label Jul 12, 2023
@staskus staskus requested a review from guarani July 12, 2023 16:20
staskus added 5 commits July 13, 2023 11:03
Issue:
- NavigationController holds onto GetStartedViewController
- GetStartedViewController holds onto PasswordCoordinator
- PasswordCoordinator holds onto NavigationController

Solution:
- Break the retain cycle by reference navigationController weakly
Issue:
- NavigationController holds onto LoginPrologueViewController
- LoginPrologueViewController holds onto StoredCredentialsAuthenticator
- StoredCredentialsAuthenticator holds onto NavigationController

Solution:
- Break the retain cycle by reference navigationController weakly
@staskus staskus force-pushed the fix/21075-fix-retain-cycles-between-button-actions-and-view-controllers branch from 0ba6dd5 to c06ffa1 Compare July 13, 2023 13:06
@staskus staskus marked this pull request as ready for review July 13, 2023 17:01
@staskus
Copy link
Copy Markdown
Contributor Author

staskus commented Jul 13, 2023

@guarani

I'm thinking about how to better test it for the reviewer.

Do you think it's better to expand the testing suite here (add unit tests, add more example UI) or is it enough to build and test wordpress-mobile/WordPress-iOS#21092 locally together with these changes?

Most of the changes are providing [weak self] inside closures, not sure if anything could break in this instance.

…ain cycles

- Created verifyObjectsDeallocatedAfterTeardown method that can be used for confirming that any object is deallocated after it's lifecycle should end
- Testing most of the view controllers confirming that there aren't easily reproducible memory leaks remaining
@staskus
Copy link
Copy Markdown
Contributor Author

staskus commented Jul 14, 2023

@guarani
I added unit tests confirming that most of the VCs in the suite don't have to retain cycles, at least the most obvious ones. I found such a test both beneficial and easy to write, so I think it's a win win. I will guard against the regressions.

To save the time, here's the test in action showing it failing when reverting some of the changes I made:

Memory.Management.tests.mov

staskus added 2 commits July 14, 2023 15:49
NUXViewControllerBase is implemented as a protocol. In order not to change architecture using Obj-C runtime feature to create observers and deinitialize them on deinit
@guarani
Copy link
Copy Markdown

guarani commented Jul 14, 2023

👋 Hey @staskus, these tests for memory leaks look promising.
I'll review this today (I couldn't get to it yesterday).

Copy link
Copy Markdown

@guarani guarani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good! I first checked that memory usage was going up when navigating the demo app on trunk and then verified that it stays the same on this branch. I also checked the Memory Graph and saw that none of the view controllers beyond the demo's home screen stay in memory.

Comment on lines +297 to +314
private var wordpressSupportNotificationReceivedObserver: NSObjectProtocol? {
get {

objc_getAssociatedObject(self, &wordpressSupportNotificationReceivedKey) as? NSObjectProtocol
}
set {
objc_setAssociatedObject(self, &wordpressSupportNotificationReceivedKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}

private var wordpressSupportNotificationClearedObserver: NSObjectProtocol? {
get {
objc_getAssociatedObject(self, &wordpressSupportNotificationClearedKey) as? NSObjectProtocol
}
set {
objc_setAssociatedObject(self, &wordpressSupportNotificationClearedKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Another option that came to mind is letting the classes that implement this protocol store the observers. These variables would move to the protocol:

    var wordpressSupportNotificationReceivedObserver: NSObjectProtocol? { get set }
    var wordpressSupportNotificationClearedObserver: NSObjectProtocol? { get set }

then the protocol could be class-bound:

public protocol NUXViewControllerBase: AnyObject {
...

Then each implementing class would need to implement these variables:

class NUXTableViewController {
    public var wordpressSupportNotificationReceivedObserver: NSObjectProtocol?
    public var wordpressSupportNotificationClearedObserver: NSObjectProtocol?
...

However, neither option seems clearly better than the other, so happy to leave it as-is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Yes, I don't like adding this Obj-C magic to the code, I didn't want to refactor the structure of the code too much though. I will check your approach, maybe it will be a sweet spot between avoiding the Obj-C runtime and having to refactor too much in the original code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the change and it seems like a good approach (not one I would have thought of) 👍
addNotificationObserver caught my eye because it's storing the observer (after its added), despite the name being being add. Definitely not an issue, just an observation and no need (from my side) to change it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to mention, the approach you suggested wasn't acceptable to the compiler. If we wanted to set observer value, we had to make all the methods inside the extension mutable. Also, it prevented from calling those methods indeinit.

addNotificationObserver caught my eye because it's storing the observer (after its added), despite the name being being add. Definitely not an issue, just an observation and no need (from my side) to change it.

Yes, probably not the best naming. Due to the mutable issue I described above, I need to make the value-changing operations from inside the viewControllers. I thought using array instead of separate observer vars will be a cleaner approach.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you recall if you made the protocol class-bound? I also ran into this when I was trying out the above last week, and then realized that the error only happens because the compiler thinks the protocol could be used on structs as well. Adding : AnyObject to the protocol fixed it for me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you recall if you made the protocol class-bound?

No, I haven't, that's a good point, I missed that from the mutable hint of the compiler 🥲 Yes, then that would've worked.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍

@staskus staskus merged commit 61af6e8 into trunk Jul 17, 2023
@staskus staskus deleted the fix/21075-fix-retain-cycles-between-button-actions-and-view-controllers branch July 17, 2023 07:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants