In Swift, didSet doesn’t fire when invoked from init() - swift

I’ve got a car and a driver. They mutually reference each other. In the car’s init() I create a driver and assign it to the driver member. The driver member has a didSet method which is supposed to set the driver’s car, thus mutually link them to each other.
class GmDriver {
var car: GmCar! = nil
}
class GmCar {
var driver: GmDriver {
didSet {
driver.car = self
}
}
init() {
driver = GmDriver()
}
}
let myCar = GmCar()
println(myCar.driver.car) // nil
However, the didSet never fires. Why?

Apple Documentation:
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.

init() {
defer {
driver = GmDriver()
}
}

Related

Swift variable observers not called before super.init called

Okay so I was reading up on how willSet/didSet are used in swift and I came across a note on apples swift docs that just doesn't make any sense to me and I hope someone can explain. Here's the note:
The willSet and didSet observers of superclass properties are called
when a property is set in a subclass initializer, after the superclass
initializer has been called. They are not called while a class is
setting its own properties, before the superclass initializer has been
called.
From: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
What confuses me is that they point out that the observers on superclass A properties in a subclass B aren't called before the super.init call by B to A.
class A {
var p: Bool
init() {
p = false
}
}
class B: A {
override var p: Bool {
didSet {
print("didSet p")
}
}
override init() {
p = true // Compiler error
super.init()
}
}
However the property is never even accessible in that time from either A nor B, so who's gonna call the observers anyway? Attempting to read/write the property will even result in a compiler error so it's never even possible to do it by mistake in Swift. Am I missing something or is this just a misleading note that points out the wrong thing?
They are talking about following scenario:
class A {
var p: Bool {
didSet {
print(">>> didSet p to \(p)")
}
}
init() {
p = false // here didSet won't be called
}
}
class B: A {
override init() {
// here you could set B's properties, but not those inherited, only after super.init()
super.init()
p = true // here didSet will be called
}
}
B()
It will print following:
>>> didSet p to true
While to you it might seems natural, the documentation has to explicitly document this behavior.

Variable with getter/setter cannot have initial value, on overridden stored property

