paint-brush
Свифт инит(), једном за свагдаод стране@kfamyn
Нова историја

Свифт инит(), једном за свагда

од стране Kiryl Famin19m2025/03/21
Read on Terminal Reader

Предуго; Читати

Овај чланак покрива све што је битно за Свифт иницијализаторе: члански, назначени, погодност, погодност заобилажења; потребни случајеви употребе; УИВиев() иницијализатор без параметара; помоћ компајлера; фаилабле инит, енум инит и још много тога.
featured image - Свифт инит(), једном за свагда
Kiryl Famin HackerNoon profile picture
0-item
1-item

Увод

Здраво! Моје име је Кирил Фамин, и ја сам иОС програмер.


Данас желим да детаљно испитам тако једноставну тему као што су иницијализатори у Свифту. Упркос привидној једноставности, понекад недостатак потпуног разумевања ове теме доводи до фрустрирајућих грешака које се жели брзо поправити без упуштања у детаље.


У овом чланку ћемо покрити све у вези са иницијализаторима, укључујући:


  • Како задржати члански иницијализатор структуре док дефинишете прилагођени

  • Зашто није увек потребно писати иницијализатор у класама

  • Зашто позивање super.init није увек потребно у одређеном иницијализатору

  • Зашто сва поља поткласе морају бити попуњена пре позивања super.init

  • Како приступити свим родитељским иницијализаторима са минималним заменама у подкласама

  • Када је тачно required иницијализатор

  • Зашто се UIView.init() увек позива без параметара, али init(frame:) и init(coder:) замењују


...и више. Али идемо корак по корак.

Садржај

Основе

Структуре

  • Члански иницијализатор
  • Опционо
  • var вс let
  • Задржавање чланског иницијализатора

класе

  • Одређени иницијализатор
  • Погодни иницијализатор
  • Задржавање иницијализатора погодности суперкласе
  • Минимизирање броја замењивања
  • Помоћ компајлера
  • required иницијализатор: генерици, протоколи, Self() , final
  • UIView() без параметара

Часне похвале

  • Неуспешно инит
  • Енумс

Резиме

Релевантне везе

Основе

Апплеов водич Тхе Свифт Программинг Лангуаге (6) (који је, иначе, изненађујуће детаљан за иницијализаторе) каже:


Иницијализација је процес припреме инстанце класе, структуре или набрајања за употребу. Овај процес укључује постављање почетне вредности за свако сачувано својство на тој инстанци и извођење било каквог другог подешавања или иницијализације које је потребно пре него што нова инстанца буде спремна за употребу.


Овај процес иницијализације имплементирате тако што дефинишете иницијализаторе , који су попут посебних метода које се могу позвати да креирају нову инстанцу одређеног типа. За разлику од Објецтиве-Ц иницијализатора, Свифт иницијализатори не враћају вредност. Њихова примарна улога је да осигурају да су нове инстанце типа исправно иницијализоване пре него што се први пут користе.


Па, претпостављам да не морам ништа да додам овде.

Структуре

Почнимо са дискусијом о иницијализаторима структуре. Ово је прилично једноставно јер нема наслеђа, али ипак постоје нека правила која морате знати.

Члански иницијализатор

Хајде да напишемо једноставну структуру:

 struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool)


Приметите да смо били у могућности да иницијализујемо структуру без експлицитног декларисања иницијализатора. Ово се дешава зато што структуре примају иницијализатор по члану који генерише компајлер. Ово функционише само за структуре .


Ако изаберете Рефактор → Генериши члански иницијализатор , можете видети како то изгледа:


Генерисање чланског иницијализатора у Ксцоде-у


 init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked }


Из потписа је лако видети да ће неуспех да обезбедите вредности за све параметре довести до грешке при компилацији:

 let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call


Међутим, ако желите да смањите број потребних аргумената, можете дефинисати прилагођени иницијализатор:

 init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅


Имајте на уму да ако isBlocked није попуњен, то ће довести до грешке при компилацији јер сва својства структуре морају бити попуњена у иницијализатору .

Опционо, var вс let

Једини случај када поље не треба да се попуни експлицитно је када је то опциона ( ? ) променљива ( var ). У таквим случајевима, поље ће подразумевано бити nil :

 struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅


Међутим, ако покушамо да користимо иницијализатор по чланству у овом случају, добићемо грешку компилације:

 let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call

Задржавање чланског иницијализатора

Ово се дешава зато што се декларисањем прилагођеног иницијализатора уклања члански иницијализатор. Још увек је могуће експлицитно дефинисати, али неће бити аутоматски доступан.


Међутим, постоји мали трик да се задржи члански иницијализатор: прогласите прилагођени иницијализатор у extension .

 struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false


