5

I have a class called Letter

class Letter
{    
    init() {}
}

And I have an extension for this class:

extension Letter
{
    convenience init(file_path:String) {
       self = Letter.loadFromFile(file_path)
    }

    class func loadFromFile(file_path:String)->Letter {...}
}

I need to create and init with path to file and when i call Letter(file_path) I need a new object that returned by a func loadFromFile. How to assign in an init method or to return a new object?

It gives the error:

Cannot assign to value: 'self' is immutable

pkamb
  • 33,281
  • 23
  • 160
  • 191
Olexiy Pyvovarov
  • 870
  • 2
  • 17
  • 32
  • Why do you have an extension? Just put your convenience initializer into the class. That is where it belongs. – Mundi Oct 18 '14 at 13:53
  • 1
    Just the opposite, IMHO. Keep the convenience initializer in the extension, but remove the init(file_path:String) initializer from the main class. Users of the Letter class can just call Letter.loadFromFile() directly. – Daniel T. Oct 18 '14 at 13:55
  • 1
    it also says that i can't assign to self, Users of the Letter class can just call Letter.loadFromFile() directly -- Yeah but I want to make it more easy to understand) – Olexiy Pyvovarov Oct 18 '14 at 14:05
  • This proposed API has a bit of an Objective-C _je ne sais quoi_. I'd suggest eliminating `loadFromFile` altogether. – Rob Oct 18 '14 at 15:36
  • related: https://stackoverflow.com/questions/54262141/why-does-swift-disallow-assignment-to-self-in-class-init-but-not-in-protocol-in – pkamb Sep 22 '21 at 17:44

3 Answers3

2

Class functions that return instances of that class seems to be an anti-pattern in Swift. You'll notice that the "with" Objective-C class methods like [NSString stringWithString:@"some other string"] made the transition as "with"-less convenience initializers: NSString(string: "some other string").

Furthermore, you'll need to delegate to a designated initializer from within a convenience initializer.

Also, since you're 1) defining the original class and 2) don't need the convenience initializer scoped differently than the designated initializer, I don't see any reason to place it in an extension.

Putting those together:

class Letter {
    init() { … }

    convenience init(filePath: String) {
        self.init()
        loadFromFile(filePath)
    }

    func loadFromFile(filePath: String) { … }
}

let letter1 = Letter()
letter1.loadFromFile("path1")

let letter2 = Letter(filePath: "path2")

In summary, the analogy for assigning to self in Swift is calling an initializer.

Let me know if this works for you!

Andrew
  • 2,438
  • 1
  • 19
  • 19
  • The Swift book talks about moving convenience initializers into an extension so you don't have to implement an empty init() method. I think the extension is a good idea. – Daniel T. Oct 18 '14 at 15:44
  • Ah! I didn't realize his `init` really was empty. But, what you said doesn't seem to be true? [Here's a gist](https://gist.github.com/aclissold/c83f6740aecb6f866614), paste it into a Playground if you want – Andrew Oct 18 '14 at 15:50
  • Ahhhh. "If you want your custom **value** type to be initializable with the default initializer and memberwise initializer, and also with your own custom initializers, write your custom initializers in an extension rather than as part of the value type’s original implementation." I think they're saying convenience inits are only for reference types. – Andrew Oct 18 '14 at 15:53
1
  1. Convenience initializer must delegate up to designated initializer

It says that convenience init(file_path:String) should call other initialiser

convenience init(file_path:String) {
  self.init()
  //here can set other properties
}
  1. Convenience initialiser usually provide some default parameters

Convenience initialiser are designed to make creation of class instance less complicated. It means that you don't need to pass all arguments to constructor. In your example the class should look like this

  • Designated initializer takess all possible arguments.
  • Convenience provide default value

Code example

// Create instance of a Letter
Letter()
Letter(file_path: "path.txt")
Letter(file_path: "path.txt", option: 0, other: 0)

//Class Implementation 

class Letter
{
  init(file_path: String , option: Int, other: Int) {
    // Instansiate class
  }
}


extension Letter {

  convenience init() {
    self.init(file_path:"a")
  }

  convenience init(file_path:String) {
    self.init(file_path: file_path , option: 0, other: 0)
  }

  class func loadFromFile(file_path:String) -> Letter {
    return Letter()
  }
}

Now you can create instance of Letter this way -

Kostiantyn Koval
  • 8,407
  • 1
  • 45
  • 58
0

You can't assign to self. What about something like this:

class Letter {
}

extension Letter {
    convenience init(filePath: String) {
        self.init()
        // code to load a Letter from a file goes here.
    }

    class func loadFromFile(filePath: String) -> Letter {
        return Letter(filePath: filePath)
    }
}
Daniel T.
  • 32,821
  • 6
  • 50
  • 72