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.
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.
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!
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.
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.