Резиме за структуре

  • Сва поља морају бити попуњена у иницијализатору
  • Опциона var поља су подразумевана на nil
  • Структуре добијају бесплатан члански иницијализатор
  • Члански иницијализатор нестаје ако је декларисан прилагођени иницијализатор
  • Да бисте задржали члански иницијализатор структуре, дефинишите прилагођени у extension

класе

Одређени иницијализатор

Примарни иницијализатор за класу је назначени иницијализатор . Служи у две сврхе:

  1. Осигурава да су сва поља попуњена
  2. Ако је класа наслеђена, она позива иницијализатор суперкласе
 class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } }


Сва поља морају бити попуњена пре позивања super.init . То је зато што иницијализатор суперкласе може позвати методе које је поткласа заменила, а које могу приступити непопуњеним својствима поткласе.

 class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } }


Дакле, да нисмо поставили self.breed = breed , наишли бисмо на грешку у току извођења јер би иницијализатор Animal позвао преодређени getInfo() метод из класе Dog . Овај метод покушава да приступи својству breed , које још не би било насељено.


За разлику од структура, класе не добијају имплицитни члански иницијализатор. Ако постоје неиницијализована својства, долази до грешке при компилацији:

 class Animal { // ❌ Class 'Animal' has no initializers var age: Int }
 class Animal { // ✅ var age: Int = 0 }
 class Animal { // ✅ var age: Int? }
 class Animal { } // ✅

Погодни иницијализатор

Класе такође могу имати иницијализатор погодности . За разлику од одређених иницијализатора, они не креирају објекат од нуле, већ поједностављују процес иницијализације поновним коришћењем логике других иницијализатора.

 class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } }


Иницијализатори погодности могу позвати или одређене иницијализаторе или друге иницијализаторе погодности. На крају, одређени иницијализатор ће увек бити позван.

Погодност, назначени и иницијализатори суперкласе


Погодни иницијализатори увек иду хоризонтално (селф.инит), а назначени иницијализатори иду вертикално (супер.инит).

Задржавање иницијализатора погодности Суперцласс

Чим поткласа прогласи нова својства, она губи приступ свим иницијализаторима погодности суперкласе.

 class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call 


Тренутна хијерархија иницијализатора, само један инит је експлицитно декларисан


Ово се може поправити тако што ће се заобићи сви назначени иницијализатори суперкласе .

 class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ 


Иницијализатори погодности су сада враћени, али су два иницијализатора експлицитно декларисана


Лако је видети да, на овај начин, да бисте користили иницијализатор погодности у следећој поткласи, морате заменити два иницијализатора.

 class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 


Иницијализатори погодности су враћени, али ГуидеДог експлицитно наводи три иницијализатора


Минимизирање броја замењивања

Међутим, ово се може избећи коришћењем иницијализатора за замену погодности .

 class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 


Два иницијализатора погодности за животиње су враћена, инит(аге:, наме:) је враћена и само два ГуидеДог-ова иницијализатора су експлицитно наведена


Сада имамо само 2 експлицитно специфицирана иницијализатора у свакој подкласи.

Обратите пажњу на то како иницијализатори који заобилазе погодности називају self уместо super.init .

Овај трик је детаљно објашњен у 5. поглављу књиге Свифт ин Дептх од Тјеерд ин 'т Веен, књиге коју топло препоручујем.

Интермедијарни сажетак

  • Одређени иницијализатор осигурава да су сва својства попуњена и позива super.init() .
  • Погодни иницијализатор поједностављује иницијализацију позивањем одређеног иницијализатора.
  • Иницијализатор погодности постаје недоступан подкласама ако декларишу нова својства.
  • Да бисте вратили иницијализатор погодности суперкласе, сви њени назначени иницијализатори морају бити замењени.
  • Да би се минимизирао број замењивања, може се користити иницијализатор погодности за замена .

Помоћ компајлера

Већ смо разговарали о томе да ако поткласа не уведе нове параметре, она аутоматски наслеђује све иницијализаторе суперкласе.

 class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅


Међутим, постоји још једна важна тачка: ако суперкласа има само један назначен иницијализатор и без параметара ( init() без аргумената), онда експлицитно декларисани иницијализатори у поткласи не морају да позивају super.init() . У овом случају, Свифт компајлер аутоматски убацује позив у доступни super.init() без аргумената.

 class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } }


Код се компајлира јер се super.init() имплицитно позива. Ово је кључно за неке од следећих примера.

Обавезно

required иницијализатор се користи у свим случајевима када поткласа мора имати исти иницијализатор као основна класа. Такође мора позвати super.init() . Испод су примери где је required иницијализатор неопходан.

Генерицс

Позивање init на генеричком типу могуће је само ако га декларишете као required init .

 class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer }


