Salom! Mening ismim Kiril Famin va men iOS dasturchisiman.
Bugun men Swift-da initsializatorlar kabi oddiy mavzuni yaxshilab ko'rib chiqmoqchiman. Ko'rinib turgan soddaligiga qaramay, ba'zida ushbu mavzuni to'liq tushunmaslik, tafsilotlarni o'rganmasdan tezda tuzatmoqchi bo'lgan umidsizlikka olib keladigan xatolarga olib keladi.
Ushbu maqolada biz initsializatorlar bilan bog'liq barcha narsalarni ko'rib chiqamiz, jumladan:
Maxsus tuzilmani belgilashda strukturaning a'zolar bo'yicha ishga tushirgichini qanday saqlash kerak
Nima uchun sinflarda initsializator yozish har doim ham kerak emas
Nima uchun super.init
qo'ng'iroq qilish har doim ham belgilangan ishga tushirgichda talab qilinmaydi
Nima uchun super.init
qo'ng'iroq qilishdan oldin kichik sinfning barcha maydonlari to'ldirilishi kerak
Pastki sinflarda minimal bekor qilingan barcha ota-initsializatorlarga qanday kirish mumkin
required
ishga tushirgich kerak bo'lganda
Nima uchun UIView.init()
har doim parametrlarsiz chaqiriladi, lekin init(frame:)
va init(coder:)
bekor qilinadi
...va boshqalar. Ammo keling, buni bosqichma-bosqich ko'rib chiqaylik.
var
vs let
required
boshlang'ich: generics, protokollar, Self()
, final
UIView()
parametrlarsizApple kompaniyasining Swift dasturlash tili (6) qo'llanmasida (aytmoqchi, initsializatorlar uchun hayratlanarli darajada batafsil) shunday deyilgan:
Initializatsiya - bu sinf, tuzilma yoki ro'yxatga olish namunasini foydalanish uchun tayyorlash jarayoni. Bu jarayon oʻsha namunadagi har bir saqlangan xususiyat uchun boshlangʻich qiymatni oʻrnatish va yangi namuna foydalanishga tayyor boʻlgunga qadar talab qilinadigan boshqa sozlash yoki ishga tushirishni oʻz ichiga oladi.
Siz ushbu ishga tushirish jarayonini ma'lum turdagi yangi namunani yaratish uchun chaqirilishi mumkin bo'lgan maxsus usullarga o'xshash initsializatorlarni belgilash orqali amalga oshirasiz. Objective-C initsializatorlaridan farqli o'laroq, Swift ishga tushirgichlari qiymat qaytarmaydi. Ularning asosiy roli yangi turdagi namunalarni birinchi marta ishlatishdan oldin to'g'ri ishga tushirishni ta'minlashdir.
Xo'sh, men bu erda hech narsa qo'shishim shart emas deb o'ylayman.
Keling, strukturani ishga tushirishni muhokama qilishdan boshlaylik. Bu juda oddiy, chunki meros yo'q, lekin siz hali ham bilishingiz kerak bo'lgan ba'zi qoidalar mavjud.
Keling, oddiy tuzilmani yozamiz:
struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool)
E'tibor bering, biz boshlang'ichni aniq e'lon qilmasdan strukturani ishga tushira oldik. Buning sababi, tuzilmalar kompilyator tomonidan yaratilgan a'zolar bo'yicha ishga tushirgichni oladi. Bu faqat tuzilmalar uchun ishlaydi.
Refactor → Generate Memberwise initializer ni tanlab, uning qanday ko'rinishini ko'rishingiz mumkin:
init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked }
Imzodan hamma parametrlar uchun qiymatlarni bermaslik kompilyatsiya xatosiga olib kelishini tushunish oson:
let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call
Biroq, agar siz talab qilinadigan argumentlar sonini kamaytirmoqchi bo'lsangiz, maxsus ishga tushirgichni belgilashingiz mumkin:
init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅
Esda tutingki, agar isBlocked
to'ldirilmagan bo'lsa, bu kompilyatsiya xatosiga olib keladi, chunki barcha struktura xususiyatlari initsializerda to'ldirilishi kerak .
var
vs let
Maydonni aniq to'ldirish shart bo'lmagan yagona holat - bu ixtiyoriy ( ?
) o'zgaruvchi ( var
). Bunday hollarda maydon sukut bo'yicha nil
ga aylanadi:
struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅
Ammo, agar biz bu holatda a'zolar bo'yicha ishga tushirgichdan foydalanishga harakat qilsak, biz kompilyatsiya xatosini olamiz:
let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call
Buning sababi, maxsus ishga tushirgichni e'lon qilish a'zolar bo'yicha ishga tushirgichni olib tashlaydi. Uni aniq belgilash hali ham mumkin, lekin u avtomatik ravishda mavjud bo'lmaydi.
Biroq, a'zolar bo'yicha ishga tushirgichni saqlab qolish uchun kichik hiyla bor: extension
maxsus ishga tushirgichni e'lon qilish .
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
Tuzilmalar uchun xulosa
var
maydonlari sukut bo'yicha nil
extension
moslashtirilganni belgilangSinf uchun asosiy ishga tushirgich belgilangan ishga tushirgichdir . U ikkita maqsadga xizmat qiladi:
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
ga qo'ng'iroq qilishdan oldin barcha maydonlar to'ldirilishi kerak . Buning sababi shundaki, superklassni ishga tushiruvchi subklass tomonidan bekor qilingan usullarni chaqirishi mumkin, bu esa to'ldirilmagan pastki sinf xususiyatlariga kirishi mumkin.
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) } }
Shunday qilib, agar self.breed = breed
o'rnatmaganimizda, biz ish vaqti xatosiga duch kelgan bo'lardik, chunki Animal
initsializer Dog
sinfidan bekor qilingan getInfo()
usulini chaqirgan bo'lar edi. Bu usul hali to'ldirilmagan breed
mulkiga kirishga harakat qiladi.
Tuzilmalardan farqli o'laroq, sinflar yashirin a'zolar inisializerini olmaydilar. Agar ishga tushirilmagan xususiyatlar mavjud bo'lsa, kompilyatsiya xatosi yuzaga keladi:
class Animal { // ❌ Class 'Animal' has no initializers var age: Int }
class Animal { // ✅ var age: Int = 0 }
class Animal { // ✅ var age: Int? }
class Animal { } // ✅
Sinflar, shuningdek, qulaylik yaratuvchisiga ega bo'lishi mumkin. Belgilangan initsializatorlardan farqli o'laroq, ular noldan ob'ekt yaratmaydi, balki boshqa initsializatorlarning mantig'ini qayta ishlatish orqali ishga tushirish jarayonini soddalashtiradi.
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 } }
Qulaylik initsializatorlari tayinlangan ishga tushirgichlarni yoki boshqa qulaylik ishga tushirgichlarini chaqirishi mumkin. Oxir-oqibat, belgilangan ishga tushiruvchi har doim chaqiriladi.
Qulaylik initsializatorlari har doim gorizontal (self.init) va belgilangan ishga tushirgichlar vertikal (super.init) bo'ladi.
Subklass yangi xususiyatlarni e'lon qilishi bilanoq, u supersinfning barcha qulaylik ishga tushirgichlariga kirish huquqini yo'qotadi.
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
Bu supersinfning barcha belgilangan initsializatorlarini bekor qilish orqali tuzatilishi mumkin.
class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅
Ko'rinib turibdiki, shu tarzda keyingi kichik sinfda qulaylik ishga tushirgichdan foydalanish uchun ikkita initsializatorni bekor qilishingiz kerak.
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) // ✅
Biroq, buning oldini olish uchun qulaylikni bekor qilish ishga tushirgichidan foydalanish mumkin.
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) // ✅
Endi bizda har bir kichik sinfda atigi 2 ta aniq ko'rsatilgan boshlang'ich mavjud.
E'tibor bering, qulaylikni bekor qilish initsializatorlari super.init
o'rniga self
tayinlangan init deb atashadi.
Bu hiyla Tjeerd in 't Veen tomonidan "Swift in Depth" kitobining 5-bobida batafsil tushuntirilgan, men buni juda tavsiya qilaman.
super.init()
chaqiradi.Agar quyi sinf yangi parametrlarni kiritmasa, u avtomatik ravishda barcha supersinf initsializatorlarini meros qilib olishini allaqachon muhokama qilgan edik.
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) // ✅
Biroq, yana bir muhim jihat bor: agar supersinfda faqat bitta tayinlangan initsializer bo'lsa va u parametrsiz bo'lsa ( init()
argumentlarsiz), unda subklassdagi aniq e'lon qilingan initsializatorlar super.init()
chaqirishga hojat yo'q . Bunday holda, Swift kompilyatori qo'ng'iroqni avtomatik ravishda mavjud super.init()
ga argumentlarsiz kiritadi .
class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } }
Kod kompilyatsiya qilinadi, chunki super.init()
bilvosita chaqiriladi. Bu quyidagi misollarning ba'zilari uchun juda muhimdir.
required
initsializer quyi sinfda asosiy sinf bilan bir xil boshlang'ichga ega bo'lishi kerak bo'lgan barcha holatlarda qo'llaniladi. Bundan tashqari, super.init()
ni chaqirishi kerak. Quyida required
ishga tushirgich zarur bo'lgan misollar keltirilgan.
Umumiy turdagi init
chaqirish faqat uni required init
deb e'lon qilish orqali mumkin.
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 }
Ushbu kod kompilyatsiya qilinmaydi, chunki Factory
Base
pastki sinflari haqida hech narsa bilmaydi. Garchi bu alohida holatda, Subclass
parametrlarsiz init()
mavjud bo'lsa-da, u yangi maydonni kiritganligini tasavvur qiling:
class Subclass: Base { let value: Int init(value: Int) { self.value = value } }
Bu erda endi bo'sh init
yo'q, shuning uchun uni required
e'lon qilish kerak.
class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance()
E'tibor bering, biz Subclass
required init
aniq e'lon qilmagan bo'lsak ham, kompilyator uni biz uchun yaratdi. Bu Compiler Assistance da muhokama qilindi. required init
avtomatik ravishda meros qilib olinadi va super.init()
deb nomlanadi.
class Subclass: Base { required init() { super.init() } }
Protokollarda e'lon qilingan barcha ishga tushirgichlar required
qilinishi kerak:
protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' }
Shunga qaramay, bu kompilyator pastki sinf protokolni ishga tushirishni amalga oshirishini ta'minlashi uchun kerak. Biz allaqachon bilganimizdek, bu har doim ham sodir bo'lmaydi - agar init
required
bo'lmasa, pastki sinf uni bekor qilishga majbur emas va o'zining boshlang'ichini belgilashi mumkin.
class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init()
Albatta, quyidagi kod kompilyatsiya qilinmaydi, chunki Base.init()
required
emas.
class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } }
Xuddi shunday holat Self()
initsializatorini statik usullarda chaqirganda ham yuzaga keladi.
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 }
Har doimgidek, masala merosga bog'liq:
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
bo'lgandan qutulish: final
required
maqsadi pastki sinflarda initsializatorni amalga oshirishni ta'minlash bo'lgani uchun, tabiiyki, final
kalit so'zidan foydalanib merosni taqiqlash, boshlang'ichni required
holda belgilash zaruratini olib tashlaydi.
protocol Initable { init() } final class InitableObject: Initable { } // ✅
protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ }
init()
bo'lsa, u subsinf initsializatorlarida avtomatik ravishda chaqiriladi.Self()
da foydalanish uchun pastki sinflarda mavjudligini kafolatlash uchun required
ishga tushirgich kerak. Hujjatlarda topib bo'lmaydigan, lekin hamma joyda sirli ravishda qo'llaniladigan parametrlarsiz UIView()
ishga tushirgichi haqida qisqacha eslatma.
Sababi, UIView
parametrsiz init()
ega bo'lgan NSObject
dan meros bo'lib o'tadi. Shuning uchun , bu ishga tushirgich UIView
interfeysida aniq e'lon qilinmagan, ammo u hali ham mavjud:
@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()
Biroq, bu ishga tushirgich kodda ishga tushirilganda init(frame:)
yoki Interface Builder orqali ishga tushirilganda init(coder:)
chaqiradi. Buning sababi, UIView
o'zining NSObject.init()
ilovasini ta'minlaydi, buni method_getImplementation
NSObject.init()
va UIView.init()
uchun turli manzillarni qaytarishi bilan tasdiqlanishi mumkin.
Muvaffaqiyatsiz init - bu ixtiyoriyni qaytaradigan
final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } }
Xom qiymatga ega bo'lgan raqamlar bepul init?(rawValue:)
enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north")
Enumlar uchun maxsus init ham yaratishingiz mumkin. Barcha enum initslari self
belgilashi kerak.
enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } }
Biz Swift-da initsializatorlarning barcha muhim jihatlarini ko'rib chiqdik:
Initsializatorda barcha maydonlar to'ldirilishi kerak.
Ixtiyoriy var
xususiyatlari sukut bo'yicha nil
teng.
Strukturalar bepul a'zo bo'yicha ishga tushirgich oladi.
Maxsus ishga tushirgich aniqlanganda a'zolar bo'yicha ishga tushirgich yo'qoladi .
Belgilangan ishga tushirgich barcha maydonlarni to'ldirishni ta'minlaydi va super.init()
ni chaqiradi.
Qulaylik ishga tushirgichi belgilangan ishga tushirgichni chaqirish orqali ishga tushirishni osonlashtiradi.
Qulaylik initsializatorlari har doim gorizontal ( self.init
) va belgilangan ishga tushirgichlar vertikal ( super.init
) bo'ladi.
Qulaylik ishga tushiruvchisi, agar ular yangi xususiyatlarni e'lon qilsalar, pastki sinflar uchun mavjud bo'lmaydi.
Supersinfning qulaylik ishga tushirgichini tiklash uchun uning barcha belgilangan initsializatorlari bekor qilinishi kerak.
Qayta belgilashlar sonini kamaytirish uchun qulaylikni bekor qilish boshlang'ichidan foydalanish mumkin.
Agar kichik sinf yangi parametrlarni kiritmasa, u o'zining yuqori sinfidagi barcha boshlang'ichlarni avtomatik ravishda meros qilib oladi.
Agar supersinfda faqat parametrlarsiz init()
bo'lsa, u avtomatik ravishda subsinf initsializatorlarida chaqiriladi.
Kerakli initsializator uning generiklar, protokollar va Self()
da foydalanish uchun quyi sinflarda mavjudligini ta'minlaydi.
UIView.init()
UIView.init(frame:)
yoki UIView.init(coder:)
chaqiradi.
Muvaffaqiyatsiz ishga tushirgich ixtiyoriyni qaytaradi.
Xom qiymatga ega bo'lgan enumlar bepul init?(rawValue:)
.
Barcha enum boshlang'ichlari self
belgilashi kerak.
Umid qilamanki, siz ushbu maqolada foydali narsalarni topdingiz. Agar biror narsa noaniq bo'lib qolsa yoki siz noaniqlik topsangiz, Telegram orqali bepul tushuntirish uchun men bilan bog'laning: @kfamyn .