diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index f47f08a9d..585f79c5f 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -50,13 +50,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro NSIndexPath* lastPotentialCellPath; // Observe UIApplicationDidEnterBackgroundNotification to cancel editing mode when app leaves the foreground state. - id UIApplicationDidEnterBackgroundNotificationObserver; + __weak id UIApplicationDidEnterBackgroundNotificationObserver; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kMXNotificationCenterDidUpdateRules to update missed messages counts. - id kMXNotificationCenterDidUpdateRulesObserver; + __weak id kMXNotificationCenterDidUpdateRulesObserver; MXHTTPOperation *currentRequest; @@ -65,7 +65,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro UISearchBar *tableSearchBar; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; } @property (nonatomic, strong) CreateRoomCoordinatorBridgePresenter *createRoomCoordinatorBridgePresenter; @@ -156,11 +156,15 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Apply dragging settings self.enableDragging = _enableDragging; + MXWeakify(self); + // Observe UIApplicationDidEnterBackgroundNotification to refresh bubbles when app leaves the foreground state. UIApplicationDidEnterBackgroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + // Leave potential editing mode - [self cancelEditionMode:isRefreshPending]; + [self cancelEditionMode:self->isRefreshPending]; }]; @@ -170,6 +174,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -268,9 +274,13 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [self.recentsTableView deselectRowAtIndexPath:indexPath animated:NO]; } + MXWeakify(self); + // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self scrollToTop:YES]; }]; @@ -278,6 +288,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro // Observe kMXNotificationCenterDidUpdateRules to refresh missed messages counts kMXNotificationCenterDidUpdateRulesObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + MXStrongifyAndReturnIfNil(self); + [self refreshRecentsTable]; }]; diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 9817f503d..a83ba10da 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -44,7 +44,7 @@ const CGFloat kTypingCellHeight = 24; // Timer used to debounce cells refresh @property (nonatomic, strong) NSTimer *refreshCellsTimer; -@property (nonatomic, readonly) id roomDataSourceDelegate; +@property (nonatomic, weak, readonly) id roomDataSourceDelegate; @property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel; diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index dd3ca7d2f..4764e03e9 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -77,6 +77,10 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { super.init() } + + deinit { + roomViewController.destroy() + } // MARK: - Public @@ -90,7 +94,8 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { self.roomViewController.delegate = self // Detect when view controller has been dismissed by gesture when presented modally (not in full screen). - self.roomViewController.presentationController?.delegate = self + // FIXME: Find a better way to manage modal dismiss. This makes the `roomViewController` to never be released + // self.roomViewController.presentationController?.delegate = self if let eventId = self.selectedEventId { self.loadRoom(withId: self.parameters.roomId, and: eventId, completion: completion) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 5b4ad0c2b..063f37ff7 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -144,7 +144,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; { // The preview header - PreviewRoomTitleView *previewHeader; + __weak PreviewRoomTitleView *previewHeader; // The customized room data source for Vector RoomDataSource *customizedRoomDataSource; @@ -156,7 +156,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSArray *currentTypingUsers; // Typing notifications listener. - id typingNotifListener; + __weak id typingNotifListener; // The position of the first touch down event stored in case of scrolling when the expanded header is visible. CGPoint startScrollingPoint; @@ -168,33 +168,33 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; UIView *missedDiscussionsDotView; // Potential encryption details view. - EncryptionInfoView *encryptionInfoView; + __weak EncryptionInfoView *encryptionInfoView; // The list of unknown devices that prevent outgoing messages from being sent MXUsersDevicesMap *unknownDevices; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; + __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kAppDelegateNetworkStatusDidChangeNotification to handle network status change. - id kAppDelegateNetworkStatusDidChangeNotificationObserver; + __weak id kAppDelegateNetworkStatusDidChangeNotificationObserver; // Observers to manage MXSession state (and sync errors) - id kMXSessionStateDidChangeObserver; + __weak id kMXSessionStateDidChangeObserver; // Observers to manage ongoing conference call banner - id kMXCallStateDidChangeObserver; - id kMXCallManagerConferenceStartedObserver; - id kMXCallManagerConferenceFinishedObserver; + __weak id kMXCallStateDidChangeObserver; + __weak id kMXCallManagerConferenceStartedObserver; + __weak id kMXCallManagerConferenceFinishedObserver; // Observers to manage widgets - id kMXKWidgetManagerDidUpdateWidgetObserver; + __weak id kMXKWidgetManagerDidUpdateWidgetObserver; // Observer kMXRoomSummaryDidChangeNotification to keep updated the missed discussion count - id mxRoomSummaryDidChangeObserver; + __weak id mxRoomSummaryDidChangeObserver; // Observer for removing the re-request explanation/waiting dialog - id mxEventDidDecryptNotificationObserver; + __weak id mxEventDidDecryptNotificationObserver; // The table view cell in which the read marker is displayed (nil by default). MXKRoomBubbleTableViewCell *readMarkerTableViewCell; @@ -209,13 +209,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; NSArray *rightBarButtonItems; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. - id kThemeServiceDidChangeThemeNotificationObserver; + __weak id kThemeServiceDidChangeThemeNotificationObserver; // Observe URL preview updates to refresh cells. - id URLPreviewDidUpdateNotificationObserver; + __weak id URLPreviewDidUpdateNotificationObserver; // Listener for `m.room.tombstone` event type - id tombstoneEventNotificationsListener; + __weak id tombstoneEventNotificationsListener; // Homeserver notices MXServerNotices *serverNotices; @@ -454,9 +454,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; self.jumpToLastUnreadLabel.text = [VectorL10n roomJumpToFirstUnread]; + MXWeakify(self); + // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; @@ -587,9 +591,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [self listenTombstoneEventNotifications]; [self listenMXSessionStateChangeNotifications]; + MXWeakify(self); + // Observe kAppDelegateDidTapStatusBarNotification. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self setBubbleTableViewContentOffset:CGPointMake(-self.bubblesTableView.adjustedContentInset.left, -self.bubblesTableView.adjustedContentInset.top) animated:YES]; }]; @@ -661,9 +669,13 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [AppDelegate theDelegate].visibleRoomId = self.roomDataSource.roomId; } + MXWeakify(self); + // Observe network reachability kAppDelegateNetworkStatusDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateNetworkStatusDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self refreshActivitiesViewDisplay]; }]; @@ -673,6 +685,8 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; // Observe missed notifications mxRoomSummaryDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXRoomSummaryDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + MXRoomSummary *roomSummary = notif.object; if ([roomSummary.roomId isEqualToString:self.roomDataSource.roomId]) @@ -1398,8 +1412,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } if (URLPreviewDidUpdateNotificationObserver) { - [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; - URLPreviewDidUpdateNotificationObserver = nil; + [NSNotificationCenter.defaultCenter removeObserver:URLPreviewDidUpdateNotificationObserver]; } [self removeCallNotificationsListeners]; @@ -1555,8 +1568,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)registerURLPreviewNotifications { + MXWeakify(self); + URLPreviewDidUpdateNotificationObserver = [NSNotificationCenter.defaultCenter addObserverForName:URLPreviewDidUpdateNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull notification) { + MXStrongifyAndReturnIfNil(self); + // Ensure this is the correct room if (![(NSString*)notification.userInfo[@"roomId"] isEqualToString:self.roomDataSource.roomId]) { @@ -4768,10 +4785,14 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)listenCallNotifications { + MXWeakify(self); + kMXCallStateDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallStateDidChange object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + MXCall *call = notif.object; - if ([call.room.roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([call.room.roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; @@ -4779,16 +4800,20 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; }]; kMXCallManagerConferenceStartedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerConferenceStarted object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + NSString *roomId = notif.object; - if ([roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; } }]; kMXCallManagerConferenceFinishedObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerConferenceFinished object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + NSString *roomId = notif.object; - if ([roomId isEqualToString:customizedRoomDataSource.roomId]) + if ([roomId isEqualToString:self->customizedRoomDataSource.roomId]) { [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; @@ -5839,8 +5864,12 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)listenMXSessionStateChangeNotifications { + MXWeakify(self); + kMXSessionStateDidChangeObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:self.roomDataSource.mxSession queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + if (self.roomDataSource.mxSession.state == MXSessionStateSyncError || self.roomDataSource.mxSession.state == MXSessionStateRunning) { diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index cb8fbaa6c..0f11fd17d 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -77,8 +77,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } func start(with spaceId: String?) { - self.currentSpaceId = spaceId - + // If start has been done once do not setup view controllers again if self.hasStartedOnce == false { let masterTabBarController = self.createMasterTabBarController() @@ -105,9 +104,13 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { versionCheckCoordinator.start() add(childCoordinator: versionCheckCoordinator) } + + self.updateMasterTabBarController(with: spaceId, forceReload: true) + } else { + self.updateMasterTabBarController(with: spaceId) } - - self.updateMasterTabBarController(with: spaceId) + + self.currentSpaceId = spaceId } func toPresentable() -> UIViewController { @@ -280,7 +283,9 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { gesture.delegate = self } - private func updateMasterTabBarController(with spaceId: String?) { + private func updateMasterTabBarController(with spaceId: String?, forceReload: Bool = false) { + + guard forceReload || spaceId != self.currentSpaceId else { return } self.updateTabControllers(for: self.masterTabBarController, showCommunities: spaceId == nil) self.masterTabBarController.filterRooms(withParentId: spaceId, inMatrixSession: self.currentMatrixSession) diff --git a/Riot/Routers/NavigationRouter.swift b/Riot/Routers/NavigationRouter.swift index 880dbd9ee..25a0aa333 100755 --- a/Riot/Routers/NavigationRouter.swift +++ b/Riot/Routers/NavigationRouter.swift @@ -299,13 +299,12 @@ final class NavigationRouter: NSObject, NavigationRouterType { self.postNotification(withName: NavigationRouter.willPopModule, for: viewController) } - private func didPopViewController(_ viewController: UIViewController) { + private func didPopViewController(_ viewController: UIViewController) { + self.postNotification(withName: NavigationRouter.didPopModule, for: viewController) // Call completion closure associated to the view controller // So associated coordinator can be deallocated runCompletion(for: viewController) - - self.postNotification(withName: NavigationRouter.didPopModule, for: viewController) self.removeModule(for: viewController) } diff --git a/changelog.d/5055.bugfix b/changelog.d/5055.bugfix new file mode 100644 index 000000000..a969f4fef --- /dev/null +++ b/changelog.d/5055.bugfix @@ -0,0 +1 @@ +RoomVC: Fix retain cycles that prevents `RoomViewController` to be deallocated. \ No newline at end of file