From 830eec402da3edcacc05dc98d27a3f574f290f35 Mon Sep 17 00:00:00 2001 From: Kateryna Kostiuk Date: Mon, 17 Dec 2018 16:39:46 -0500 Subject: [PATCH] fix: memory leak on call fix memory leak related to call widgets and call model Change-Id: Icd0f86cc4045deffb1decd88bc88c63bbe890d13 --- Ring/Ring/Calls/ButtonsContainerView.swift | 13 +--- .../Calls/ButtonsContainerViewModel.swift | 20 ++--- Ring/Ring/Calls/CallViewController.swift | 77 ++++++++++--------- Ring/Ring/Calls/CallViewModel.swift | 45 ++++++----- 4 files changed, 77 insertions(+), 78 deletions(-) diff --git a/Ring/Ring/Calls/ButtonsContainerView.swift b/Ring/Ring/Calls/ButtonsContainerView.swift index dca2ec3..d60ba41 100644 --- a/Ring/Ring/Calls/ButtonsContainerView.swift +++ b/Ring/Ring/Calls/ButtonsContainerView.swift @@ -51,14 +51,14 @@ class ButtonsContainerView: UIView, NibLoadable { didSet { self.viewModel?.observableCallOptions .observeOn(MainScheduler.instance) - .subscribe(onNext: { (callOptions) in + .subscribe(onNext: { [weak self] callOptions in switch callOptions { case .none: - self.withoutOptions() + self?.withoutOptions() case .optionsWithoutSpeakerphone: - self.optionsWithoutSpeaker() + self?.optionsWithoutSpeaker() case .optionsWithSpeakerphone: - self.optionsWithSpeaker() + self?.optionsWithSpeaker() } }).disposed(by: self.disposeBag) } @@ -76,11 +76,6 @@ class ButtonsContainerView: UIView, NibLoadable { override open func didMoveToWindow() { super.didMoveToWindow() self.cancelButton.backgroundColor = UIColor.red - if #available(iOS 11.0, *) { - guard let window = self.window else { - return - } - } } func commonInit() { diff --git a/Ring/Ring/Calls/ButtonsContainerViewModel.swift b/Ring/Ring/Calls/ButtonsContainerViewModel.swift index 2d7198d..9a0b625 100644 --- a/Ring/Ring/Calls/ButtonsContainerViewModel.swift +++ b/Ring/Ring/Calls/ButtonsContainerViewModel.swift @@ -35,7 +35,7 @@ class ButtonsContainerViewModel { var isAudioOnly: Bool let avalaibleCallOptions = BehaviorSubject(value: .none) - lazy var observableCallOptions: Observable = { + lazy var observableCallOptions: Observable = { [unowned self] in return self.avalaibleCallOptions.asObservable() }() @@ -49,38 +49,38 @@ class ButtonsContainerViewModel { private func checkCallOptions() { let callIsActive: Observable = { - self.callService.currentCall.filter({ [unowned self] call in - return call.state == .current && call.callId == self.callID + self.callService.currentCall.filter({ [weak self] call in + return call.state == .current && call.callId == self?.callID }).map({_ in return true }) }() callIsActive - .subscribe(onNext: { [unowned self] active in + .subscribe(onNext: { [weak self] active in if !active { return } if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad { - self.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone) + self?.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone) return } - self.connectToSpeaker() + self?.connectToSpeaker() }).disposed(by: self.disposeBag) } private func connectToSpeaker() { - let speakerIsAvailable: Observable = { + let speakerIsAvailable: Observable = { [unowned self] in return self.audioService.enableSwitchAudio.map({ (hide) in !hide }) }() - speakerIsAvailable.subscribe(onNext: { [unowned self] available in + speakerIsAvailable.subscribe(onNext: { [weak self] available in if available { - self.avalaibleCallOptions.onNext(.optionsWithSpeakerphone) + self?.avalaibleCallOptions.onNext(.optionsWithSpeakerphone) return } - self.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone) + self?.avalaibleCallOptions.onNext(.optionsWithoutSpeakerphone) }).disposed(by: self.disposeBag) } } diff --git a/Ring/Ring/Calls/CallViewController.swift b/Ring/Ring/Calls/CallViewController.swift index 1cb5283..2cd2f46 100644 --- a/Ring/Ring/Calls/CallViewController.swift +++ b/Ring/Ring/Calls/CallViewController.swift @@ -135,16 +135,17 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { @objc func hideCapturedVideo() { if self.isMenuShowed { return } - UIView.animate(withDuration: 0.3, animations: { - if self.capturedVideoBlurEffect.alpha == 0 { - self.isVideoHidden = true - self.capturedVideoBlurEffect.alpha = 1 + UIView.animate(withDuration: 0.3, animations: { [weak self] in + if self?.capturedVideoBlurEffect.alpha == 0 { + self?.isVideoHidden = true + self?.capturedVideoBlurEffect.alpha = 1 } else { - self.isVideoHidden = false - self.capturedVideoBlurEffect.alpha = 0 + self?.isVideoHidden = false + self?.capturedVideoBlurEffect.alpha = 0 } - self.resizeCapturedVideo(withInfoContainer: !self.infoContainer.isHidden) - self.view.layoutIfNeeded() + guard let hidden = self?.infoContainer.isHidden else {return} + self?.resizeCapturedVideo(withInfoContainer: !hidden) + self?.view.layoutIfNeeded() }) } @@ -163,13 +164,13 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { func animateCallCircle() { self.callPulse.alpha = 0.5 self.callPulse.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) - UIView.animate(withDuration: 1.5, animations: { - self.callPulse.alpha = 0.0 - self.callPulse.transform = CGAffineTransform(scaleX: 2.0, y: 2.0) - self.view.layoutIfNeeded() - }, completion: { [unowned self] _ in - if self.viewModel.call?.state == .ringing || self.viewModel.call?.state == .connecting { - self.animateCallCircle() + UIView.animate(withDuration: 1.5, animations: { [weak self] in + self?.callPulse.alpha = 0.0 + self?.callPulse.transform = CGAffineTransform(scaleX: 2.0, y: 2.0) + self?.view.layoutIfNeeded() + }, completion: { [weak self] _ in + if self?.viewModel.call?.state == .ringing || self?.viewModel.call?.state == .connecting { + self?.animateCallCircle() } }) } @@ -178,8 +179,8 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { self.buttonsContainer.viewModel = self.viewModel.containerViewModel self.buttonsContainer.cancelButton.rx.tap .subscribe(onNext: { [weak self] in - self?.removeFromScreen() self?.viewModel.cancelCall() + self?.removeFromScreen() }).disposed(by: self.disposeBag) self.buttonsContainer.muteAudioButton.rx.tap @@ -326,17 +327,17 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { self.viewModel.showCallOptions .observeOn(MainScheduler.instance) - .subscribe(onNext: { show in + .subscribe(onNext: { [weak self] show in if show { - self.showContactInfo() + self?.showContactInfo() } }).disposed(by: self.disposeBag) self.viewModel.showCancelOption .observeOn(MainScheduler.instance) - .subscribe(onNext: { show in + .subscribe(onNext: { [weak self] show in if show { - self.showCancelButton() + self?.showCancelButton() } }).disposed(by: self.disposeBag) @@ -362,8 +363,8 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { if !self.viewModel.isAudioOnly { self.viewModel.callPaused .observeOn(MainScheduler.instance) - .subscribe(onNext: { show in - self.setAvatarView(show) + .subscribe(onNext: { [weak self] show in + self?.setAvatarView(show) }).disposed(by: self.disposeBag) } @@ -454,6 +455,9 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { } func removeFromScreen() { + if !self.infoContainer.isHidden { + task?.cancel() + } UIDevice.current.isProximityMonitoringEnabled = false self.dismiss(animated: false) } @@ -485,10 +489,11 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { // Waiting for screen size change DispatchQueue.global(qos: .background).async { sleep(UInt32(0.5)) - DispatchQueue.main.async { - self.resizeCapturedVideo(withInfoContainer: !self.infoContainer.isHidden) - if UIDevice.current.hasNotch && (UIDevice.current.orientation == .landscapeRight || UIDevice.current.orientation == .landscapeLeft) && self.infoContainer.isHidden == false { - self.buttonsContainerBottomConstraint.constant = 1 + DispatchQueue.main.async { [weak self] in + guard let hidden = self?.infoContainer.isHidden else {return} + self?.resizeCapturedVideo(withInfoContainer: !hidden) + if UIDevice.current.hasNotch && (UIDevice.current.orientation == .landscapeRight || UIDevice.current.orientation == .landscapeLeft) && self?.infoContainer.isHidden == false { + self?.buttonsContainerBottomConstraint.constant = 1 } } } @@ -572,21 +577,21 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { self.infoContainer.isHidden = false self.view.layoutIfNeeded() - UIView.animate(withDuration: 0.2, animations: { - self.capturedVideoBlurEffect.alpha = 0 - self.resizeCapturedVideo(withInfoContainer: true) - self.infoContainerTopConstraint.constant = -10 - if UIDevice.current.hasNotch && (self.orientation == .landscapeRight || self.orientation == .landscapeLeft) { - self.buttonsContainerBottomConstraint.constant = 1 + UIView.animate(withDuration: 0.2, animations: { [weak self] in + self?.capturedVideoBlurEffect.alpha = 0 + self?.resizeCapturedVideo(withInfoContainer: true) + self?.infoContainerTopConstraint.constant = -10 + if UIDevice.current.hasNotch && (self?.orientation == .landscapeRight || self?.orientation == .landscapeLeft) { + self?.buttonsContainerBottomConstraint.constant = 1 } else if UIDevice.current.userInterfaceIdiom == .pad { - self.buttonsContainerBottomConstraint.constant = 30 + self?.buttonsContainerBottomConstraint.constant = 30 } else { - self.buttonsContainerBottomConstraint.constant = 10 + self?.buttonsContainerBottomConstraint.constant = 10 } - self.view.layoutIfNeeded() + self?.view.layoutIfNeeded() }) - task = DispatchWorkItem { self.hideContactInfo() } + task = DispatchWorkItem {[weak self] in self?.hideContactInfo() } DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 7, execute: task!) } diff --git a/Ring/Ring/Calls/CallViewModel.swift b/Ring/Ring/Calls/CallViewModel.swift index 6400352..f2d8038 100644 --- a/Ring/Ring/Calls/CallViewModel.swift +++ b/Ring/Ring/Calls/CallViewModel.swift @@ -93,11 +93,11 @@ class CallViewModel: Stateable, ViewModel { return callService.currentCall.filter({ [weak self] call in return call.callId == self?.call?.callId }) - .map({[weak self] call in + .map({ call in return call.state == .over || call.state == .failure - }).map({ hide in + }).map({ [weak self] hide in if hide { - self.videoService.setCameraOrientation(orientation: UIDevice.current.orientation) + self?.videoService.setCameraOrientation(orientation: UIDevice.current.orientation) } return hide }) @@ -154,16 +154,16 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var isActiveVideoCall: Observable = { - return self.callService.currentCall - .filter({ [weak self] call in - return call.callId == self?.call?.callId + lazy var isActiveVideoCall: Observable = { [unowned self] in + return (self.callService.currentCall + .filter({call in + return call.callId == self.call?.callId }).map({ call in return call.state == .current && !self.isAudioOnly - }) + })) }() - lazy var showCallOptions: Observable = { + lazy var showCallOptions: Observable = { [unowned self] in return Observable.combineLatest(self.screenTapped.asObservable(), isActiveVideoCall) { (tapped, shouldRespond) in if tapped && shouldRespond { @@ -173,7 +173,7 @@ class CallViewModel: Stateable, ViewModel { } }() - lazy var showCancelOption: Observable = { + lazy var showCancelOption: Observable = { [unowned self] in return self.callService.currentCall .filter({ [weak self] call in return call.callId == self?.call?.callId && @@ -183,7 +183,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var showCapturedFrame: Observable = { + lazy var showCapturedFrame: Observable = { [unowned self] in return self.callService.currentCall .filter({ [weak self] call in return call.callId == self?.call?.callId && @@ -195,7 +195,7 @@ class CallViewModel: Stateable, ViewModel { var screenTapped = BehaviorSubject(value: false) - lazy var videoButtonState: Observable = { + lazy var videoButtonState: Observable = { [unowned self] in let onImage = UIImage(asset: Asset.videoRunning) let offImage = UIImage(asset: Asset.videoMuted) @@ -208,7 +208,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var videoMuted: Observable = { + lazy var videoMuted: Observable = { [unowned self] in return self.callService.currentCall.filter({ [weak self] call in call.callId == self?.call?.callId && call.state == .current @@ -217,7 +217,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var audioButtonState: Observable = { + lazy var audioButtonState: Observable = { [unowned self] in let onImage = UIImage(asset: Asset.audioRunning) let offImage = UIImage(asset: Asset.audioMuted) @@ -229,7 +229,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var speakerButtonState: Observable = { + lazy var speakerButtonState: Observable = { [unowned self] in let offImage = UIImage(asset: Asset.disableSpeakerphone) let onImage = UIImage(asset: Asset.enableSpeakerphone) @@ -242,16 +242,16 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var isOutputToSpeaker: Observable = { + lazy var isOutputToSpeaker: Observable = { [unowned self] in return self.audioService.isOutputToSpeaker.asObservable() }() - lazy var speakerSwitchable: Observable = { + lazy var speakerSwitchable: Observable = { [unowned self] in return self.audioService.isHeadsetConnected.asObservable() .map { value in return !value } }() - lazy var audioMuted: Observable = { + lazy var audioMuted: Observable = { [unowned self] in return self.callService.currentCall.filter({ [weak self] call in call.callId == self?.call?.callId && call.state == .current @@ -260,7 +260,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var pauseCallButtonState: Observable = { + lazy var pauseCallButtonState: Observable = { [unowned self] in let unpauseCall = UIImage(asset: Asset.unpauseCall) let pauseCall = UIImage(asset: Asset.pauseCall) @@ -272,7 +272,7 @@ class CallViewModel: Stateable, ViewModel { }) }() - lazy var callPaused: Observable = { + lazy var callPaused: Observable = { [unowned self] in return self.callService.currentCall.filter({ [weak self] call in call.callId == self?.call?.callId && (call.state == .hold || @@ -301,10 +301,9 @@ class CallViewModel: Stateable, ViewModel { return call.callId == self?.call?.callId }).map({ call in return call.state == .current - }).subscribe(onNext: { _ in - self.videoService.setCameraOrientation(orientation: UIDevice.current.orientation) + }).subscribe(onNext: { [weak self] _ in + self?.videoService.setCameraOrientation(orientation: UIDevice.current.orientation) }).disposed(by: self.disposeBag) - } static func formattedDurationFrom(interval: Int) -> String { -- GitLab