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 class
es, so the example initializer will work for as-is for struct
s and enum
s, 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
How to Test If an Instance Is a Specific Class or Type in Swift
Swift iOS14 Datepicker Text Alignment
Swift - How to Deal with Uncaught Exception
Apple Watch and iPhone Are Not Connected When The App in Phone Goes to Background
Realm: Predicate Returning Lazyfiltercollection - How to Convert to Results<T>
Performseguewithidentifier Not Working If Called from Viewdidload
Cmlogitem Timestamp: Why So Complicated
Variable with Getter/Setter Cannot Have Initial Value, on Overridden Stored Property
Swiftui Scrollview Only Scroll in One Direction
Cannot Assign to Value: 'self' Is Immutable
How to Get The Count of a Type Conforming to 'sequence'
Why I Can Not Use Setvalue for Dictionary
How to Get .Adjustsfontsizetofitwidth to Function Properly
Why Does Cabasicanimation Try to Initialize Another Instance of My Custom Calayer
Xcode 9.2 Is Not Showing Swift 4.1