Почему удобное ключевое слово даже необходимо в Swift?

Так как Swift поддерживает метод и перегрузку инициализатора, вы можете поместить несколько init рядом друг с другом и использовать то, что вы считаете удобным:

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    init() {
        self.name = "John"
    }
}

Итак, почему ключевое слово convenience существует даже? Что делает следующие существенно лучше?

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "John")
    }
}

Ответ 1

Существующие ответы показывают только половину истории convenience. Другая половина истории, половина, на которую не распространяется ни один из существующих ответов, отвечает на вопрос, который Десмонд опубликовал в комментариях:

Почему Swift заставил меня поставить convenience перед моим инициализатором только потому, что мне нужно вызвать self.init из него?

Я немного коснулся этого вопроса , в котором я подробно описываю несколько правил инициализации Swift, но основное внимание было уделено слову required, Но этот ответ все еще рассматривал то, что было связано с этим вопросом и этим ответом. Мы должны понять, как работает наследование инициализации Swift.

Поскольку Swift не позволяет использовать неинициализированные переменные, вам не гарантируется наследование всех (или любых) инициализаторов из класса, на который вы наследуете. Если мы подклассы и добавим какие-либо неинициализированные переменные экземпляра в наш подкласс, мы перестали наследовать инициализаторы. И пока мы не добавим собственные инициализаторы, компилятор будет кричать на нас.

Чтобы быть ясным, неинициализированная переменная экземпляра представляет собой любую переменную экземпляра, которая не получает значения по умолчанию (имея в виду, что опции и неявно развернутые опции автоматически принимают значение по умолчанию nil).

Итак, в этом случае:

class Foo {
    var a: Int
}

a - неинициализированная переменная экземпляра. Это не будет компилироваться, если мы не дадим a значение по умолчанию:

class Foo {
    var a: Int = 0
}

или инициализировать a в методе инициализации:

class Foo {
    var a: Int

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

Теперь, посмотрим, что произойдет, если мы подклассом Foo, будем ли мы?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

Правильно? Мы добавили переменную, и мы добавили инициализатор, чтобы установить значение b, чтобы оно скомпилировалось. В зависимости от того, на каком языке вы находитесь, вы можете ожидать, что Bar унаследовал инициализатор Foo, init(a: Int). Но это не так. И как это могло быть? Как Foo init(a: Int) знает, как назначить значение переменной b, которую добавил Bar? Это не так. Поэтому мы не можем инициализировать экземпляр Bar с инициализатором, который не может инициализировать все наши значения.

Какое отношение это имеет к convenience?

Хорошо, давайте рассмотрим правила наследования инициализатора:

Правило 1

Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.

Правило 2

Если ваш подкласс обеспечивает реализацию всех его инициализаторов, назначенных суперклассам, либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части своего определения, то он автоматически наследует все инициализаторы удобства суперкласса.

Уведомление Правило 2, в котором упоминаются инициализаторы удобства.

То, что делает ключевое слово convenience, указывает нам, какие инициализаторы могут быть наследованы подклассами, которые добавляют переменные экземпляра без значений по умолчанию.

Возьмем этот пример Base class:

class Base {
    let a: Int
    let b: Int

    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }

    convenience init() {
        self.init(a: 0, b: 0)
    }

    convenience init(a: Int) {
        self.init(a: a, b: 0)
    }

    convenience init(b: Int) {
        self.init(a: 0, b: b)
    }
}

Обратите внимание, что здесь есть три инициализатора convenience. Это означает, что у нас есть три инициализатора, которые можно унаследовать. И у нас есть один назначенный инициализатор (назначенный инициализатор - это просто любой инициализатор, который не является инициализатором удобства).

Мы можем создавать экземпляры базового класса четырьмя различными способами:

введите описание изображения здесь

Итак, создадим подкласс.

class NonInheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}

Мы наследуем от Base. Мы добавили нашу собственную переменную экземпляра, и мы не дали ей значение по умолчанию, поэтому мы должны добавить собственные инициализаторы. Мы добавили один, init(a: Int, b: Int, c: Int), но он не соответствует сигнатуре инициализатора класса Base: init(a: Int, b: Int). Это означает, что мы не наследуем никаких инициализаторов из Base:

введите описание изображения здесь

Итак, что произойдет, если мы унаследовали от Base, но мы пошли вперед и внедрили инициализатор, который сопоставил назначенный инициализатор от Base?

class Inheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }

    convenience override init(a: Int, b: Int) {
        self.init(a: a, b: b, c: 0)
    }
}

Теперь, в дополнение к двум инициализаторам, которые мы реализовали непосредственно в этом классе, поскольку мы реализовали инициализатор, соответствующий инициализатору класса Base, мы получили наследование всех инициализаторов Base class convenience:

введите описание изображения здесь

Тот факт, что инициализатор с соответствующей сигнатурой отмечен как convenience, здесь не имеет никакого значения. Это означает, что Inheritor имеет только один назначенный инициализатор. Поэтому, если мы наследуем от Inheritor, нам просто нужно реализовать этот назначенный инициализатор, а затем мы наследуем Inheritor инициализатор удобства, который, в свою очередь, означает, что мы реализовали все Base назначенные инициализаторы и можем наследует его инициализаторы convenience.

Ответ 2

Большая ясность. Из вашего второго примера,

init(name: String) {
    self.name = name
}

требуется или назначается. Он должен инициализировать все ваши константы и переменные. Инициализаторы удобства необязательны и обычно могут использоваться для упрощения инициализации. Например, скажем, ваш класс Person имеет необязательный переменный пол:

var gender: Gender?

где Пол - это перечисление

enum Gender {
  case Male, Female
}

у вас могут быть инициализаторы удобства, такие как

convenience init(maleWithName: String) {
   self.init(name: name)
   gender = .Male
}

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}

Инициализаторы удобства должны вызывать в них назначенные или требуемые инициализаторы. Если ваш класс является подклассом, он должен вызвать super.init() внутри него инициализацию.

Ответ 3

Хорошо, первое, что приходит мне на ум, это то, что оно используется в наследовании класса для организации кода и его удобочитаемости. Продолжая свой класс Person, подумайте о таком сценарии

class Person{
    var name: String
    init(name: String){
        self.name = name
    }

    convenience init(){
        self.init(name: "Unknown")
    }
}


class Employee: Person{
    var salary: Double
    init(name:String, salary:Double){
        self.salary = salary
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name:name, salary: 0)
    }
}

let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}

С инициализатором удобства я могу создать объект Employee() без значения, поэтому слово convenience

Ответ 4

Помимо точек, которые другие пользователи объяснили здесь, я немного понимаю.

Я сильно чувствую связь между инициализатором удобства и расширениями. Что касается меня, то инициализаторы удобства полезны, когда я хочу изменить (в большинстве случаев сделать короткую или легкую) инициализацию существующего класса.

Например, какой-либо сторонний класс, который вы используете, имеет init с четырьмя параметрами, но в вашем приложении последние два имеют одинаковое значение. Чтобы избежать ввода текста и сделать код чистым, вы можете определить convenience init только с двумя параметрами и внутри него вызывать self.init с последними параметрами со значениями по умолчанию.

Ответ 5

В соответствии с документацией Swift 2.1, инициализаторы convenience должны придерживаться определенных правил:

  • А convenience инициализатор может вызывать только intializers в том же класс, а не в суперклассах (только поперек, а не вверх)

  • А convenience инициализатор должен вызывать назначенный инициализатор где-то в цепочке

  • А convenience инициализатор не может изменить свойство ЛЮБОЕ, прежде чем он вызвал другой инициализатор, тогда как назначенный инициализатор должен инициализировать свойства, которые вводятся текущим классом перед вызовом другого инициализатора.

Используя ключевое слово convenience, компилятор Swift знает, что он должен проверять эти условия, иначе он не мог.

Ответ 6

Класс может иметь более одного назначенного инициализатора. Инициализатор удобства - это вторичный инициализатор, который должен вызывать назначенный инициализатор того же класса.