When creating a stored property with Observing Accessors, I can specify a default value. However, when overriding a stored property and its Accessors I cannot specify a default value.
Variable with getter/setter cannot have initial value.
Which seems very strange, as this is NOT a computed property with a getter/setter, but a set of Observing Accessors on a stored property!
class FirstViewController: UIViewController {
internal var test: Float = 32.0 {
willSet {
}
didSet {
}
}
The first view controller compiles fine, with a stored property initialized to 32.0
class SecondViewController: FirstViewController {
override var test: Float = 64.0 {
willSet {
}
didSet {
}
}
The second view controller does not compile, as the 'computed property' is being given an initial value
In swift you are able to override properties only with computed properties (which are not able to have default values) with same type. In your case, if you wish override test property in SecondViewController you need write something like this:
override var test: Float {
get {
return super.test
}
set {
super.test = newValue
}
}
And there is no way to override didSet/willSet observers directly; you may do this by write other methods invoked in observers and just override them:
FirstViewController:
internal var test: Float = 32.0 {
willSet {
test_WillSet(newValue)
}
didSet {
test_DidSet(oldValue)
}
}
func test_WillSet(newValue: Float) {}
func test_DidSet(oldValue: Float) {}
SecondViewController:
override func test_WillSet(newValue: Float) {
super.test_WillSet(newValue)
}
override func test_DidSet(oldValue: Float) {
super.test_DidSet(oldValue)
}
I know that this has been asked a long time ago but I came up with a slightly different solution and it works exactly as you wanted. You have a property in the first ViewController then in the inherited one you override it and have observers set on it in the form of didSet.
So in the FirstViewController you have a property like in the example below:
var myNumber: Double = 20.00
Then in the SecondViewController which inherits from FirstViewController you override it as follows:
override var myNumber: Double {
didSet {
//Here you can update UI or whatever you want to do once the property changes
//Print its value
print("Value of myNumber is : \(myNumber)")
}
I hope this will help someone with the above issue as this is a nice and easy way to solve the problem mentioned above.

Call external function using WatchKit force touch MenuItem

I need to implement a WatchKit force-touch MenuItem to call a saveWorkout() method that is located in a separate class that does not subclass WKInterfaceController.
I realize that every class needs at least one designated initializer. I am guessing this is the key?
Btw, my "saveSession() reached" print statement logs to the console when using the sim but not when I use a device. All other print statements log to the console even when using the device. A bit odd.
My attempts at initialization throw various errors such as:
1.fatal error: use of unimplemented initializer 'init()' for class 'DashboardController'
2.Missing argument for parameter 'context' in call
Dashboard.swift
class DashboardController: WKInterfaceController {
#IBOutlet var timerLabel: WKInterfaceTimer!
#IBOutlet weak var milesLabel: WKInterfaceLabel!
// var wSM: WorkoutSessionManager
//init(wSM: WorkoutSessionManager) {
// self.wSM = wSM
// super.init()
// }
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))
}
override func willActivate() {
super.willActivate()
print("Dashboard controller reached")
}
func saveSession() {
//wSM.saveWorkout()
print("saveSession() reached")
}
WorkoutSessionManager.swift
class WorkoutSessionContext {
let healthStore: HKHealthStore
let activityType: HKWorkoutActivityType
let locationType: HKWorkoutSessionLocationType
init(healthStore: HKHealthStore, activityType: HKWorkoutActivityType = .Other, locationType: HKWorkoutSessionLocationType = .Unknown) {
self.healthStore = healthStore
self.activityType = activityType
self.locationType = locationType
}
}
protocol WorkoutSessionManagerDelegate: class {
// ... protocol methods
}
class WorkoutSessionManager: NSObject, HKWorkoutSessionDelegate {
let healthStore: HKHealthStore
let workoutSession: HKWorkoutSession
init(context: WorkoutSessionContext) {
self.healthStore = context.healthStore
self.workoutSession = HKWorkoutSession(activityType: context.activityType, locationType: context.locationType)
self.currentActiveEnergyQuantity = HKQuantity(unit: self.energyUnit, doubleValue: 0.0)
self.currentDistanceQuantity = HKQuantity(unit: self.distanceUnit, doubleValue: 0.0)
super.init()
self.workoutSession.delegate = self
}
func saveWorkout() {
guard let startDate = self.workoutStartDate, endDate = self.workoutEndDate else {return}
// ...code...
The fatal error is (or was) caused by this line:
let wSM = WorkoutSessionManager()
That line creates a new instance of WorkoutSessionManager and calls init() on it.
Swift provides a default initializer called init() for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. But WorkoutSessionManager does not provide default values for the healthStore and workoutSession properties (and those properties are not optionals), and it provides its own initializer named init(context:), so it has no default initializer.
You need to either create your instance of WorkoutSessionManager using the designated initializer init(context:) (passing an appropriate instance of WorkoutSessionContext) or provide a default initializer for WorkoutSessionManager named init().
The precise manner in which you should do the former depends on the implementation of the rest of your app and the presentation of your DashboardController. I assume you are trying to recreate the "Fit" app shown in WWDC 2015 Session 203.
In that demonstration, the initial controller is an instance of ActivityInterfaceController, and that controller is responsible for presenting the next interface (via segues created in the storyboard). You can see the following code in the ActivityInterfaceController class:
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
let activityType: HKWorkoutActivityType
switch segueIdentifier {
case "Running":
activityType = .Running
case "Walking":
activityType = .Walking
case "Cycling":
activityType = .Cycling
default:
activityType = .Other
}
return WorkoutSessionContext(healthStore: self.healthStore, activityType: activityType)
}
The function above creates and returns a new instance of WorkoutSessionContext using an instance of HKHealthStore held by the initial controller. The context returned by that function is passed to the destination interface controller for the relevant segue through awakeWithContext.
For transitions in code, you can pass a context instance using equivalent functions such as pushControllerWithName(context:) which also lead to awakeWithContext.
If your initial controller is similar to the above, you can access the passed context in awakeWithContext in your DashboardController class and use it to configure a new instance of WorkoutSessionManager:
class DashboardController: WKInterfaceController
{
// ...
var wSM: WorkoutSessionManager?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if context is WorkoutSessionContext {
wSM = WorkoutSessionManager(context: context as! WorkoutSessionContext)
}
addMenuItemWithItemIcon(.Accept, title: "Save", action: #selector(DashboardController.saveSession))
}
// ...
}
Creating an instance of WorkoutSessionManager in that way avoids calling the (non-existent) init() initializer and permits reuse of the HKHealthStore instance. Whether that approach is open to you depends on the rest of your code and the way you are presenting your DashboardController.
Note that you should avoid creating multiple instances of WorkoutSessionManager. Use a singleton to provide a single instance of WorkoutSessionManager that is shared across your extension.

Reference `self` in Swift instance member declaration

I have a struct set up that accepts a reference as a single initialization parameter:
internal struct NodeState: Equatable {
weak var node: Node! = nil
// ...
init(node: Node) {
self.node = node
}
}
I want to instantiate a NodeState as a member of the Node class, passing self in to set that weak reference:
public class Node: NSObject {
internal var state = NodeState(node: self)
// ...
}
…but I am getting this weird compile error:
Cannot convert value of type 'NSObject -> () -> Node' to expected argument type 'Node'
Am I not allowed to reference self in a member declaration in Swift?
In general you can't reference self in a member class declaration but you can if you make the property lazy and initialize it with a closure. Changing your Node class to something like this should work:
public class Node: NSObject {
internal lazy var staticState: NodeState = { NodeState(node: self) }()
}
It works because the lazy property isn't initialized until after self is initialized. self has to be fully initialized before it can be used.
Am I not allowed to reference self in a member declaration in Swift?
Sort of. You can't reference self (e.g. calling methods, passing self as a parameter) until the object is fully initialized.
You could use a lazy var in this case, which would work since it can't be accessed until the object is initialized. Here's an example:
public class Node: NSObject {
internal lazy var staticState: NodeState = {
return NodeState(node: self)
}()
}
Reference self in a closure?
public class Node: NSObject {
lazy var staticState: () -> (NodeState) = {
[unowned self] in
return NodeState(node: self)
}
}
I'm explicitly decorating self as unowned in the closure to prevent a retain cycle.

swift setter causing exc_bad_access

I have a simple class below
import Foundation
public class UsefulClass: NSObject{
var test:NSNumber{
get{return self.test}
set{
println(newValue)
self.test = newValue
}
}
override init() {
super.init()
self.test = 5;
}
}
and I'm initializing it here
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var testClass = UsefulClass()
}
}
But it results in xcode printing out 200 5s and then crashing due to EXC_BAD_ACCESS code = 2. Why does this happen?
#vadian has provided a solution in his answer, which should fix your problem. Let me just explain what's happening.
You have created a computed property, i.e. a property which is not backed by a variable, instead both the getter and the setter do some processing, usually on another stored property, in order to respectively return a value and set a new value.
This is your computed property:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Look at the getter implementation:
return self.test
What does it do? It reads the test property of the current instance, and returns it. Which is the test property? It's this one:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Yes, it's the same property. What your getter does is to recursively and indefinitely calling itself, until a crash happen at runtime.
The same rule applies to the setter:
self.test = newValue
it keeps invoking itself, until the app crashes.
Swift variables are synthesized properties by default.
In the most cases this is sufficient (it's recommended to prefer Swift types)
var test: Int
override init() {
super.init()
test = 5
}
If you need to do something after a variable is set, use
var test: Int {
didSet{
println("\(oldValue) - \(newValue)")
}
}
your code sets the variable permanently by calling the setter which calls the setter which …
It's an infinite loop; your setter is recursively calling itself.
var test: NSNumber {
set {
test = newValue
}
}
This compiles fine, and an Objective-C programmer might expect no loop due to instead setting a "backing ivar" such as _test rather than re-calling the setter method.
But property-backing instance variable _ivars do not exist in Swift for computed properties unless you create them yourself.