Convenience Failable Initializers Assign Self

Convenience Failable Initializers assign self

Have you considered a factory method on NSURL? It gets you most of the way there.

extension NSURL {
static func initialize(string: String, throwOnNil: Bool) throws -> NSURL? {
if let URL = NSURL(string: string) {
return URL
} else if throwOnNil {
throw MyURLError(string: string)
} else {
return nil
}
}
}

Use `self =` in convenience initializers to delegate to JSONDecoder or factory methods in Swift to avoid `Cannot assign to value: 'self' is immutable`

First, note that this limitation exists only for classes, so the example initializer will work for as-is for structs and enums, but not all situations allow changing a class to one of these types.

This limitation on class initializers is a frequent pain-point that shows up often on this site (for example). There is a thread on the Swift forums discussing this issue, and work has started to add the necessary language features to make the example code above compile, but this is not complete as of Swift 5.4.
From the thread:

Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.

Using this idea to fix the example code yields

final class Test: Codable {
let foo: Int

init(foo: Int) {
self.foo = foo
}

func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
}

protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}

let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)

which works fine. If Test is the only type conforming to TestProtocol, then only Test will get this initializer.

An alternative is to simply extend Decodable or another protocol to which your class conforms, but this may be undesirable if you do not want other types conforming to that protocol to also get your initializer.

Cannot assign to self in a method - Swift

Assigning to self is for value types only, struct and enum.

In your case you should do like this:

public convenience init?(context: NSManagedObjectContext) {
let entity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: context);
self.init(entity: entity!, insertIntoManagedObjectContext: context)

if User.loggedInUserId(context) == nil {
context.deleteObject(self)
return nil
}
}

You must call self.init(..) unconditionally, because, it's a rule for convenience initializer.
If the condition is false, deleteObject(self) and return nil. Hmm... I think you should not use
failable initializers for this.

Swift 5: 'self' used before 'self.init' call

A solution is not to call the designated initializer

public required init?(coder aDecoder: NSCoder) {
guard
let authState = aDecoder.decodeObject(forKey: "authState") as? OIDAuthState,
let config = aDecoder.decodeObject(forKey: "config") as? [String: String]
else {
print("KeycloakTokenManager: There was an error intializing authState or config")
return nil
}

self.authState = authState
self.config = config
self.accessibility = aDecoder.decodeObject(forKey: "accessibility") as! CFString

super.init()

KeycloakAuth.configuration = config
}

Creating a second failable initializer using a convenience initializer

In Swift 3, a convenience initializer must call a designated initializer in the same class before any attempt to access self is made in the convenience initializer.

I suggest you change your convenience initializer to be like this:

convenience init?(barnivoreDictionary: [String: AnyObject]) {
guard
let id = barnivoreDictionary[Constants.kID] as? Int,
let companyName = barnivoreDictionary[Constants.kCompanyName] as? String,
let address = barnivoreDictionary[Constants.kAddress] as? String,
let city = barnivoreDictionary[Constants.kCity] as? String,
let state = barnivoreDictionary[Constants.kState] as? String,
let postal = barnivoreDictionary[Constants.kPostal] as? String,
let country = barnivoreDictionary[Constants.kCountry] as? String,
let phone = barnivoreDictionary[Constants.kPhone] as? String,
let email = barnivoreDictionary[Constants.kEmail] as? String,
let url = barnivoreDictionary[Constants.kURL] as? String,
let checkedBy = barnivoreDictionary[Constants.kCheckedBy] as? String,
let notes = barnivoreDictionary[Constants.kNotes] as? String,
let status = barnivoreDictionary[Constants.kStatus] as? String,
let statusColor = barnivoreDictionary[Constants.kStatusColor] as? String else {
return nil
}

self.init(id: id, companyName: companyName, address: address, city: city, state: state, postal: postal, country: country, phone: phone, email: email, url: url, checkedBy: checkedBy, notes: notes, status: status, statusColor: statusColor, alcoholType: alcoholType)
}

Note that you don't need to call self.init... inside the guard.

Swift unwrap optional init inside convenience init

You cannot have such an unwrapping, because it is forbidden to delegate a non-failable initializer to a failable initializer. This is the consequence of the following language reference principle:

In either case, if you delegate to another initializer that causes initialization to fail, the entire initialization process fails immediately, and no further initialization code is executed.

In Swift, how do I create a convenience init for a class where the init's implementation creates the class value instead of calling an existing init

Saying that factory initializers are "not supported yet" in Swift is fallacious. Their exclusion is a design decision, and their use intended to be covered by failable initializers; quoting the following Apple Swift blog post

Failable initializers eliminate the most common reason for factory
methods in Swift, which were previously the only way to report failure
when constructing this object.

...

Using the failable initializer allows greater use of Swift’s uniform
construction syntax, which simplifies the language by eliminating
the confusion and duplication between initializers and factory
methods
.

So in your case, you're probably looking for a convenience failable initializer. E.g., something along the lines

extension NSData {
convenience init?(JSONObject: AnyObject) {
do {
let foo = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: [])
self.init(data: foo)
}
catch {
return nil
}
}
}

/* Example usage */
let foo : AnyObject = ["Foo":"bar"]
let bar = NSData.init(JSONObject: foo)

In the title of your question you include "... instead of calling an existing init". When making use of convenience initializer, a designated initializer (of same class) must be called at some point (even via other convenience initializers). From the Swift Language Guide - Initialization - Class Inheritance and Initialization:

...

Rule 2

A convenience initializer must call another initializer from the same
class.

Rule 3

A convenience initializer must ultimately call a designated
initializer.

The example code above, however, allows an early escape (failure) of the convenience initializer if NSJSONSerialization.dataWithJSONObject(...) fails, but if it succeeds, sooner or later a designated initializer needs to be called (in this case init(data:) designated initializer).


For details on failable initializers, see the Swift Language Guide - Initialization - Failable Initializers. For an additional remark regarding the initializer chain (convenience -> ... -> designated initializer), see rickster:s comment below.

How to check for nil with nested convenience failable initializers in Swift?

I think that when you call a failable initializer of a superclass, it has an implicit return if it fails.

In fact the documentation about Failable Initializers states that:

If the superclass initialization fails because of an empty name value, the entire initialization process fails immediately and no further initialization code is executed



Related Topics



Leave a reply



Submit