32

I'd like my class init() in Swift to throw an error if something goes wrong with loading a file into a string within the class. Once the file is loaded, the string will not be altered, so I would prefer to use let. This works:

class FileClass {    
    var text: NSString = ""   
    init() throws {   
        do {
            text = try NSString( contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding ) }
        catch let error as NSError {
            text = ""
            throw error
        }      
    }
}

but when I replace

var text: NSString = ""

with

let text: NSString

I get an All stored properties of a class instance must be initialized before throwing from an initializer error.

I've tried various approaches such as to make text optional

let text: NSString?

but haven't found any that work. It it possible to have text be loaded from a file, immutable, and for init() to throw an error? Can I have my cake and eat it three?

Many thanks in advance!

Dribbler
  • 4,343
  • 10
  • 33
  • 53
  • 1
    maybe, you have to look at this post http://stackoverflow.com/a/26496022/3527499 – hamsternik Jan 01 '16 at 22:45
  • Thanks, @Hamsternik, I must have read that post about 5 times before I finally put this one up. It does contain some great insights, although it didn't solve my problem. – Dribbler Jan 01 '16 at 22:56

2 Answers2

30

[Update] Swift Version >= 2.2

Since Swift 2.2 you can break the execution of a class initialiser without the need of populating all stored properties

class FileStruct {
    let text: String

    init() throws {
        do {
            text = try String(contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding ) }
        catch let error as NSError {
            throw error
        }
    }
}

Swift Version <= 2.1

Currently in Swift class you cannot break the execution of an initialiser before every stored property has been initialized.

On the other hand, you don't have this constraint with structs so

struct FileStruct {
    var text: String

    init() throws {
        do {
            text = try String(contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding ) }
        catch let error as NSError {
            throw error
        }
    }
}

You can also avoid the do/catch block

struct FileStruct {
    var text: String

    init() throws {
        text = try String(contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding)
    }
}

Finally I replaced NSString with String since we are using Swift, not Objective-C ;-)

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Thanks @appzYourLife, that works for me. And thanks for the kick re String--old habits come hard to break :) – Dribbler Jan 01 '16 at 22:54
14

You can use a failable initializer instead, as is more suited for this kind of scenarios.

class FileClass {
    let text: String
    init?() {
        guard let fileContents = try? NSString( contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding ) else {
            text = ""
            return nil
        }
        text = fileContents as String
    }
}

or, if you want to print the error:

class FileClass {
    let text: String
    init?() {
        do {
            text = try String( contentsOfFile: "/Users/me/file.txt", encoding: NSUTF8StringEncoding )
        } catch let error as NSError {
            print("Error while reading: \(error)")
            text = ""
            return nil
        }
    }
}

Usage is easier than with a throwing initializer, as you can use if-let or guard-let:

if let file = FileClass() {
}

, or

guard let file = FileClass() else {
    return
}

versus

let file: FileClass
do {
    file = FileClass()
} catch {
}
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 2
    Thanks, @Cristik! It is cleaner, but don't I lose the information about what the error was? I find it helpful in debugging to bring up a modal dialog box with the error description. – Dribbler Jan 01 '16 at 23:38
  • 1
    You can do/catch in the initializer if you want to print the error, I added sample code for this too. And you can display an error message to inform the user if you detect the initializer returned nil. – Cristik Jan 01 '16 at 23:45