bandejapaisa
bandejapaisa

Reputation: 26952

How can I make the memberwise initialiser public, by default, for structs in Swift?

I have a Swift framework that defines a struct:

public struct CollectionTO {
    var index: Order
    var title: String
    var description: String
}

However, I can't seem to use the implicit memberwise initialiser from another project that imports the library. The error is:

'CollectionTO' cannot be initialised because it has no accessible initialisers

i.e. the default synthesized memberwise initialiser is not public.

var collection1 = CollectionTO(index: 1, title: "New Releases", description: "All the new releases")

I'm having to add my own init method like so:

public struct CollectionTO {
    var index: Order
    var title: String
    var description: String

    public init(index: Order, title: String, description: String) {
        self.index = index;
        self.title = title;
        self.description = description;
    }
}

... but is there a way to do this without explicitly defining a public init?

Upvotes: 251

Views: 48382

Answers (6)

gohanlon
gohanlon

Reputation: 406

To generate a public memberwise initializer automatically, you can use @MemberwiseInit, a Swift macro that I authored. Ensure that all properties are declared public. For example:

import MemberwiseInit

@MemberwiseInit(.public)
public struct CollectionTO {
    public var index: Order
    public var title: String
    public var description: String
}

In 2015, Chris Lattner, one of the principal architects of Swift, acknowledged several “deficiencies” in the built-in memberwise initializer: lack of public access, disabled memberwise initializers when custom initializers are used, and complexities with classes and lazy properties. You can read his full post here. As Lattner then postulated, the “hygienic macro system” introduced some 9 years later in Swift 5.9 allows @MemberwiseInit to address these issues.

Selective Access via @Init

Instead of making a property public (e.g., public var index: Order), you can use @Init(.public) to specify that a property be publicly accessible through the initializer, e.g. @Init(.public) var index: Order. However, the practical difference between making a property public versus just exposing it through the initializer is usually minimal. In most cases, it’s better to mark the properties directly as public if they are intended to be used externally.

Upvotes: 1

Robert Dresler
Robert Dresler

Reputation: 11140

You have to define public init by yourself, luckily starting from Xcode 14 🥳 there is an automatic initializer completion (source - 60399329)

enter image description here

Upvotes: 4

Ben Patch
Ben Patch

Reputation: 1223

Sometimes it's really annoying having an initializer when you don't need one. If you're constantly updating the variables to the object, it becomes bothersome very quickly to update the variables in 3 places (variable declaration, initializer parameter, and initializer implementation). A workaround I've used for this issue is to have a static variable on the struct to act as (or essentially wrap) the "initializer". For instance:

struct MyStruct {
    static var empty = Self()
    static func empty(name: String) -> Self {
        .init(privateName: name)
    }

    private var identifier: String = ""
}

Then you can call it similar to how you would an initializer (with autocomplete and everything!):

func someFunction(_ value: MyStruct) { ... }

//someFunction(.init()) -> ERROR, invalid due to `private` variable
someFunction(.empty)
someFunction(.empty(name: "Dan IRL"))
let myObject = MyStruct.empty
let myObject2 = MyStruct.empty(name: "Monty Python")

Upvotes: 0

JP Aquino
JP Aquino

Reputation: 4066

While it is not possible to have the default memberwise initializer at least you can make one quickly with the following steps:

UPDATE: Xcode 11 and later

As mentioned by Brock Batsell on the comments, for Xcode 11 and later all you need to is this:

  • Right click the class or struct name and choose refactor -> Generate Memberwise Initializer

Xcode 10 and earlier answer

  1. Make the object a class temporarily instead of a struct
  2. Save
  3. Right click the class name and choose refactor -> Generate Memberwise Initializer
  4. Change it back to a struct

Upvotes: 182

bandejapaisa
bandejapaisa

Reputation: 26952

Quoting the manual:

"Default Memberwise Initializers for Structure Types The default memberwise initializer for a structure type is considered private if any of the structure’s stored properties are private. Otherwise, the initializer has an access level of internal.

As with the default initializer above, if you want a public structure type to be initializable with a memberwise initializer when used in another module, you must provide a public memberwise initializer yourself as part of the type’s definition."

Excerpt from "The Swift Programming Language", section "Access Control".

Upvotes: 395

Mehul Parmar
Mehul Parmar

Reputation: 3699

We now have a ruby gem 💎 to parse a complete swift data model file, line-by-line, and add public access modifiers, public member-wise default initializers, and other things into a separate auto-generated output swift file.

This gem is called swift_republic

Please check out the following documentation for running this gem:

https://github.com/mehul90/swift_republic

Upvotes: 0

Related Questions