Увод Здраво! Моје име је Кирил Фамин, и ја сам иОС програмер. Данас желим да детаљно испитам тако једноставну тему као што су иницијализатори у Свифту. Упркос привидној једноставности, понекад недостатак разумевања ове теме доводи до фрустрирајућих грешака које се жели брзо поправити без упуштања у детаље. потпуног У овом чланку ћемо покрити све у вези са иницијализаторима, укључујући: Како задржати члански иницијализатор структуре док дефинишете прилагођени Зашто није увек потребно писати иницијализатор у класама Зашто позивање није увек потребно у одређеном иницијализатору 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 класе Одређени иницијализатор Примарни иницијализатор за класу је . Служи у две сврхе: назначени иницијализатор Осигурава да су сва поља попуњена Ако је класа наслеђена, она позива иницијализатор суперкласе 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 Надам се да сте пронашли нешто корисно у овом чланку. Ако нешто остане нејасно или нађете нетачност, слободно ме контактирајте за бесплатно објашњење на Телеграму: . @кфамин Релевантне везе Програмски језик Свифт (6) / Иницијализација Свифт ин Дептх од Тјеерд ин 'т Веен Телеграм - @кфамин