Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I throw from class init() in Swift with constant string loaded from file?

Tags:

swift

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!

like image 563
Dribbler Avatar asked Jan 01 '16 22:01

Dribbler


People also ask

What does the init function do in Swift?

Swift init() Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

How do you initialize a class in Swift?

An initializer is a special type of function that is used to create an object of a class or struct. In Swift, we use the init() method to create an initializer. For example, class Wall { ... // create an initializer init() { // perform initialization ... } }

What is Failable initializer in Swift?

In a failable initializer, return nil indicates that initialization has failed; no other value can be returned. In the example, failure occurs when the string could not be parsed as an integer. Otherwise, self is initialized to the parsed value.


2 Answers

[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 ;-)

like image 80
Luca Angeletti Avatar answered Sep 27 '22 22:09

Luca Angeletti


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 { } 
like image 23
Cristik Avatar answered Sep 27 '22 23:09

Cristik