В чем разница между удобством init и init в быстрых, явных примерах лучше

82

Мне трудно понять разницу между обоими или цель convenience init.

Чино Пан
источник
Вы читали раздел « Инициализация » книги «Swift Programming Language» ? В чем именно ваше замешательство?
rmaddy
1
Ответы доступны уже в SO. Пожалуйста, обратитесь - stackoverflow.com/questions/30896231/…
Сантош

Ответы:

105

Стандарт init:

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

convenience init:

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

согласно Swift Documentation

Вкратце, это означает, что вы можете использовать удобный инициализатор, чтобы сделать вызов назначенного инициализатора более быстрым и «удобным». Таким образом, для удобных инициализаторов требуется использование self.initвместо, которое super.initвы можете увидеть при переопределении назначенного инициализатора.

пример псевдокода:

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)
}

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

Treyhakanson
источник
73

Инициализаторы удобства используются, когда у вас есть класс с множеством свойств, из-за которых всегда «утомительно» инициализировать остроумие со всеми этими переменными, поэтому с инициализатором удобства вы просто передаете некоторые переменные для инициализации объект, а остальным присвойте значение по умолчанию. На сайте Рэя Вендерлиха есть очень хорошее видео, не уверен, что оно бесплатное или нет, потому что у меня есть платный аккаунт. Вот пример, где вы можете видеть, что вместо инициализации моего объекта всеми этими переменными я просто даю ему заголовок.

struct Scene {
  var minutes = 0
}

class Movie {
  var title: String
  var author: String
  var date: Int
  var scenes: [Scene]

  init(title: String, author: String, date: Int) {
    self.title = title
    self.author = author
    self.date = date
    scenes = [Scene]()
  }

  convenience init(title:String) {
    self.init(title:title, author: "Unknown", date:2016)
  }

  func addPage(page: Scene) {
    scenes.append(page)
  }
}


var myMovie = Movie(title: "my title") // Using convenicence initializer
var otherMovie = Movie(title: "My Title", author: "My Author", date: 12) // Using a long normal initializer
Маго Николас Паласиос
источник
4
Хороший пример. Это прояснило концепцию. Спасибо.
Арджун Калидас
5
У нас также может быть init () со значениями по умолчанию для автора и параметра даты как `init (title: String, author: String =" Unknown ", date: Int = 2016) {self.title = title self.author = author self. date = date scene = [Scene] ()} `тогда зачем нам нужен инициализатор удобства?
shripad20
@ shripad20 здесь тот же вопрос. Вы узнали, почему мы должны использовать удобство, хотя мы можем сделать другой инициализатор и отправить из него параметр по умолчанию. Пожалуйста, дайте мне знать, если найдете ответ
user100
удобство является дополнительным к "основному" self.init, в то время как self.init с параметрами по умолчанию заменяет "основной" self.init. hackingwithswift.com/example-code/language/…
user1105951 06
@ shripad20 просмотрите мой пример ниже, чтобы прояснить, где инициализаторы удобства превосходят значения по умолчанию. Надеюсь, поможет.
Крис Граф
14

Вот простой пример, взятый с портала разработчиков Apple .

В основном назначенный инициализатор - это init(name: String), он обеспечивает инициализацию всех сохраненных свойств.

init()Удобство инициализатор, не принимая никаких аргументов, автоматически устанавливает значение nameхранящегося имущества [Unnamed], используя назначенный инициализатор.

class Food {
    let name: String

    // MARK: - designated initializer
    init(name: String) {
        self.name = name
    }

    // MARK: - convenience initializer
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

// MARK: - Examples
let food = Food(name: "Cheese") // name will be "Cheese"
let food = Food()               // name will be "[Unnamed]"

Это полезно, когда вы имеете дело с большими классами, по крайней мере, с несколькими сохраненными свойствами. Я бы порекомендовал узнать больше о дополнительных возможностях и наследовании на портале Apple Developer.

грязный дэн
источник
6

Где удобные инициализаторы превосходят значения параметров по умолчанию

Для меня, convenience initializers они полезны, если нужно сделать больше, чем просто установить значение по умолчанию для свойства класса.

Реализация класса с назначенным init ()

В противном случае я бы просто установил значение по умолчанию в initопределении, например:

class Animal {

    var race: String // enum might be better but I am using string for simplicity
    var name: String
    var legCount: Int

