Протоколът е много мощна характеристика на Бърз език за програмиране .
колко голяма е индустрията за красота
Протоколи се използват за определяне на „план на методи, свойства и други изисквания, които отговарят на определена задача или функционалност“.
Swift проверява за проблеми със съответствието на протокола по време на компилация, позволявайки на разработчиците да открият някои фатални грешки в кода, дори преди да стартират програмата. Протоколите позволяват на разработчиците да пишат гъвкав и разширяем код в Swift, без да се налага да компрометират изразителността на езика.
Swift използва удобството при използването на протоколи още една стъпка, като предоставя заобиколни решения на някои от най-често срещаните странности и ограничения на интерфейсите, които тормозят много други езици за програмиране.
В по-ранните версии на Swift беше възможно само да се разширят класове, структури и преброявания, както е вярно в много съвременни езици за програмиране. От версия 2 на Swift обаче стана възможно да се разширят и протоколите.
Тази статия разглежда как протоколите в Swift могат да се използват за писане на код за многократна употреба и поддръжка и как промените в голяма кодова база, ориентирана към протокол, могат да бъдат консолидирани на едно място чрез използването на разширения на протокола.
Какво е протокол?
В най-простата си форма протоколът е интерфейс, който описва някои свойства и методи. Всеки тип, който отговаря на протокол, трябва да попълни специфичните свойства, дефинирани в протокола, с подходящи стойности и да приложи необходимите му методи. Например:
protocol Queue { var count: Int { get } mutating func push(_ element: Int) mutating func pop() -> Int }
Протоколът за опашка описва опашка, която съдържа цели числа. Синтаксисът е съвсем ясен.
Вътре в блока на протокола, когато описваме свойство, трябва да посочим дали свойството може да се получи само { get }
или както за получаване, така и за настройка { get set }
. В нашия случай променливата Count (от тип Int
) е само за получаване.
Ако протоколът изисква свойство да бъде достъпно и зададено, това изискване не може да бъде изпълнено от постоянно съхранявано свойство или изчислено свойство само за четене.
Ако протоколът изисква само свойство да бъде достъпно, изискването може да бъде изпълнено от всякакъв вид свойства и е валидно свойството също да може да бъде зададено, ако това е полезно за вашия собствен код.
За функциите, дефинирани в протокол, е важно да посочите дали функцията ще промени съдържанието с mutating
ключова дума. Освен това подписът на функция е достатъчен като дефиниция.
За да се съобрази с протокол, типът трябва да предоставя всички свойства на екземпляра и да прилага всички методи, описани в протокола. По-долу например има структура Container
което отговаря на нашите Queue
протокол. Структурата по същество съхранява push Int
s в частен масив items
.
struct Container: Queue { private var items: [Int] = [] var count: Int { return items.count } mutating func push(_ element: Int) { items.append(element) } mutating func pop() -> Int { return items.removeFirst() } }
Сегашният ни протокол за опашка обаче има голям недостатък.
Само контейнери, които се занимават с Int
s, могат да отговарят на този протокол.
Можем да премахнем това ограничение, като използваме функцията „асоциирани типове“. Свързаните типове работят като генерични лекарства. За да демонстрираме, нека променим протокола на опашката, за да използваме свързани типове:
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Сега протоколът Queue позволява съхранението на всякакви вид елементи.
При изпълнението на Container
структура, компилаторът определя свързания тип от контекста (т.е. тип на връщане на метода и типове параметри). Този подход ни позволява да създадем Container
структура с родов тип елементи. Например:
class Container: Queue { private var items: [Item] = [] var count: Int { return items.count } func push(_ element: Item) { items.append(element) } func pop() -> Item { return items.removeFirst() } }
Използването на протоколи опростява писането на код в много случаи.
Например всеки обект, който представлява грешка, може да съответства на Error
(или LocalizedError
, в случай че искаме да предоставим локализирани описания) протокол.
След това същата логика за обработка на грешки може да се приложи към всеки от тези обекти за грешки в целия ви код. Следователно не е необходимо да използвате какъвто и да е конкретен обект (като NSError в Objective-C) за представяне на грешки, можете да използвате всеки тип, който съответства на Error
или LocalizedError
протоколи.
Можете дори да разширите типа String, за да го приведете в съответствие с LocalizedError
протокол и хвърляне на низове като грешки.
extension String: LocalizedError { public var errorDescription: String? { Return NSLocalizedString(self, comment:””) } } throw “Unfortunately something went wrong” func handle(error: Error) { print(error.localizedDescription) }
Разширенията на протоколите се основават на страхотността на протоколите. Те ни позволяват да:
Осигурете изпълнение по подразбиране на методите на протокола и стойностите по подразбиране на свойствата на протокола, като по този начин ги правите „по избор“. Типовете, които съответстват на протокол, могат да предоставят свои собствени реализации или да използват тези по подразбиране.
Добавете внедряване на допълнителни методи, които не са описани в протокола, и „украсете“ всички типове, които съответстват на протокола, с тези допълнителни методи. Тази функция ни позволява да добавяме специфични методи към множество типове, които вече съответстват на протокола, без да се налага да модифицираме всеки тип поотделно.
Нека създадем още един протокол:
protocol ErrorHandler { func handle(error: Error) }
Този протокол описва обекти, които отговарят за обработката на грешки, възникващи в приложение. Например:
struct Handler: ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Тук просто отпечатваме локализираното описание на грешката. С разширението на протокола можем да направим това изпълнение по подразбиране.
extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Правейки това прави handle
метод по избор чрез предоставяне на изпълнение по подразбиране.
Възможността за разширяване на съществуващ протокол с поведение по подразбиране е доста мощна, позволявайки на протоколите да растат и да се разширяват, без да се притеснявате за нарушаване на съвместимостта на съществуващия код.
калкулатор на часова ставка за независим изпълнител
Затова предоставихме изпълнение по подразбиране на handle
метод, но отпечатването на конзолата не е особено полезно за крайния потребител.
Вероятно бихме предпочели да им покажем някакъв изглед с предупреждение с локализирано описание в случаите, когато манипулаторът на грешки е контролер на изглед. За целта можем да разширим ErrorHandler
протокол, но може да ограничи разширението да се прилага само за определени случаи (т.е. когато типът е контролер на изгледа).
Swift ни позволява да добавяме такива условия към разширенията на протокола, използвайки where
ключова дума.
extension ErrorHandler where Self: UIViewController { func handle(error: Error) { let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) let action = UIAlertAction(title: 'OK', style: .cancel, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } }
Аз (с главна буква „S“) в кодовия фрагмент по-горе се отнася до типа (структура, клас или изброяване). Като посочваме, че ние разширяваме протокола само за типове, които се наследяват от UIViewController
, ние можем да използваме UIViewController
специфични методи (като present(viewControllerToPresnt: animated: completion)
).
Сега всички контролери за изглед, които съответстват на ErrorHandler
протокол имат собствено изпълнение по подразбиране на handle
метод, който показва изглед на предупреждение с локализирано описание.
Да приемем, че има два протокола, и двата имат метод с еднакъв подпис.
protocol P1 { func method() //some other methods } protocol P2 { func method() //some other methods }
И двата протокола имат разширение с изпълнение по подразбиране на този метод.
extension P1 { func method() { print('Method P1') } } extension P2 { func method() { print('Method P2') } }
Сега нека приемем, че има тип, който съответства и на двата протокола.
struct S: P1, P2 { }
В този случай имаме проблем с неясното изпълнение на метода. Типът не посочва ясно коя реализация на метода трябва да използва. В резултат на това получаваме грешка при компилацията. За да поправим това, трябва да добавим изпълнението на метода към типа.
struct S: P1, P2 { func method() { print('Method S') } }
Много обектно-ориентирани езици за програмиране са изложени на ограничения, свързани с разрешаването на двусмислени дефиниции на разширения. Swift се справя с това доста елегантно чрез разширения на протокола, като позволява на програмиста да поеме контрола там, където компилаторът не достига.
Нека да разгледаме Queue
протокол още веднъж.
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Всеки тип, който съответства на Queue
протоколът има count
екземпляр свойство, което определя броя на съхранените елементи. Това ни позволява, наред с други неща, да сравняваме такива типове, за да решим кой е по-голям. Можем да добавим този метод чрез разширение на протокола.
extension Queue { func compare(queue: Q) -> ComparisonResult where Q: Queue { if count queue.count { return .orderedAscending } return .orderedSame } }
Този метод не е описан в Queue
самият протокол, защото не е свързан с функционалността на опашката.
Следователно това не е изпълнение по подразбиране на метода на протокола, а по-скоро е реализация на нов метод, който „декорира“ всички типове, които съответстват на Queue
протокол. Без разширения на протокола ще трябва да добавим този метод към всеки тип поотделно.
Разширенията на протоколи може да изглеждат доста подобни на използването на основен клас, но има няколко предимства от използването на разширения на протокол. Те включват, но не са задължително ограничени до:
Тъй като класовете, структурите и преброяванията могат да отговарят на повече от един протокол, те могат да приемат изпълнението по подразбиране на множество протоколи. Това е концептуално подобно на множественото наследяване на други езици.
Протоколите могат да се приемат от класове, структури и изброявания, докато базовите класове и наследяването са достъпни само за класове.
В допълнение към разширяването на вашите собствени протоколи, можете да разширите протоколите от стандартната библиотека Swift. Например, ако искаме да намерим средния размер на колекцията от опашки, можем да го направим, като разширим стандарта Collection
протокол.
Структурите от данни на последователността, предоставени от стандартната библиотека на Swift, чиито елементи могат да бъдат преместени и достъпни чрез индексиран индекс, обикновено съответстват на Collection
протокол. Чрез удължаване на протокол е възможно да се разширят всички такива стандартни структури от библиотечни данни или да се разширят няколко от тях селективно.
Забележка: Протоколът, известен преди като
CollectionType
в Swift 2.x е преименуван наCollection
в Суифт 3.
extension Collection where Iterator.Element: Queue { func avgSize() -> Int { let size = map { Въведение в програмирането, ориентирано към протокола в Swift
Протоколът е много мощна характеристика на Бърз език за програмиране .
Протоколи се използват за определяне на „план на методи, свойства и други изисквания, които отговарят на определена задача или функционалност“.
Swift проверява за проблеми със съответствието на протокола по време на компилация, позволявайки на разработчиците да открият някои фатални грешки в кода, дори преди да стартират програмата. Протоколите позволяват на разработчиците да пишат гъвкав и разширяем код в Swift, без да се налага да компрометират изразителността на езика.
Swift използва удобството при използването на протоколи още една стъпка, като предоставя заобиколни решения на някои от най-често срещаните странности и ограничения на интерфейсите, които тормозят много други езици за програмиране.
В по-ранните версии на Swift беше възможно само да се разширят класове, структури и преброявания, както е вярно в много съвременни езици за програмиране. От версия 2 на Swift обаче стана възможно да се разширят и протоколите.
Тази статия разглежда как протоколите в Swift могат да се използват за писане на код за многократна употреба и поддръжка и как промените в голяма кодова база, ориентирана към протокол, могат да бъдат консолидирани на едно място чрез използването на разширения на протокола.
Какво е протокол?
В най-простата си форма протоколът е интерфейс, който описва някои свойства и методи. Всеки тип, който отговаря на протокол, трябва да попълни специфичните свойства, дефинирани в протокола, с подходящи стойности и да приложи необходимите му методи. Например:
protocol Queue { var count: Int { get } mutating func push(_ element: Int) mutating func pop() -> Int }
Протоколът за опашка описва опашка, която съдържа цели числа. Синтаксисът е съвсем ясен.
Вътре в блока на протокола, когато описваме свойство, трябва да посочим дали свойството може да се получи само { get }
или както за получаване, така и за настройка { get set }
. В нашия случай променливата Count (от тип Int
) е само за получаване.
Ако протоколът изисква свойство да бъде достъпно и зададено, това изискване не може да бъде изпълнено от постоянно съхранявано свойство или изчислено свойство само за четене.
Ако протоколът изисква само свойство да бъде достъпно, изискването може да бъде изпълнено от всякакъв вид свойства и е валидно свойството също да може да бъде зададено, ако това е полезно за вашия собствен код.
За функциите, дефинирани в протокол, е важно да посочите дали функцията ще промени съдържанието с mutating
ключова дума. Освен това подписът на функция е достатъчен като дефиниция.
За да се съобрази с протокол, типът трябва да предоставя всички свойства на екземпляра и да прилага всички методи, описани в протокола. По-долу например има структура Container
което отговаря на нашите Queue
протокол. Структурата по същество съхранява push Int
s в частен масив items
.
struct Container: Queue { private var items: [Int] = [] var count: Int { return items.count } mutating func push(_ element: Int) { items.append(element) } mutating func pop() -> Int { return items.removeFirst() } }
Сегашният ни протокол за опашка обаче има голям недостатък.
Само контейнери, които се занимават с Int
s, могат да отговарят на този протокол.
Можем да премахнем това ограничение, като използваме функцията „асоциирани типове“. Свързаните типове работят като генерични лекарства. За да демонстрираме, нека променим протокола на опашката, за да използваме свързани типове:
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Сега протоколът Queue позволява съхранението на всякакви вид елементи.
При изпълнението на Container
структура, компилаторът определя свързания тип от контекста (т.е. тип на връщане на метода и типове параметри). Този подход ни позволява да създадем Container
структура с родов тип елементи. Например:
class Container: Queue { private var items: [Item] = [] var count: Int { return items.count } func push(_ element: Item) { items.append(element) } func pop() -> Item { return items.removeFirst() } }
Използването на протоколи опростява писането на код в много случаи.
Например всеки обект, който представлява грешка, може да съответства на Error
(или LocalizedError
, в случай че искаме да предоставим локализирани описания) протокол.
След това същата логика за обработка на грешки може да се приложи към всеки от тези обекти за грешки в целия ви код. Следователно не е необходимо да използвате какъвто и да е конкретен обект (като NSError в Objective-C) за представяне на грешки, можете да използвате всеки тип, който съответства на Error
или LocalizedError
протоколи.
Можете дори да разширите типа String, за да го приведете в съответствие с LocalizedError
протокол и хвърляне на низове като грешки.
extension String: LocalizedError { public var errorDescription: String? { Return NSLocalizedString(self, comment:””) } } throw “Unfortunately something went wrong” func handle(error: Error) { print(error.localizedDescription) }
Разширенията на протоколите се основават на страхотността на протоколите. Те ни позволяват да:
Осигурете изпълнение по подразбиране на методите на протокола и стойностите по подразбиране на свойствата на протокола, като по този начин ги правите „по избор“. Типовете, които съответстват на протокол, могат да предоставят свои собствени реализации или да използват тези по подразбиране.
Добавете внедряване на допълнителни методи, които не са описани в протокола, и „украсете“ всички типове, които съответстват на протокола, с тези допълнителни методи. Тази функция ни позволява да добавяме специфични методи към множество типове, които вече съответстват на протокола, без да се налага да модифицираме всеки тип поотделно.
Нека създадем още един протокол:
protocol ErrorHandler { func handle(error: Error) }
Този протокол описва обекти, които отговарят за обработката на грешки, възникващи в приложение. Например:
struct Handler: ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Тук просто отпечатваме локализираното описание на грешката. С разширението на протокола можем да направим това изпълнение по подразбиране.
extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Правейки това прави handle
метод по избор чрез предоставяне на изпълнение по подразбиране.
Възможността за разширяване на съществуващ протокол с поведение по подразбиране е доста мощна, позволявайки на протоколите да растат и да се разширяват, без да се притеснявате за нарушаване на съвместимостта на съществуващия код.
Затова предоставихме изпълнение по подразбиране на handle
метод, но отпечатването на конзолата не е особено полезно за крайния потребител.
Вероятно бихме предпочели да им покажем някакъв изглед с предупреждение с локализирано описание в случаите, когато манипулаторът на грешки е контролер на изглед. За целта можем да разширим ErrorHandler
протокол, но може да ограничи разширението да се прилага само за определени случаи (т.е. когато типът е контролер на изгледа).
Swift ни позволява да добавяме такива условия към разширенията на протокола, използвайки where
ключова дума.
extension ErrorHandler where Self: UIViewController { func handle(error: Error) { let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) let action = UIAlertAction(title: 'OK', style: .cancel, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } }
Аз (с главна буква „S“) в кодовия фрагмент по-горе се отнася до типа (структура, клас или изброяване). Като посочваме, че ние разширяваме протокола само за типове, които се наследяват от UIViewController
, ние можем да използваме UIViewController
специфични методи (като present(viewControllerToPresnt: animated: completion)
).
Сега всички контролери за изглед, които съответстват на ErrorHandler
протокол имат собствено изпълнение по подразбиране на handle
метод, който показва изглед на предупреждение с локализирано описание.
Да приемем, че има два протокола, и двата имат метод с еднакъв подпис.
protocol P1 { func method() //some other methods } protocol P2 { func method() //some other methods }
И двата протокола имат разширение с изпълнение по подразбиране на този метод.
extension P1 { func method() { print('Method P1') } } extension P2 { func method() { print('Method P2') } }
Сега нека приемем, че има тип, който съответства и на двата протокола.
struct S: P1, P2 { }
В този случай имаме проблем с неясното изпълнение на метода. Типът не посочва ясно коя реализация на метода трябва да използва. В резултат на това получаваме грешка при компилацията. За да поправим това, трябва да добавим изпълнението на метода към типа.
struct S: P1, P2 { func method() { print('Method S') } }
Много обектно-ориентирани езици за програмиране са изложени на ограничения, свързани с разрешаването на двусмислени дефиниции на разширения. Swift се справя с това доста елегантно чрез разширения на протокола, като позволява на програмиста да поеме контрола там, където компилаторът не достига.
Нека да разгледаме Queue
протокол още веднъж.
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Всеки тип, който съответства на Queue
протоколът има count
екземпляр свойство, което определя броя на съхранените елементи. Това ни позволява, наред с други неща, да сравняваме такива типове, за да решим кой е по-голям. Можем да добавим този метод чрез разширение на протокола.
extension Queue { func compare(queue: Q) -> ComparisonResult where Q: Queue { if count queue.count { return .orderedAscending } return .orderedSame } }
Този метод не е описан в Queue
самият протокол, защото не е свързан с функционалността на опашката.
Следователно това не е изпълнение по подразбиране на метода на протокола, а по-скоро е реализация на нов метод, който „декорира“ всички типове, които съответстват на Queue
протокол. Без разширения на протокола ще трябва да добавим този метод към всеки тип поотделно.
Разширенията на протоколи може да изглеждат доста подобни на използването на основен клас, но има няколко предимства от използването на разширения на протокол. Те включват, но не са задължително ограничени до:
Тъй като класовете, структурите и преброяванията могат да отговарят на повече от един протокол, те могат да приемат изпълнението по подразбиране на множество протоколи. Това е концептуално подобно на множественото наследяване на други езици.
Протоколите могат да се приемат от класове, структури и изброявания, докато базовите класове и наследяването са достъпни само за класове.
В допълнение към разширяването на вашите собствени протоколи, можете да разширите протоколите от стандартната библиотека Swift. Например, ако искаме да намерим средния размер на колекцията от опашки, можем да го направим, като разширим стандарта Collection
протокол.
Структурите от данни на последователността, предоставени от стандартната библиотека на Swift, чиито елементи могат да бъдат преместени и достъпни чрез индексиран индекс, обикновено съответстват на Collection
протокол. Чрез удължаване на протокол е възможно да се разширят всички такива стандартни структури от библиотечни данни или да се разширят няколко от тях селективно.
Забележка: Протоколът, известен преди като
CollectionType
в Swift 2.x е преименуван наCollection
в Суифт 3.
extension Collection where Iterator.Element: Queue { func avgSize() -> Int { let size = map { $0.count }.reduce(0, +) return Int(round(Double(size) / Double(count.toIntMax()))) } }
Сега можем да изчислим средния размер на всяка колекция от опашки (Array
, Set
и т.н.). Без разширения на протокола, би трябвало да добавим този метод към всеки тип колекция поотделно.
В стандартната библиотека Swift се използват разширения на протокол, за да се внедрят например такива методи като map
, filter
, reduce
и т.н.
extension Collection { public func map(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] { } }
Както казах по-рано, разширенията на протокола ни позволяват да добавим реализации по подразбиране на някои методи и да добавим и нови реализации на методи. Но каква е разликата между тези две характеристики? Да се върнем към манипулатора на грешки и да разберем.
protocol ErrorHandler { func handle(error: Error) } extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } struct Handler: ErrorHandler { func handle(error: Error) { fatalError('Unexpected error occurred') } } enum ApplicationError: Error { case other } let handler: Handler = Handler() handler.handle(error: ApplicationError.other)
Резултатът е фатална грешка.
Сега премахнете handle(error: Error)
декларация на метод от протокола.
protocol ErrorHandler { }
Резултатът е същият: фатална грешка.
Означава ли това, че няма разлика между добавяне на изпълнение по подразбиране на метода на протокола и добавяне на нов метод на изпълнение към протокола?
Не! Разлика съществува и можете да я видите, като промените типа на променливата handler
от Handler
до ErrorHandler
.
let handler: ErrorHandler = Handler()
Сега изходът към конзолата е: Операцията не може да бъде завършена. (Грешка в ApplicationError 0.)
Но ако върнем метода на декларацията на манипулатора (грешка: грешка) към протокола, резултатът ще се промени обратно до фаталната грешка.
protocol ErrorHandler { func handle(error: Error) }
Нека разгледаме реда на това, което се случва във всеки отделен случай.
Когато декларацията за метод съществува в протокола:
Протоколът декларира handle(error: Error)
метод и осигурява изпълнение по подразбиране. Методът е заменен в Handler
изпълнение. Така че, правилното изпълнение на метода се извиква по време на изпълнение, независимо от типа на променливата.
Когато декларацията за метод не съществува в протокола:
Тъй като методът не е деклариран в протокола, типът не може да го замени. Ето защо изпълнението на извикан метод зависи от типа на променливата.
Ако променливата е от тип Handler
, се извиква изпълнението на метода от типа. В случай, че променливата е от тип ErrorHandler
, се извиква изпълнението на метода от разширението на протокола.
В тази статия демонстрирахме част от силата на разширенията на протокола в Swift.
За разлика от други езици за програмиране с интерфейси, Swift не ограничава протоколите с ненужни ограничения. Swift работи около често срещаните странности на тези програмни езици, като позволява на разработчика да разрешава неясноти, ако е необходимо.
С протоколите Swift и разширенията на протоколите кодът, който пишете, може да бъде толкова изразителен, колкото повечето динамични езици за програмиране, и въпреки това да бъде безопасен за типа по време на компилация. Това ви позволява да осигурите повторна употреба и поддръжка на вашия код и да правите промени в кодовата база на приложението Swift с повече увереност.
Надяваме се, че тази статия ще ви бъде полезна и ще приветстваме всякакви отзиви или по-нататъшна информация.
Сега можем да изчислим средния размер на всяка колекция от опашки (Array
, Set
и т.н.). Без разширения на протокола, би трябвало да добавим този метод към всеки тип колекция поотделно.
В стандартната библиотека Swift се използват разширения на протокол, за да се внедрят например такива методи като map
, filter
, reduce
и т.н.
верига на стойността на интернет на нещата
extension Collection { public func map(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] { } }
Както казах по-рано, разширенията на протокола ни позволяват да добавим реализации по подразбиране на някои методи и да добавим и нови реализации на методи. Но каква е разликата между тези две характеристики? Да се върнем към манипулатора на грешки и да разберем.
protocol ErrorHandler { func handle(error: Error) } extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } struct Handler: ErrorHandler { func handle(error: Error) { fatalError('Unexpected error occurred') } } enum ApplicationError: Error { case other } let handler: Handler = Handler() handler.handle(error: ApplicationError.other)
Резултатът е фатална грешка.
Сега премахнете handle(error: Error)
декларация на метод от протокола.
protocol ErrorHandler { }
Резултатът е същият: фатална грешка.
Означава ли това, че няма разлика между добавяне на изпълнение по подразбиране на метода на протокола и добавяне на нов метод на изпълнение към протокола?
Не! Разлика съществува и можете да я видите, като промените типа на променливата handler
от Handler
до ErrorHandler
.
let handler: ErrorHandler = Handler()
Сега изходът към конзолата е: Операцията не може да бъде завършена. (Грешка в ApplicationError 0.)
Но ако върнем метода на декларацията на манипулатора (грешка: грешка) към протокола, резултатът ще се промени обратно до фаталната грешка.
protocol ErrorHandler { func handle(error: Error) }
Нека разгледаме реда на това, което се случва във всеки отделен случай.
Когато декларацията за метод съществува в протокола:
Протоколът декларира handle(error: Error)
метод и осигурява изпълнение по подразбиране. Методът е заменен в Handler
изпълнение. Така че, правилното изпълнение на метода се извиква по време на изпълнение, независимо от типа на променливата.
Когато декларацията за метод не съществува в протокола:
Тъй като методът не е деклариран в протокола, типът не може да го замени. Ето защо изпълнението на извикан метод зависи от типа на променливата.
Ако променливата е от тип Handler
, се извиква изпълнението на метода от типа. В случай, че променливата е от тип ErrorHandler
, се извиква изпълнението на метода от разширението на протокола.
В тази статия демонстрирахме част от силата на разширенията на протокола в Swift.
За разлика от други езици за програмиране с интерфейси, Swift не ограничава протоколите с ненужни ограничения. Swift работи около често срещаните странности на тези програмни езици, като позволява на разработчика да разрешава неясноти, ако е необходимо.
С протоколите Swift и разширенията на протоколите кодът, който пишете, може да бъде толкова изразителен, колкото повечето динамични езици за програмиране, и въпреки това да бъде безопасен за типа по време на компилация. Това ви позволява да осигурите повторна употреба и поддръжка на вашия код и да правите промени в кодовата база на приложението Swift с повече увереност.
Надяваме се, че тази статия ще ви бъде полезна и ще приветстваме всякакви отзиви или по-нататъшна информация.