Овај код се не компајлира јер Factory не зна ништа о подкласама Base . Иако у овом конкретном случају, Subclass има init() без параметара, замислите да је увела ново поље:

 class Subclass: Base { let value: Int init(value: Int) { self.value = value } }


Овде више нема празан init , тако да мора бити декларисан по required .

 class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance()


Приметите да иако нисмо експлицитно декларисали required init у Subclass , компајлер га је генерисао за нас. О томе се расправљало у Цомпилер Ассистанце . required init је аутоматски наслеђен и назван super.init() .

 class Subclass: Base { required init() { super.init() } }

Протоколи

Сви иницијализатори декларисани у протоколима морају бити required :

 protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' }


Опет, ово је неопходно како би компајлер осигурао да поткласа имплементира иницијализатор протокола. Као што већ знамо, то се не дешава увек – ако init није required , поткласа није обавезна да је надјача и може да дефинише сопствени иницијализатор.

 class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init()


Наравно, следећи код се неће компајлирати јер Base.init() није required .

 class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } }

Селф()

Слична ситуација се дешава када се позива Self() иницијализатор у статичким методама.

 class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer }


Као и увек, проблем је у наслеђивању:

 class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌
 class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } }

Отклањање required : final

Пошто је сврха required да се наметне имплементација иницијализатора у подкласама, наравно, забрана наслеђивања коришћењем final кључне речи уклања потребу да се иницијализатор означи као required .

 protocol Initable { init() } final class InitableObject: Initable { } // ✅
 protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ }

Интермедијарни сажетак

  • Ако поткласа не уводи нове параметре, она аутоматски наслеђује све иницијализаторе из своје суперкласе.
  • Ако суперкласа има само init() без параметара, она се аутоматски позива у иницијализаторима поткласе.
  • required је иницијализатор да би се гарантовало његово присуство у подкласама за употребу у генерицима, протоколима и Self() .

УИВиев()

Кратак помен иницијализатора UIView() без параметара, који се не може наћи у документацији, али се мистериозно користи свуда.


Разлог је тај што UIView наслеђује од NSObject , који има init() без параметара. Стога , овај иницијализатор није експлицитно декларисан у UIView интерфејсу, али је и даље доступан:

 @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init()


Међутим, испод хаубе, овај иницијализатор позива init(frame:) када се иницијализује у коду или init(coder:) када се иницијализује преко Интерфаце Буилдер-а. Ово се дешава зато што UIView обезбеђује сопствену имплементацију NSObject.init() , што се може потврдити чињеницом да method_getImplementation враћа различите адресе за NSObject.init() и UIView.init() .

Часне похвале

Неуспешно инит

Неуспешан инит је само онај који враћа опциони

 final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } }

Енум

Енумови са сировом вредношћу добијају бесплатну init?(rawValue:)

 enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north")

Такође можете креирати прилагођени инит за енуме. Све инитс набрајања морају доделити self .

 enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } }

Завршни резиме

Покрили смо све битне аспекте иницијализатора у Свифт-у:


  • У иницијализатору, сва поља морају бити попуњена.

  • Опциона својства var су подразумевана на nil .

  • Структуре добијају бесплатни иницијализатор по члану .

  • Члански иницијализатор нестаје када се дефинише прилагођени иницијализатор.

  • Одређени иницијализатор осигурава да су сва поља попуњена и позива super.init() .

  • Погодни иницијализатор поједностављује иницијализацију позивањем одређеног иницијализатора.

  • Погодни иницијализатори увек иду хоризонтално ( self.init ), а назначени иницијализатори иду вертикално ( super.init ).

  • Иницијализатор погодности постаје недоступан подкласама ако декларишу нова својства.

  • Да бисте вратили иницијализатор погодности суперкласе, сви њени назначени иницијализатори морају бити замењени.

  • Да би се минимизирао број замењивања, може се користити иницијализатор погодности за замена .

  • Ако поткласа не уводи нове параметре, она аутоматски наслеђује све иницијализаторе из своје суперкласе.

  • Ако суперкласа има само init() без параметара, она се аутоматски позива у иницијализаторима поткласе.

  • Потребан иницијализатор обезбеђује његово присуство у подкласама за употребу у генерицима, протоколима и Self() .

  • UIView.init() позива или UIView.init(frame:) или UIView.init(coder:) .

  • Неуспешан иницијализатор враћа опциони.

  • Енумови са сировом вредношћу добијају бесплатну init?(rawValue:) .

  • Сви иницијализатори енума морају доделити self .


Надам се да сте пронашли нешто корисно у овом чланку. Ако нешто остане нејасно или нађете нетачност, слободно ме контактирајте за бесплатно објашњење на Телеграму: @кфамин .

Релевантне везе

  1. Програмски језик Свифт (6) / Иницијализација
  2. Свифт ин Дептх од Тјеерд ин 'т Веен
  3. Телеграм - @кфамин