    init(race: String = "Dog", name: String, legCount: Int = 4) {
        self.race = race
        self.name = name
        self.legCount = legCount // will be 4 by default
    }
}

Расширение класса с удобством init ()

Однако здесь может быть что-то большее, чем просто установить значение по умолчанию, и это то, где convenience initializersпригодится:

extension Animal {
    convenience init(race: String, name: String) {
        var legs: Int

        if race == "Dog" {
            legs = 4
        } else if race == "Spider" {
            legs = 8
        } else {
            fatalError("Race \(race) needs to be implemented!!")
        }

        // will initialize legCount automatically with correct number of legs if race is implemented
        self.init(race: race, name: name, legCount: legs)
    }
}

Примеры использования

// default init with all default values used
let myFirstDog = Animal(name: "Bello")

// convenience init for Spider as race
let mySpider = Animal(race: "Spider", name: "Itzy")

// default init with all parameters set by user
let myOctopus = Animal(race: "Octopus", name: "Octocat", legCount: 16)

// convenience init with Fatal error: Race AlienSpecies needs to be implemented!!
let myFault = Animal(race: "AlienSpecies", name: "HelloEarth")
Крис Граф
источник
1
хорошо и просто объяснил здесь
vivi
4

Удобный инициализатор можно определить в расширении класса . А вот стандартный - не может.

Serg
источник
1
Хотя это не совсем полный ответ на вопрос, я еще не думал об этом, спасибо!
Marc-André Weibezahn
3

convenience initделает необязательным для инициализации класса со значениями.

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

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

Бадр
источник
3

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

Попробуйте сделать это на детской площадке

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    init(name: String) {
        self.init(name: name, level: 0) //<- Call the initializer above?

        //Sorry you can't do that. How about adding a convenience keyword?
    }
}

Player(name:"LoseALot")

С ключевым словом удобства

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    //Add the convenience keyword
    convenience init(name: String) {
        self.init(name: name, level: 0) //Yes! I am now allowed to call my fellow initializer!
    }
}
Jeromegamo
источник
2

Примечание: прочтите весь текст

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

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

Назначенные инициализаторы для классов записываются так же, как и простые инициализаторы для типов значений:

init(parameters) {
statements
}

Инициализаторы удобства написаны в том же стиле, но с модификатором удобства, помещенным перед ключевым словом init, разделенным пробелом:

convenience init(parameters) {
statements
}

Практический пример:

class Food {
var name: String
init(name: String) {
    self.name = name
}
convenience init() {
    self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon”

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

«Класс Food также предоставляет удобный инициализатор init () без аргументов. Инициализатор init () предоставляет имя заполнителя по умолчанию для нового блюда, делегируя его классу Food init (name: String) со значением имени [Безымянный]: »

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]”

Второй класс в иерархии - это подкласс Food под названием RecipeIngredient. Класс RecipeIngredient моделирует ингредиент в рецепте приготовления. Он вводит свойство Int, называемое количеством (в дополнение к свойству name, которое оно наследует от Food), и определяет два инициализатора для создания экземпляров RecipeIngredient:

class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
    self.quantity = quantity
    super.init(name: name)
}
override convenience init(name: String) {
    self.init(name: name, quantity: 1)
}
}

Класс RecipeIngredient имеет единственный назначенный инициализатор, init (имя: String, количество: Int), который можно использовать для заполнения всех свойств нового экземпляра RecipeIngredient. Этот инициализатор начинается с присвоения переданного аргумента количества свойству количества, которое является единственным новым свойством, введенным RecipeIngredient. После этого инициализатор передает полномочия инициализатору init (name: String) класса Food.

страница: 536 Отрывок из: Apple Inc. «Язык программирования Swift (Swift 4)». iBooks. https://itunes.apple.com/pk/book/the-swift-programming-language-swift-4-0-3/id881256329?mt=11

Абузар Манзур
источник
2

Так что это пригодится, когда вам не нужно указывать каждое свойство класса. Так, например, если я хочу создавать все приключения с начальным значением HP, равным 100, я бы использовал следующий удобный init и просто добавил имя. Это сильно сократит код.

class Adventure { 

// Instance Properties

    var name: String
    var hp: Int
    let maxHealth: Int = 100

    // Optionals

    var specialMove: String?

    init(name: String, hp: Int) {

        self.name = name
        self.hp = hp
    }

    convenience init(name: String){
        self.init(name: name, hp: 100)
    }
}
Нирав Джайн
источник
1

Все ответы звучат хорошо, но давайте разберемся с этим на простом примере

class X{                                     
   var temp1
   init(a: Int){
        self.temp1 = a
       }

Теперь мы знаем, что класс может наследовать другой класс, поэтому

class Z: X{                                     
   var temp2
   init(a: Int, b: Int){
        self.temp2 = b
        super.init(a: a)
       }

Теперь, в этом случае при создании экземпляра для класса Z вам нужно будет указать оба значения «a» и «b».

let z = Z(a: 1, b: 2)

Но что, если вы хотите передать только значение b и хотите, чтобы rest принимал значение по умолчанию для других, тогда в этом случае вам нужно инициализировать другие значения значением по умолчанию. Но подождите, как ?, для этого U нужно установить его задолго до этого только в классе.

//This is inside the class Z, so consider it inside class Z's declaration
convenience init(b: Int){
    self.init(a: 0, b: b)
}
convenience init(){
    self.init(a: 0, b: 0)
}

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

let z1 = Z(b: 2)
let z2 = Z()
Шубхам Мишра
источник