MVP - Destroy Presenter object from View Controller on dismiss in iOS - swift

I'm following Example Here to apply MVP pattern in swift.
When I dismiss my View Controller, the presenter is not destroyed and View Controller also remains in memory.
When I try to make the presenter object 'weak', the code break at this line.
presenter.login(email: "email", password: "password")
How can I properly declare and destroy the presenter instance. Thanks

In your code in Presenter you create request to network and capture self in closure without using weak or unowned reference. Because of this there is a retain cycle. You can read more about retain cycles here.
Updated code:
func login(email: String, password: String)
{
self.view.showProgress()
FoodAPI.api.login(email: email, password: password) { [weak self] (msg, user) in
guard let `self` = self else {
return
}
DispatchQueue.main.async {
self.view.hideProgress()
if let user = user
{
AppDelegate.shared.user = user
UserDefaultsHelper.saveUser(user: user)
self.view.openMenu() //this line will dismiss the VC and presents next one.
}else
{
self.view.showAlert(message: msg)
}
}
}
}
If you want to see advanced usage of MVP pattern you can checkout my open project here and feel free to ask me anything.

Related

Why does this NOT leak memory? RxFeedback

class ViewModel {
...
func state(with bindings: #escaping (Driver<State>) -> Signal<Event>) -> Driver<State> {
Driver.system(
initialState: .initial,
reduce: State.reduce(state:event:),
feedback:
bindings,
react(request: { $0.startLoading }, effects: { _ in
self.fetchFavoriteRepositoriesUseCase.execute()
.asObservable()
.observe(on: self.scheduler)
.map(self.repositoriesToRepositoryViewModelsMapper.map(input:))
.map { repositories in .loaded(repositories) }
.asSignal { error in
.just(.failed(error.localizedDescription))
}
}))
}
...
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let initialTrigger = BehaviorRelay<Void>(value: ())
let trigger = Observable.merge(initialTrigger.asObservable(), refreshRelay.asObservable())
let uiBindings: (Driver<FavoriteRepositoriesViewModel.State>) -> Signal<FavoriteRepositoriesViewModel.Event> = bind(self) { me, state in
let subscriptions = [
state.drive(onNext: { state in
switch state {
case .initial:
print("Initial")
case .loading:
print("Loading")
case .failed:
print("Failed")
case .loaded:
print("Loaded")
}
})
]
let events: [Signal<FavoriteRepositoriesViewModel.Event>] = [
trigger.map {
.load
}
.asSignal(onErrorSignalWith: .empty())
]
return Bindings(subscriptions: subscriptions, events: events)
}
viewModel.state(with: uiBindings)
.drive()
.disposed(by: disposeBag)
}
}
I'm trying to grasp my head around why the react method from RxFeedback does NOT create a memory leak in this case. It has the effects closure as one of its arguments which is an #escaping closure and I'm not weakifying it, but capturing self strongly in it to call the use case. I assume it has nothing to do with RxFeedback but my knowledge of ARC and memory management.
To test the deallocation of the ViewController I'm just popping it from a NavigationController.
I would appreciate a detailed explanation on why this code is NOT creating a retain cycle. Thanks in advance!
There is no retain cycle. However, your view controller is holding several references (both direct and indirect) to your view model.
So for example, your view controller has a viewModel property. It's also holding a disposeBag which is retaining a disposable, which retains an Observable that retains the closure in your view model, which retains the view model.
The only time the strong capture of self is an issue is if the disposable is also being retained by the same object that is being captured. In this case, the view model is "self" but the view controller is the one retaining the disposable (through its dispose bag.)

How to observe login state?

I want View Controllers to be aware of every change in login status. Do I have to make a single tone and subscribe?
Singleton.swift
class Singleton {
static let shared = Singleton()
let isLogin: BehaviorRelay<Bool>
private init() {
isLogin = BehaviorRelay<Bool>(value: false)
}
}
SomeViewController
class SomeVc: UIViewController {
Sigleton.shared.isLogin.subscribe(.....)
}
No you don't need a Singleton...
Here's code I use in actual production. This code is in my application(_:didFinishLaunchingWithOptions:) method.
_ = UserDefaults.standard.rx.observe(String.self, "token")
.map { $0 ?? "" }
.filter { $0.isEmpty }
.bind(onNext: presentScene(animated: true) { _ in
LoginViewController.scene { $0.connect() }
})
When the user logs in, I save a token in UserDefaults, when the user logs out, I remove it. The above code will present my LoginViewController when the user logs out.
If any other view controller needs to track the login state of the user, they can also subscribe to the token observable.
The presentScene(animated:_:) function and scene(_:) method both come from my CLE Library

Weakly captured self won't let the view model deallocate until the Task finishes

