How to Create Generic Convenience Initializer in Swift

How to create generic convenience initializer in Swift?

As matt already explained, you cannot define an initializer which
is restricted to types implementing a protocol. Alternatively, you
could define a global function instead:

public protocol Nameable {
static func entityName() -> String
}

func createInstance<T : NSManagedObject>(type : T.Type, context : NSManagedObjectContext) -> T where T: Nameable {
let entity = NSEntityDescription.entity(forEntityName: T.entityName(), in: context)!
return T(entity: entity, insertInto: context)
}

which is then used as

let obj = createInstance(Entity.self, context)

You can avoid the additional type parameter if you define the method
as

func createInstance<T : NSManagedObject>(context : NSManagedObjectContext) -> T where T: Nameable { ... }

and use it as

let obj : Entity = createInstance(context)

or

let obj = createInstance(context) as Entity

where the type is now inferred from the context.

swift convenience init and generic class

Generic placeholders are satisfied at the usage of the given generic type – therefore inside your convenience init, you cannot assume that T is a Section. It's an arbitrary concrete type that conforms to A.

For example, it would be perfectly legal for the caller to define a

struct SomeOtherSection : A {...}

and then call your convenience initialiser with T being SomeOtherSection.

The solution in this case is simple, you can just add your convenience initialiser in an extension of DataSource, where T is constrained to being Section – therefore allowing you to
call init(sections:) with a [Section]:

extension DataSource where T == Section {

convenience init() {
var section = Section()
section.items.append("Goodbye")
section.items.append("Swift")

self.init(sections: [section])
}
}

// ...

// compiler will infer that T == Section here.
let ds = DataSource()

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.

Create a generic Data initializer for Decodable types using Swift

You can't overwrite class itself, but you can init it, init object from json and then assign values/ If take your code - it'll be something like this:

public class MyResponse: Codable {
public var id: String?
public var context: String?
public var results: [MyResult]?
}

public extension MyResponse {
convenience init(data: Data) throws {
self.init()
let object = try JSONDecoder().decode(MyResponse.self, from: data)
self.id = object.id
self.context = object.context
self.results = object.results
}
}

If you really don't need a class it's better to use struct instead of it, and it can be like this:

public struct MyResponse: Codable {
public let id: String?
public let context: String?
public let results: [String]?
}

public extension MyResponse {
init(data: Data) throws {
self = try JSONDecoder().decode(MyResponse.self, from: data)
}
}

What is the difference between convenience init vs init in swift, explicit examples better

Standard init:

Designated initializers are the primary initializers for a class. A
designated initializer fully initializes all properties introduced by
that class and calls an appropriate superclass initializer to continue
the initialization process up the superclass chain.

convenience init:

Convenience initializers are secondary, supporting initializers for a
class. You can define a convenience initializer to call a designated
initializer from the same class as the convenience initializer with some
of the designated initializer’s parameters set to default values. You can
also define a convenience initializer to create an instance of that class
for a specific use case or input value type.

per the Swift Documentation

In a nutshell, this means that you can use a convenience initializer to make calling a designated initializer faster and more "convenient". So convenience initializers require the use of self.init instead of the super.init you might see in an override of a designated initializer.

pseudocode example:

init(param1, param2, param3, ... , paramN) {
// code
}

// can call this initializer and only enter one parameter,
// set the rest as defaults
convenience init(myParamN) {
self.init(defaultParam1, defaultParam2, defaultParam3, ... , myParamN)
}

I use these a lot when creating custom views and such that have long initializers with mainly defaults. The docs do a better job explaining than I can, check them out!

How to write a generic Swift class function to initialize classes using a closure?

As Hamish said, maybe it's better to leave the init outside so it is more flexible. However there could be a kind of workaround for this using a protocol.

protocol Initializable {
init()
}

extension Initializable {
static func new(_ initialization: (inout Self) -> Void) -> Self {
var newSelf = Self()
initialization(&newSelf)
return newSelf
}
}

extension NSObject: Initializable {}

NSObject already have an init so it automatically conforms to Initializable.
Then write your extension on the protocol.

The only thing to be aware is that you cannot use class modifier now, as you're in a protocol.

Not sure if this can lead to problem for the NS_UNAVAILABLE modifier, I think it could crash at runtime when you use this on a class that is hiding the init.

Adding Convenience Initializers in Swift Subclass

My understanding of Initializer Inheritance is the same as yours, and I think we are both well aligned with what the book states. I don't think it's an interpretation issue or a misunderstanding of the stated rules. That said, I don't think you're doing anything wrong.

I tested the following in a Playground and it works as expected:

class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}

class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}

RectShape inherits from NSObject and doesn't define any designated initializers. Thus, as per Rule 1, it inherits all of NSObject's designated initializers. The convenience initializer I provided in the implementation correctly delegates to a designated initializer, prior to doing the setup for the intance.

SquareShape inherits from RectShape, doesn't provide a designated initializer and, again, as per Rule 1, inherits all of SquareShape's designated initializers. As per Rule 2, it also inherits the convenience initializer defined in RectShape. Finally, the convenience initializer defined in SquareShape properly delegates across to the inherited convenience initializer, which in turn delegates to the inherited designated initializer.

So, given the fact you're doing nothing wrong and that my example works as expected, I am extrapolating the following hypothesis:

Since SKShapeNode is written in Objective-C, the rule which states that "every convenience initializer must call another initializer from the same class" is not enforced by the language. So, maybe the convenience initializer for SKShapeNode doesn't actually call a designated initializer. Hence, even though the subclass MyShapeNode inherits the convenience initializers as expected, they don't properly delegate to the inherited designated initializer.

But, again, it's only a hypothesis. All I can confirm is that the mechanics works as expected on the two classes I created myself.

How a generic class to conform a protocol (that have init in it) with constraint?

I don't know of a way to achieve this without making Graph final. If you do make it final, however, it will work fine as long as you remove required (required doesn't mean anything if there can't be subclasses):

extension Graph: ExpressibleByArrayLiteral where D == Int {
convenience init(arrayLiteral elements: Node...) {
self.init(Array(zip(0..., elements)))
}
}

Swift Generic constraints in init

The problem is that the first init method

init<T: Equatable>(data: [T]) 

introduces a local type placeholder T which hides (and is completely
unrelated to) the placeholder T of the Generic type, so it
is essentially the same problem as in Array extension to remove object by value.

As of Swift 2 you can solve that with a "restricted extension":

extension Generic where T : Equatable {
init(data: [T]) {
let handler: (T, T) -> Bool = { $0 == $1 }
compare = handler
// ...
}
}

For Swift 1.x the only solution is probably to define a global helper
function

func makeGeneric<T : Equatable>(data: [T]) -> Generic<T> {
return Generic(compareHandler: { $0 == $1 }, data: data)
}

(and I could not think of a sensible name for the function :).



Related Topics



Leave a reply



Submit