iOS MVVM Tutorial: Refactoring from MVC | raywenderlich.com

Can someone explain/elaborate a bit on weak self? in the viewdidload of WeatherViewController why would self be weak in geocoder.geocode(addressString: defaultAddress) { [weak self] locations in ???

Can we use boxing or any other tools for binding INPUT of View Model to UI element. For example, View Model defaultAddress with Textfield.text instead of button?

@happiehappie Happy to answer your question. [weak self] is necessary to avoid a memory leak caused by a retain cycle. The WeatherViewModel has a strong reference to the geocoder. The closure passed to geocoder.geocode contains a reference to the WeatherViewModel because it accesses various properties of the WeatherViewModel. If both references are strong, which is the default, then that memory would never become dereferenced and would never get released. So one of them has to be a weak reference, and that is the child object, or the geocoder. You can find many articles if you search for “Swift memory management retain cycles”.

@tatianakornilova Could you elaborate on your question? I need to better understand what you are asking. Perhaps a snippet of code would help me understand you question so that I can answer it.

I asked, if I have View Model like this:

import Combine
import Foundation

final class TempViewModel: ObservableObject {
    // input
    @Published var city: String = "London"
    // output
    @Published var temp = " "
    
    private var validString:  AnyPublisher<String, Never> {
          $city
               .debounce(for: 0.3, scheduler: RunLoop.main)
               .removeDuplicates()
               .eraseToAnyPublisher()
       }
    
    init() { validString
            .flatMap { (city:String) -> AnyPublisher <String, Never> in
                WeatherAPI.shared.fetchTemperature(for: city)
        }
        .receive(on: RunLoop.main)
        .assign(to: \.temp , on: self)
        .store(in: &self.cancellableSet)
   }
    
    private var cancellableSet: Set<AnyCancellable> = []
}

How can I bind in ViewController bind both @Published properties ( Input and output)?

And I found answer here.

import UIKit
import Combine

class ViewController: UIViewController{
     // MARK: - UI
    @IBOutlet weak var cityTextField: UITextField!{
        didSet {
            cityTextField.isEnabled = true
            cityTextField.becomeFirstResponder()
        }
    }
    
    @IBOutlet weak var temperatureLabel: UILabel!
     // MARK: - View Model
    private let viewModel = TempViewModel()
     
     // MARK: - Life Cycle View Controller
    override func viewDidLoad() {
        super.viewDidLoad()
        cityTextField.text = viewModel.city
        binding()
    }
    
     // MARK: - Combine
    func binding() {
        cityTextField.textPublisher
           .assign(to: \.city, on: viewModel)
           .store(in: &cancellable)
       
        viewModel.$temp
           .sink(receiveValue: {[weak self] temp in
                      self?.temperatureLabel.text = temp})
           .store(in: &cancellable)
    }

     private var cancellable = Set<AnyCancellable>()
}

First we make TextField Publisher:

import UIKit
import Combine

extension UITextField {
    var textPublisher: AnyPublisher<String, Never> {
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: self)
            .compactMap { $0.object as? UITextField } // receiving notifications with objects which are instances of UITextFields
            .map { $0.text ?? "" } // mapping UITextField to extract text
            .eraseToAnyPublisher()
    }
}

And use it in viewDidLoad:

  // MARK: - Life Cycle View Controller
    override func viewDidLoad() {
        super.viewDidLoad()
        cityTextField.text = viewModel.city
        binding()
    }
    
     // MARK: - Combine
    func binding() {
        cityTextField.textPublisher
           .assign(to: \.city, on: viewModel)
           .store(in: &cancellable)
       
        viewModel.$temp
           .sink(receiveValue: {[weak self] temp in
                      self?.temperatureLabel.text = temp})
           .store(in: &cancellable)
    }

     private var cancellable = Set<AnyCancellable>()

I asked you about binding INPUT like UITextField to View Model.

@tatianakornilova I’m glad you were able to find an answer. Thank you for providing more detail. Others will surely benefit.

Hello. Thank you for visiting

From today the Dark Sky API isn’t available anymore to registration. Do you plan to update the tutorial ? Thanks for all your work !

I just found out. I will get an update together ASAP.

@tonioverzeworld I’ve edited the article to use weatherbit.io. Hope you’ll give it a try.

Wow!
I got an email from Dark Sky, Apple seems to have acquired it. Thought of updating that here, and seems the tutorial has already been updated to use a different API. Neat!

Thanks, I’ll do it this week-end. Thanks for this quick update !

I have a similar question:
when the submitAction is declared in the IBAction promptForLocation in the WeatherViewController, the closure to the UIAlertAction has an unowned reference to the alert and a weak reference to the WeatherViewController.

let submitAction = UIAlertAction(
      title: "Submit",
      style: .default) { [unowned alert, weak self] _ in

according to the swift docs

Use a weak reference when the other instance has a shorter lifetime—that is, when the other instance can be deallocated first. …
In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime.

Why aren’t both of these references declared as unowned?
The alert outlives submitAction but doesn’t the WeatherViewController as well?

Rather than focus on the lifecycle durations, let’s look at what unowned and weak really mean. Both will not cause the reference to be retained. Using weak will treat the reference as optional and make sure that the reference is not nil before attempting to use it. Using unowned will not check for nil and can cause a crash. So neither is right or wrong, just safer (weak) and more dangerous (unowned). When you use unowned, you must be sure that the reference will always be populated when invoked.

Here is a discussion about weak vs unowned at SwiftLee:

The only benefit of using unowned over weak is that you don’t have to deal with optionals. Therefore, using weak is always safer in those scenarios.

1 Like

To gain a better understanding of the Box class, I abstracted it into an Xcode playground and started testing it with String types. The one thing I’m having trouble understanding is how does changing the value on boxedString trigger my print function to be called? I understand the first call to print function, but the second call confuses me. I also observed the didSet property calling listener, but I’m still not seeing how the magic happens. Here is the code:

final class Box<T> {

typealias Listener = (T) -> Void
private var listener: Listener?

var value: T {
    didSet {
        listener?(value)
    }
}

init(_ value: T) {
    self.value = value
}

func bind(listener: Listener?) {
    self.listener = listener
    listener?(value)
  }
} 

// Testing Box
let boxedString = Box("Chris")

boxedString.bind { name in
    print("Value changed: \(name)")
}

boxedString.value = "Chuck"

When you call bind on the boxedString, you provide a function that calls print and boxedString retains a copy of that function in the listener property then immediately calls that listener.

{ name in
    print("Value changed: \(name)")
}

is your listener function.

Then when you change the value of the boxedString, the didSet method is immediately invoked, causing the listener function to be invoked again. For as long as the boxedString stays in memory, it will keep a copy of the listener that you provided and invoke it each time the value changes. That is the magic of using didSet in Swift.

Hope that answers your question.

Chuck, I had been pounding my head at this for 4 hours yesterday. You helped me understand the whole data binding concept in its entirety. Thank you!

1 Like

Great tutorial loved it thank you

While going through it I noticed the before app has an error asking for where the “clear-sky” image is. You go into the storyboard and it indeed is missing.

Also there’s a typo in the tutorial: “You must import the app for texting. Immediately below import XCTest , add the following”

Good finds, @kaylanx. As you can see in the discussion above, had to change weather APIs from DarkSky to Weatherbit after the tutorial was released. I guess I missed a few things. I’ll clean then up now. Thanks for bringing them to my attention.

1 Like

This is one of the best tutorials I have ever seen! Thank you so very much for creating a tutorial that not only covers the pertinent content so thoroughly but also does it in a way that is very easy to understand. Very awesome. :+1: