Calling instance method during initialization in Swift
Asked Answered
S

8

17

I am new to Swift and would like to initialize an object's member variable using an instance method like this:

class MyClass {
  var x: String
  var y: String

  func createY() -> String {
    self.y = self.x + "_test" // this computation could be much more complex
  }

  init(x: String) {
    self.x = x
    self.y = self.createY()
  }     
}

Basically, instead of inlining all the initialization code in init method, I want to extract the initialization code of y to a dedicated method createY and call this instance method createY in init. However, Swift compiler (Swift 1.2 compiler in Xcode 6.3 beta) complains:

use of 'self' in method call 'xxx' before super.init initialize self

Here 'xxx' is the name of the instance method (createY).

I can understand what Swift compiler is complaining and the potential problem it wants to address. However, I have no idea how to fix it. What should be the correct way in Swift to call other instance method of initialization code in init?

Currently, I use the following trick as work around but I don't think this is the idiomatic solution to this problem (and this workaround requires y to be declared using var instead of let which makes me feel uneasy too):

init(x: String) {
  self.x = x
  super.init()
  self.y = createY()
} 

Any comment is appreciated. Thanks.

Slantwise answered 21/2, 2015 at 14:21 Comment(0)
D
11

Convert createY() to a global or class function that accepts x as an argument and returns a y.

func createY(x: String) -> String {
    return x + "_test" // this computation could be much more complex
}

Then just call it normally from your init.

class MyClass {
  let x: String
  let y: String

  init(x: String) {
    self.x = x
    self.y = createY(x)
  }     
}
Depose answered 21/2, 2015 at 14:29 Comment(5)
Thanks for the reply, it seems I still have to declare 'y' using 'var' instead of 'let' even if my 'y' variable is immutable?Slantwise
@Slantwise I added the code for the class itself to show how I meant createY() to be used. let should be fine.Depose
This does work but feels so wrong. Creating a static method just for the sake of manipulating a class variable just doesn't look right.Coelacanth
Whether it's right or wrong is up to the developer's discretion. Besides, you don't have to "create" a static method per se, they may well be existing global functions.Depose
... or extensionsDepose
B
6

In Swift 3, I've been using this pattern,

class MyClass {
  var x: String?
  private(set) lazy var y: String? = self.createY()

  init(x: String){ self.x = x }

  private func createY() -> String?
  {
    return "\(x ?? "nil") test"
  }
}

The secret sauce here is the use of private(set) lazy. This way, you can label your property a var. And lazy will delay initialization until after your init function has completed. Using private(set) only allows the functions inside this class to modify that property, including the lazy keyword, but not let public interfaces change it. Of course, if you want your interface to change your property, then you can also mark it internal (the default) or public. But you need to leave it marked a lazy var

Backdrop answered 2/3, 2017 at 4:4 Comment(0)
C
1

As answered here, create a class function. I've added the full code.

class MyClass {
    var x: String
    var y: String

    class func createY(x: String) -> String {
         return x + "_test" // this computation could be much more complex
    }

    init(x: String) {
        self.x = x
        self.y = MyClass.createY(x)
    }     
}
Catalepsy answered 12/4, 2015 at 18:16 Comment(0)
M
1

I think the Swift way to do this is with Computed Properties (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html)

EDIT

Instead of calling a function to modify a property on set/get, you can use computed properties:

class MyClass {

    var x: String?
    var y: String? {
        get {
            return "\(x!)_test"
        }
    }

    init(x: String!){
       self.x = x
    }
}

let myClass = MyClass(x: "string") 
print(myClass.y!) #=> "string_test"
Maidel answered 2/12, 2015 at 20:10 Comment(3)
You could improve your answer by providing an actual example rather than only linking to an external page (which might vanish).Allegraallegretto
sorry, I added an example.Maidel
If I understand correctly, computed properties will do the computation every time I access this computed property, is it correct? If the computation is expensive and only needed once during initialization, how am I supposed to do this?Slantwise
C
1

You can use nested functions, they have access to all the local variables of the parent function, or in this case initialiser, they are only available in the parent function or initialiser, and they don't require the parent struct or class initialiser to be complete because, they are not accessing any instance variable, only what's available locally in the parent class

class MyClass {
  var x: String
  var y: String


  init(x: String) {
    func createY() -> String {
        return x + "_test" // this computation could be much more complex
    }

    self.x = x
    self.y = createY()
  }
}
Conviction answered 25/1, 2023 at 9:15 Comment(0)
H
0

You can use in this approach

class MyClass: NSObject {
        
        
            let x: String
            var y: String
            
            init(x: String) {
        
                self.x = x
                self.y = self.x + "_test"
                print(self.x)
                print(self.y)
            }
    }
Haroun answered 2/3, 2017 at 4:49 Comment(1)
Thanks for the answer, but as I mentioned above, I don't want to inline "all the initialization code in init method.Slantwise
W
0
class MyClass {
        let x: String
        lazy var y : String =  {
                return x + "_test"
            }()
        init(x: String) {
            self.x = x
        }
}
Weird answered 30/5, 2017 at 14:37 Comment(1)
Not really helpful without any further explanations ;-(Fourfold
E
0

see Two-Phase Initialization, and the example : https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID52 at Unowned References and Implicitly Unwrapped Optional Properties

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }  
}
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

To set up the interdependency between the two classes, the initializer for City takes a Country instance, and stores this instance in its country property.

The initializer for City is called from within the initializer for Country. However, the initializer for Country cannot pass self to the City initializer until a new Country instance is fully initialized, as described in Two-Phase Initialization.

To cope with this requirement, you declare the capitalCity property of Country as an implicitly unwrapped optional property, indicated by the exclamation mark at the end of its type annotation (City!). This means that the capitalCity property has a default value of nil, like any other optional, but can be accessed without the need to unwrap its value as described in Implicitly Unwrapped Optionals.

Because capitalCity has a default nil value, a new Country instance is considered fully initialized as soon as the Country instance sets its name property within its initializer. This means that the Country initializer can start to reference and pass around the implicit self property as soon as the name property is set. The Country initializer can therefore pass self as one of the parameters for the City initializer when the Country initializer is setting its own capitalCity property.

so add a exclamation mark of y's type -> var y: String!

Eye answered 22/10, 2018 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.