Яка різниця між зручністю init та init у швидких, явних прикладах краще


82

У мене виникають проблеми, щоб зрозуміти різницю між ними або мету convenience init.


Чи читали ви розділ " Ініціалізація " книги "Швидка мова програмування" ? У чому саме полягає ваша плутанина?
rmaddy

1
Відповіді доступні вже в SO. Будь ласка, зверніться - stackoverflow.com/questions/30896231/…
Сантош,

Відповіді:


105

Стандарт init:

Призначені ініціалізатори є основними ініціалізаторами для класу. Призначений ініціалізатор повністю ініціалізує всі властивості, введені цим класом, і викликає відповідний ініціалізатор суперкласу, щоб продовжити процес ініціалізації вгору по ланцюжку суперкласу.

convenience init:

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

відповідно до документації Swift

У двох словах, це означає, що ви можете використовувати зручний ініціалізатор, щоб зробити виклик призначеного ініціалізатора швидшим та "зручнішим". Отже, зручні ініціалізатори вимагають використання 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)
}

Я багато використовую їх при створенні власних подань та таких, які мають довгі ініціалізатори з головним чином за замовчуванням. Документи краще пояснюють роботу, ніж я можу, перевірте їх!


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 (заголовок: Рядок, автор: Рядок =" Невідомо ", дата: Int = 2016) {self.title = заголовок self.author = автор самостійно. date = дата сцени = [Scene] ()} `тоді навіщо нам зручний ініціалізатор?
shripad20,

@ shripad20 те саме питання тут. Чи знайшли ви, чому нам доводиться користуватися зручністю, хоча ми можемо зробити ще один ініціалізатор і надіслати з нього параметр за замовчуванням. Будь ласка, дайте мені знати, якщо знайдете відповідь
user100

зручність є додатковою до "main" self.init, тоді як self.init із параметрами за замовчуванням є заміною "main" self.init. hackingwithswift.com/example-code/language/…
user1105951

@ 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

Зручний ініціалізатор можна визначити у розширенні класу . Але стандартний - не може.


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

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 () надає ім’я заповнювача за замовчуванням для нового продукту, делегуючи його до init класу Food (name: String) зі значенням імені [Без імені]:

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

Другим класом в ієрархії є підклас їжі, який називається RecipeIngredient. Клас RecipeIngredient моделює інгредієнт рецепту приготування. Він вводить властивість Int, що називається кількість (на додаток до властивості імені, яке вона успадковує від 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 і хочете, щоб відпочинок прийняв значення за замовчуванням для інших, тоді в цьому випадку вам потрібно ініціалізувати інші значення за замовчуванням. Але почекайте як ?, для цього 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.