I am trying to learn ARC and I'm having a hard time with a weakly captured self. My project is using MVVM with SwiftUI. I'm presenting a sheet (AuthenticationLoginView) that has a #StateObject var viewModel = AuthenticationLoginViewModel() property. On dismiss of the presented sheet, I expect that the viewModel will have it's deinit called and so it does until I run an asynchronous function within a Task block.
class AuthenticationLoginViewModel: ObservableObject {
#Published var isLoggingIn: Bool = false
private var authenticationService: AuthenticationService
private var cancellables: Set<AnyCancellable> = Set()
private var onLoginTask: Task<Void, Never>?
init(authenticationService: AuthenticationService) {
self.authenticationService = authenticationService
}
deinit {
onLoginTask?.cancel()
LoggerService.log("deallocated")
}
public func onLogin() {
guard !isLoggingIn else { return }
isLoggingIn = true
onLoginTask = Task { [weak self] in
await self?.login()
}
}
private func login() async {
LoggerService.log("Logging in...")
sleep(2)
//
// self is still allocated here <<<---- ???
//
let authResponse = try? await self.authenticationService.authenticate(username: username, password: password)
LoggerService.log(self.isLoggingIn) // <<--- prints `true`
handleLoginResponse(authResponse: authResponse)
}
}
So I have my two cases here:
Case #1
I present the sheet.
I dismiss the sheet.
The deinit function is getting called (app logs: "deallocated")
Case #2
I present the sheet.
I press the login button so the onLogin function is getting called.
I dismiss the sheet before the sleep(2) ends.
---- I EXPECT the "deallocated" message to be printed from deinit and the logging at LoggerService.log(self.isLoggingIn) to print nil and the self.authenticationService.authenticate(... to never be called as self doesn't exist anymore.
Not expected but happening: the app prints "Logging in", sleeps for 2 seconds, calls the service, prints true, and then deallocates the view model (the view was dismissed 2 seconds ago)
What am I doing wrong?
I'm still learning and I'm pretty much unsure if this is normal or I miss something. Anyway, I expect the view model to be deallocated as the view referencing it was dismissed.
At the time you call onLogin the reference to self is valid and so the Task commences.
After that, the reference to self in login keeps self alive. The Task has a life of its own, and you did not cancel it.
Moreover the use of sleep is wrong, as it is not cancellable in any case, so neither is your Task. Use Task.sleep.

Facebook Login with MVVM pattern

I'm trying to learn MVVM and it's kind of hard to me to migrate from MVC to MVVM
I'm working with Facebook Login. Here's what I'm setup my code :
Inside ViewController :
fileprivate func facebookLoginAction(){
self.viewModel.performFacebookLogin(rootVC: self)
}
Inside ViewModel :
func performFacebookLogin(rootVC: UIViewController) {
let fbLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["email"], from: rootVC) { [weak self] (result, err) in
if let err = err {
print(err.localizedDescription)
return
}
guard let result = result else {return}
if result.isCancelled {
return
}
if (result.grantedPermissions.contains("email")) {
self?.getFacebookUserData()
}
}
}
It's works but I'm not sure that I'm doing right MVVM pattern because I'm passing ViewController to ViewModel.
Can you give me some ideas or rules of MVVM
It looks like you've done a pretty good job of implementing MVVM here! The general rule of MVVM is that if you were to strip out all of the "visuals" from your view controller, it would look identical to your view model (ie: the view model holds all of the information). Unfortunately the API here makes it difficult to avoid passing the view controller to its model directly. One possible option is to have the view model implement a view model delegate protocol...
protocol viewModelDelegate {
func getViewController() -> viewController
}
The view Controller should implement this protocol and return self. Then, you can get your view controller by calling delegate?.getViewController().
Honestly, this kind of abuses the delegate pattern, and technically still passes the view controller TYPE to the view model haha, so this might not be the best option for you!

Swift: possible removeObserver cyclic reference in addObserverForName's usingBlock

I'm toying around with a small Swift application. In it the user can create as many MainWindow instances as he wants by clicking on "New" in the application's menu.
The application delegate holds an array typed to MainWindowController. The windows are watched for the NSWindowWillCloseNotification in order to remove the controller from the MainWindowController array.
The question now is, if the removal of the observer is done correctly – I fear there might be a cyclic reference to observer, but I don't know how to test for that:
class ApplicationDelegate: NSObject, NSApplicationDelegate {
private let notificationCenter = NSNotificationCenter.defaultCenter()
private var mainWindowControllers = [MainWindowController]()
func newWindow() {
let mainWindowController = MainWindowController()
let window = mainWindowController.window
var observer: AnyObject?
observer = notificationCenter.addObserverForName(NSWindowWillCloseNotification,
object: window,
queue: nil) { (_) in
// remove the controller from self.mainWindowControllers
self.mainWindowControllers = self.mainWindowControllers.filter() {
$0 !== mainWindowController
}
// remove the observer for this mainWindowController.window
self.notificationCenter.removeObserver(observer!)
}
mainWindowControllers.append(mainWindowController)
}
}
In general you should always specify that self is unowned in blocks registered with NSNotificationCenter. This will keep the block from having a strong reference to self. You would do this with a capture list in front of the parameter list of your closure:
{ [unowned self] (_) in
// Block content
}