Uvod Zdravo! Moje ime je Kiryl Famin i ja sam iOS programer. Danas želim temeljito proučiti tako jednostavnu temu kao što su inicijalizatori u Swiftu. Unatoč prividnoj jednostavnosti, ponekad nedostatak razumijevanja ove teme dovodi do frustrirajućih pogrešaka koje se želi brzo popraviti bez zalaženja u detalje. potpunog U ovom ćemo članku pokriti sve što se odnosi na inicijalizatore, uključujući: Kako zadržati inicijalizator strukture po članu dok definirate prilagođeni Zašto nije uvijek potrebno pisati inicijalizator u klasama Zašto pozivanje nije uvijek potrebno u određenom inicijalizatoru super.init Zašto sva polja podklase moraju biti popunjena prije pozivanja super.init Kako pristupiti svim nadređenim inicijalizatorima uz minimalna nadjačavanja u podklasama Kada je točno potreban inicijalizator required Zašto se uvijek poziva bez parametara, ali i nadjačani UIView.init() init(frame:) init(coder:) ...i više. Ali idemo korak po korak. Sadržaj Osnove Strukture Inicijalizator po članu Mogućnosti vs var let Zadržavanje inicijalizatora po članu Nastava Određeni inicijalizator Praktičan inicijalizator Zadržavanje prikladnog inicijalizatora superklase Smanjivanje broja nadjačavanja Pomoć kompilatora inicijalizator: generički, protokoli, , required Self() final bez parametara UIView() Časna priznanja Neuspješno pokretanje Nabrajanja Sažetak Relevantne poveznice Osnove Appleov vodič (koji je, usput rečeno, iznenađujuće detaljan za inicijalizatore) navodi: The Swift Programming Language (6) je proces pripreme instance klase, strukture ili enumeracije za upotrebu. Ovaj proces uključuje postavljanje početne vrijednosti za svako pohranjeno svojstvo na toj instanci i izvođenje bilo kojeg drugog postavljanja ili inicijalizacije koji su potrebni prije nego što nova instanca bude spremna za upotrebu. Inicijalizacija Ovaj proces inicijalizacije implementirate definiranjem , koji su poput posebnih metoda koje se mogu pozvati za stvaranje nove instance određenog tipa. Za razliku od Objective-C inicijalizatora, Swift inicijalizatori ne vraćaju vrijednost. Njihova primarna uloga je osigurati da su nove instance tipa ispravno inicijalizirane prije nego što se koriste po prvi put. inicijalizatora Pa, pretpostavljam da ne moram ništa dodati ovdje. Strukture Počnimo s raspravom o inicijalizatorima strukture. Ovo je prilično jednostavno jer nema nasljeđivanja, ali ipak postoje neka pravila koja morate znati. Inicijalizator po članu Napišimo jednostavnu strukturu: struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool) Primijetite da smo mogli inicijalizirati strukturu bez eksplicitnog deklariranja inicijalizatora. To se događa zato što strukture primaju koji generira kompilator. Ovo radi . inicijalizator po članu samo za strukture Odabirom možete vidjeti kako to izgleda: Refactor → Generate memberwise initializer init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked } Iz potpisa je lako vidjeti da će nenavođenje vrijednosti za sve parametre rezultirati pogreškom kompilacije: let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call Međutim, ako želite smanjiti broj potrebnih argumenata, možete definirati prilagođeni inicijalizator: init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅ Imajte na umu da ako nije popunjen, to će rezultirati pogreškom kompilacije jer . isBlocked sva svojstva strukture moraju biti popunjena u inicijalizatoru vs Optionals, var let Jedini slučaj u kojem se polje ne mora popuniti je kada je ( ) ( ). U takvim slučajevima, polje će biti zadano postavljeno na : eksplicitno opcijska ? varijabla var nil struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅ Međutim, ako pokušamo upotrijebiti inicijalizator po članu u ovom slučaju, dobit ćemo pogrešku kompilacije: let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call Zadržavanje inicijalizatora po članu To se događa jer se deklariranjem prilagođenog inicijalizatora uklanja inicijalizator po članu. I dalje ga je moguće eksplicitno definirati, ali neće biti automatski dostupan. Međutim, postoji mali trik za zadržavanje inicijalizatora po članu: deklarirajte prilagođeni inicijalizator u . 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 Sažetak za strukture Sva polja moraju biti popunjena u inicijalizatoru Neobavezna var polja zadana su na nil Strukture dobivaju besplatni inicijalizator po članu Inicijalizator po članu nestaje ako se deklarira prilagođeni inicijalizator Da biste zadržali inicijalizator strukture po članu, definirajte prilagođeni u extension Nastava Određeni inicijalizator Primarni inicijalizator za klasu je . Služi u dvije svrhe: naznačeni inicijalizator Osigurava da su sva polja popunjena Ako je klasa naslijeđena, ona poziva inicijalizator superklase 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) } } Sva polja pozivanja . To je zato što inicijalizator superklase može pozvati metode nadjačane od strane podklase, koje bi mogle pristupiti nepopunjenim svojstvima podklase. moraju biti popunjena prije 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) } } Dakle, da nismo postavili , naišli bismo na pogrešku vremena izvođenja jer bi inicijalizator pozvao nadjačanu metodu iz klase . Ova metoda pokušava pristupiti svojstvu , koje još nije popunjeno. self.breed = breed Animal getInfo() Dog breed Za razliku od struktura, klase ne primaju implicitni inicijalizator po članu. Ako postoje neinicijalizirana svojstva, javlja se pogreška kompilacije: class Animal { // ❌ Class 'Animal' has no initializers var age: Int } class Animal { // ✅ var age: Int = 0 } class Animal { // ✅ var age: Int? } class Animal { } // ✅ Praktičan inicijalizator Klase također mogu imati . Za razliku od određenih inicijalizatora, oni ne stvaraju objekt od nule, već pojednostavljuju proces inicijalizacije ponovnim korištenjem logike drugih inicijalizatora. praktični inicijalizator 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 } } Pogodni inicijalizatori mogu pozivati ili naznačene inicijalizatore ili druge prikladne inicijalizatore. U konačnici, uvijek će se pozivati određeni inicijalizator. Pogodni inicijalizatori uvijek idu vodoravno (self.init), a naznačeni inicijalizatori idu okomito (super.init). Zadržavanje praktičnog inicijalizatora superklase Čim podklasa deklarira nova svojstva, gubi pristup svim pomoćnim inicijalizatorima superklase. 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 To se može popraviti nadjačavanjem . svih naznačenih inicijalizatora superklase class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ Lako je vidjeti da, na ovaj način, za korištenje pomoćnog inicijalizatora u sljedećoj podklasi, morate nadjačati inicijalizatora. dva 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) // ✅ Smanjivanje broja nadjačavanja Međutim, to se može izbjeći upotrebom . inicijalizatora za praktično nadjačavanje 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) // ✅ Sada imamo samo 2 eksplicitno navedena inicijalizatora u svakoj podklasi. init umjesto . Primijetite kako inicijalizatori nadjačavanja pogodnosti nazivaju self super.init Ovaj trik je temeljito objašnjen u 5. poglavlju knjige autora Tjeerda in 't Veena, knjige koju toplo preporučujem. Swift in Depth Međusažetak osigurava da su sva svojstva popunjena i poziva . Određeni inicijalizator super.init() pojednostavljuje inicijalizaciju pozivanjem određenog inicijalizatora. Pogodni inicijalizator postaje nedostupan podklasama ako deklariraju nova svojstva. Pogodni inicijalizator Za vraćanje superklase, svi njegovi naznačeni inicijalizatori moraju biti nadjačani. prikladnog inicijalizatora Kako bi se broj nadjačavanja sveo na najmanju moguću mjeru, može se koristiti . praktični inicijalizator nadjačavanja Pomoć kompilatora Već smo raspravljali da ako potklasa ne uvodi nove parametre, ona automatski nasljeđuje sve inicijalizatore superklase. 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) // ✅ Međutim, postoji još jedna važna točka: ako superklasa ima samo jedan naznačeni inicijalizator i on je bez parametara ( bez argumenata), tada eksplicitno deklarirani inicijalizatori u podklasi pozivati . U ovom slučaju, prevodilac Swift poziv na dostupni bez argumenata. init() ne moraju super.init() automatski umeće super.init() class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } Kod se kompilira jer se implicitno poziva. Ovo je ključno za neke od sljedećih primjera. super.init() Potreban inicijalizator se koristi u svim slučajevima kada podklasa mora imati isti inicijalizator kao osnovna klasa. Također mora pozvati . Ispod su primjeri u kojima je inicijalizator. required super.init() required Generici Pozivanje na generičkom tipu moguće je samo deklariranjem kao . 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 } Ovaj kod se ne kompajlira jer ne zna ništa o podklasama . Iako u ovom konkretnom slučaju, ima bez parametara, zamislite da uvodi novo polje: Factory Base Subclass init() class Subclass: Base { let value: Int init(value: Int) { self.value = value } } Ovdje više nema prazno , pa se mora deklarirati kao . init required class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance() Primijetite da iako nismo eksplicitno deklarirali u , prevodilac ga je generirao za nas. O tome se raspravljalo u . je automatski naslijeđen i pozvan . required init Subclass pomoći za kompajler required init super.init() class Subclass: Base { required init() { super.init() } } Protokoli Svi inicijalizatori deklarirani u protokolima moraju biti : required protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' } Opet, ovo je neophodno kako bi prevodilac osigurao da podklasa implementira inicijalizator protokola. Kao što već znamo, to se ne događa uvijek—ako nije , podklasa ga nije obavezna nadjačati i može definirati vlastiti inicijalizator. 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() Naravno, sljedeći kod se neće kompilirati jer nije . 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() Slična situacija se događa kada se poziva inicijalizator u statičkim metodama. 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 } Kao i uvijek, problem je u nasljeđu: 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) } } Oslobađanje od : required final Budući da je svrha nametnuti implementaciju inicijalizatora u potklasama, naravno, zabrana nasljeđivanja pomoću ključne riječi uklanja potrebu za označavanjem inicijalizatora kao . required final required protocol Initable { init() } final class InitableObject: Initable { } // ✅ protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ } Međusažetak Ako podklasa ne uvodi nove parametre, ona automatski nasljeđuje sve inicijalizatore iz svoje nadklase. Ako nadklasa ima samo bez parametara, automatski se poziva u inicijalizatorima podklase. init() je inicijalizator kako bi se zajamčila njegova prisutnost u potklasama za korištenje u generičkim oblicima, protokolima i . required Self() UIView() Kratko spominjanje inicijalizatora bez parametara, koji se ne može pronaći u dokumentaciji, ali se misteriozno koristi posvuda. UIView() Razlog je što nasljeđuje od , koji ima bez parametara. ovaj inicijalizator nije eksplicitno deklariran u sučelju, ali je još uvijek dostupan: UIView NSObject init() Stoga 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() Međutim, ispod haube, ovaj inicijalizator poziva kada se inicijalizira u kodu ili kada se inicijalizira putem Interface Buildera. To se događa jer pruža vlastitu implementaciju , što se može potvrditi činjenicom da vraća različite adrese za i . init(frame:) init(coder:) UIView NSObject.init() method_getImplementation NSObject.init() UIView.init() Časna priznanja Neuspješno pokretanje Init koji nije uspio je samo onaj koji vraća opciju final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } } Enum Enumi sa sirovom vrijednošću dobivaju besplatno init?(rawValue:) enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north") Također možete stvoriti prilagođeni init za enume. Svi enum initi moraju dodijeliti . self enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } } Konačni sažetak Pokrili smo sve bitne aspekte inicijalizatora u Swiftu: U inicijalizatoru sva polja moraju biti popunjena. Izborna svojstva zadana su na . var nil Strukture dobivaju besplatni . inicijalizator po članu kada se definira prilagođeni inicijalizator. Inicijalizator po članu nestaje osigurava da su sva polja popunjena i poziva . Određeni inicijalizator super.init() pojednostavljuje inicijalizaciju pozivanjem određenog inicijalizatora. Pogodni inicijalizator Pogodni inicijalizatori uvijek idu ( ), a naznačeni inicijalizatori idu ( ). vodoravno self.init okomito super.init postaje nedostupan podklasama ako deklariraju nova svojstva. Pogodni inicijalizator Za vraćanje superklase, svi njegovi naznačeni inicijalizatori moraju biti nadjačani. prikladnog inicijalizatora Kako bi se broj nadjačavanja sveo na najmanju moguću mjeru, može se koristiti . praktični inicijalizator nadjačavanja Ako podklasa ne uvodi nove parametre, ona automatski nasljeđuje sve inicijalizatore iz svoje nadklase. Ako nadklasa ima samo bez parametara, automatski se poziva u inicijalizatorima podklase. init() osigurava svoju prisutnost u podklasama za korištenje u generičkim oblicima, protokolima i . Potreban inicijalizator Self() poziva ili . UIView.init() UIView.init(frame:) UIView.init(coder:) vraća opciju. Neuspjeli inicijalizator Enumi sa sirovom vrijednošću dobivaju besplatno . init?(rawValue:) Svi inicijalizatori enuma moraju dodijeliti . self Nadam se da ste u ovom članku pronašli nešto korisno. Ako nešto ostane nejasno ili pronađete netočnost, slobodno me kontaktirajte za besplatno objašnjenje na Telegramu: . @kfamyn Relevantne poveznice Programski jezik Swift (6) / Inicijalizacija Swift in Depth autora Tjeerda in 't Veena Telegram - @kfamyn