Adopting Cocoa Design Patterns (Адаптация Cocoa паттернов)
Подразделы:
- Delegation (Делегирование)
- Error Handling (Обработка ошибок, Swift 2.0)
- Key-Value Observing (наблюдатель за изменениями в свойствах объектов)
- Target-Action (Цель-Действие)
- Introspection (Самонаблюдение)
- API Availability (доступность API) Swift 2.0
Delegation (Делегирование)
Пожалуй самый часто используемый паттерн, реализуется с помощью свойства в которое сохраняется делегат. Главный подводный камень – не забыть проверить что делегат не равен nil и что он реализует конкретный метод/свойство.
1 2 3 4 5 6 7 8 9 10 11 |
Пример class MyDelegate: NSObject, NSWindowDelegate { func window(NSWindow, willUseFullScreenContentSize proposedSize: NSSize) -> NSSize { return proposedSize } } var myDelegate: NSWindowDelegate? = MyDelegate() if let fullScreenSize = myDelegate?.window?(myWindow, willUseFullScreenContentSize: mySize) { println(NSStringFromSize(fullScreenSize)) } |
1) проверяем что myDelegate не равен nil
2) что он реализует метод window:willUseFullScreenContentSize:
3) в случае успеха первых двух пунктов – вызывается метод делегата, результат сохраняется в fullScreenSize
4) распечатываем результат.
Error Handling (Обработка ошибок, Swift 2.0)
В swift 1.2 использовался NSError, и паттерн работал идентично Objective-C, если функция вернула false – обработать значение которое вернулось через inout параметр NSError
В swift 2.0 мы получили полноценную генерацию и перехват ошибок. И Objective-C методы в которых ошибка передавалась через последний в очереди параметр NSError автоматически были сконвертированы в throws методы
Подробно по теме можно посмотреть здесь Error Handling (Обработка ошибок) Swift 2.0
К примеру метод
1 |
- (BOOL) removeItemAtURL:(NSURL *)URL error:(NSError **)error; |
Будет импортирован в swift как
1 |
func removeItemAtURL(URL: NSURL) throws |
Заметим, что из сигнатуры исчезли как BOOL так и NSError. Если метод отработает без ошибок – ошибка не будет сгенерирована, и код отработает дальше – иначе ее надо ловить и обрабатывать, нормальное выполнение программы будет нарушено. Вместо этого в конце функции добавили ключевое слово throws.
Если NSError – первый параметр в Objective-C методе, swift если найдет – удалит из имени суффикс AndReturnError, но только в том случае если ранее не было метода с именем без суффикса.
Если метод ранее возвращал nil чтобы уведомить что произошла ошибка – теперь он будет возвращать обычное значение типа, не опционал.
В Objective-C можно было игнорировать ошибки, просто не проверяя NSError. В swift же мы обязаны обернуть вызов throws метода в do/catch (в коде вызывающем throws метод, либо если метод вызывающий тоже помечен как throws – в родителе, и так далее по цепочке).
Сгенерировать ошибку в swift можно с помощью throw
1 |
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil) |
Если же throws метод используется в Objective-C, – он конвертируется в метод у которого последний аргумент типа NSError**
Не стоит путать Objective-C exception (исключение) и Swift runtime error (ошибку реального времени), хоть они и схожи. Если exception был сгенерирован в коде Objective-C и не был там перехвачен – перехватить его в swift коде невозможно.
Key-Value Observing (наблюдатель за изменениями в свойствах объектов)
Если мы хотим чтобы один класс мог наблюдать за изменениями свойств другого класса – применим KVO.
В Swift этот паттерн доступен если клас был унаследован от NSObject
1. Добавим dynamic модификатор к любому свойству за которым мы хотим наблюдать.
1 2 3 4 5 6 |
class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } } |
2. Создадим global context variable (глобальную переменную контекста)
1 |
private var myContext = 0 |
3. Добавим наблюдателя для ключ-значение, переопределив метод observeValueForKeyPath:ofObject:change:context: Не забываем удалить наблюдателя в deinit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class MyObserver: NSObject { var objectToObserve = MyObjectToObserve() override init() { super.init() objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext) } override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject: AnyObject], context: UnsafeMutablePointer<Void>) { if context == &myContext { print("Date changed: \(change[NSKeyValueChangeNewKey])") } else { super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) } } deinit { objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext) } } |
Target-Action (Цель-Действие)
Так же очень распространенный паттерн, позволяющий слать сообщения другому объекту когда произошло специфическое событие. Target-Action модель фундаментально одинакова в Objective-C и Swift. В Swift мы используем тип Selector который связан с селекторами в Objective-C.
Подробнее описано применение тут Objective-C Selectors (Селекторы Objective-C)
Introspection (Самонаблюдение)
В Objective-C мы используем isKindOfClass: метод чтобы проверить является ли объект определенного типа класса и conformsToProtocol: метод чтобы проверить соответствует ли объект определенному протоколу.
В Swift оператор is проверяет на соответствие типу.
1 2 3 4 5 |
if object is UIButton { // объект типа UIButton (или его наследники) } else { // объект другого типа } |
Оператор же as? используется для downcaste to type (понижающего приведения к определенному типу)
1 2 3 4 5 |
if let button = object as? UIButton { // объект успешно приведен к типу UIButton и присвоен константе button } else { // объект не может быть приведен к типу UIButton } |
Подробней по теме можно посмотреть здесь Type Casting (Приведение типа)
API Availability (доступность API) Swift 2.0
Тема была рассмотрена здесь Checking API Availability (Проверка доступности API) Swift 2.0
Дополним тем, что в Objective-C для проверки доступен ли метод мы пользуемся методами respondsToSelector: и instancesRespondToSelector: для проверки доступен ли метод класса или обьекта
В swift же ввели #available
1 2 3 4 5 |
let locationManager = CLLocationManager() guard #available(iOS 9.0, OSX 10.10, *) else { return } locationManager.requestWhenInUseAuthorization() |
Мы можем для собственного API указывать минимальные версии платформ с которыми можно вызывать данный метод.
1 2 3 |
@available(iOS 8.0, OSX 10.10, *) func useShinyNewFeature() { } |
код внутри функции помеченной @available может спокойно использовать API, которое удовлетворяет указанным условиям без повторной проверки с помощью #available