Social Icons

пятница, 19 июня 2015 г.

Optionals in Swift.

Введение

Считаю, что для тех, кто начал программировать под iOS сразу на языке Swift, минуя Objective-C, трудно понять смысл языковой конструкции Optionals. 
Вот что сказано в документации  [1]: "Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes."
Что это означает. В Objective-C не все типы являются классами. Есть базовые типы, которые не являются классами в терминах ООП.
В Objective-C, если говорят об объектах, то имеют ввиду объкты классов (или инстанс-переменная класса). Так, объявление переменной-указателя на объект выглядит следующим образом:
     UIView* view;
Пока нам известно только одно, что переменная имеет тип UIView. UIView - это класс. Все классы имеют одного общего родителя - класс NSObject. Т.е., все объекты в  Objective-С обладают некими общими свойствами, которые они наследуют от NSObject.  Переменная с именем view хранит адрес в памяти, где находится объект. Пока еще объект не создан. Что это значит? Это значит, что не выдедена память для объекта. Переменная указывает в никуда. Тут возникает вопрос: а что если послать сообщение такой переменной? Ведь не всегда одновременно с объявлением переменной мы инициализируем ее значением, а именно, выделяем память для объекта. К счастью такие переменные инициализируются таким объектом, который ничего не выполняет если ему отправить любое сообщение (вызвать любую функцию у такой переменной). Этот объект -  nil. Транслятор не анализирует, что содержит переменная: nil, ссылку на реально существующий объект, или объект, который уже не существует. В последнем случае программа закрешится. Чтобы этого избежать, надо присвоить переменной nil, если точно известно, что объект уничтожен ранее, или сам факт присвоения nil влечет за собой уничтожение объекта (если переменная владела объектом. Эти ньюансы в данном контексте не важны). В любом случае nil - это такая удобная вещь, которую можно всегда проверить:
    if (view != nil){
...
}  
    или так
    if (view){...
}  
Плюс к тому, что как сказано ранее, вызов метода, например 
    [view setNeedsDisplay];
ни к чему плохому не приведет. По крайней мере программа продолжит выполнение.
Надеюсь теперь это выражение: "Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes."
стало более понятным. 
В Swift есть несколько основных положений. Первое - все является объектом. Даже число 1 - объект (так скажем объект не класса, как можно подумать, а структуры, но сейчас это не важно. Он остается объектом и может принимать сообщения. И классы и структуры - типы. Поэтому можно сказать точнее: в Swift все является объектом какого-то типа). В этом есть большое отличие Swift от Objective-C. Второе - безопасность типов (type safe). Тип переменной, однажды объявленный (если тип явно не указан, то транслятор сам определит его из контекста (называется это type inference)), не может быть изменен. Уже в момент объявления переменной транслятор должен зать тип этой переменнной. И дальше он будет контролировать: в переменную указанного типа невозможно записать значение переменной другого типа. При попытке сделть это транслятор предупредит. Кстати, для совместимости, существует тип AnyObject, который может принимать переменную любого типа. 
Все типы в Swift едины в том смысле что абсолютно для всех переменных мы можем проверить, содержит ли она что-то полезное, была ли она проинициализирована, присвоено ли ей значение, а не только для объектов как в Objective-C.
Понятно, что если мы проинициализировали переменную в момент ее объявления или в инициализаторе, тогда нет смысла проверять. 
В каких же случаях надо проверять? В случаях, когда переменная была объявлена, но ей при этом не было присвоено значение. 
В процессе выполнения программы ей возможно будет оно присвоено, но транслятор об этом не знает, где и когда. Он будет строго реагировать на тот факт, что переменная не проинициализирована. Чтобы "обмануть" транслятор, существует Optionals. Optional это такая обертка над переменной. Мы всегда сможем развернуть ее и посмотреть что там внутри. Ради этого есть конструкция. Транслятор о ней знает. Иногда мы точно знаем, что там под оберткой 100% что-то есть. Можно не проверять, не разворачивать. Это тоже предусмотрено. Об этом потом. И надо понимать, что то, что лежит под оберткой в случае, если там ничего нет, то это оно ведет себя так же как nil в Objective-C. Это надо на случай когда мы пытаемся что-то выполнить для этого "нечто".
Короче говоря, не закрешится, перейдет просто к следующему оператору. 

Как это работает?

Например, 
   var optionalValue: Int?
 Обратите внимание, переменная не проинициализирована. Ей не присвоено значение. Транслятор не ругается только потому, что он видит 
это волшебный значек "?". Эта конструкция Int? называется "optional int". Это значит, что переменная optionalValue может хранить что-то одно из двух (и только): это значение типа Int (структура) или nil - которое говорит об отстутсвии (временном) какого-либо значения внутри. Второе значение (nil) появляется только потому, что это Optionals. Понятие nil больше нигде не появляется и его нельзя использовать для не Optionals переменных. Итак, optionalValue сейчас содержит nil. Как же мы сможем проверить, есть ли что-то под оберткой или нет?
Это можно сделать просто указав эту переменную в условии:
if optionalValue {   ...
} else {
   ...
}
Это называется "Force Unwrapping". Независимо от типа переменной условие вернет true если под оберткой что-то есть. Сейчас важно понять, что если мы попытаемся использовать нашу переменную внутри "истинного" блока, транслятор не даст нам это сделать. Так как он знает точно, 
что эта переменная не была проинициализирована и тут возможен креш. Чтобы использовать эту переменную, надо еще раз обмануть транслятор. Для этого так называемый "Optional Binding": 
   if let unwrappedOptionalValue = optionalValue {       println("\(unwrappedOptionalValue)")
   }
На этот раз мы можем смело использовать значение unwrappedOptionalValue в вычислениях внутри блока. Транслятор уже понимает. Молчит.
Эта конструкция (оператор if let, именно "if let" - это можно понимать как самостоятельный оператор) широко используется. Но понятно не всегда удобно так делать. Наш весь код будет состоять на 30% из таких условий.
Читать такой код будет не очень удобно. Если сразу после имени переменной поставить другой волшебный знак "!", то 
переменную можно использовать везде и без проверки. Транслятор будт спокойно все пропускать. Это ("!") называется "implicitly unwrapped optionals". Единственно, если окажется что в этой переменной нет значения в данный момент, то произойдет креш (ошибка времени выполнения). Итак, первое условие можно переписать используя implicitly unwrapped optionals:
if optionalValue {   println("\(optionalValue!)")
} else {
   ...
}
и все. Транслятор молчит.
После использования переменной ей можно присвоить значение nil: 
optionalValue = nil 
Теперь optionalValue опять не содержит полезных значений. Но! Присвоить nil можно только переменной, объявленной как Optional. Во всех других случаях, если вы объявили переменную, проинициализировали ее (неважно, это let или var), вы не сможете присвоить ей nil. Кстати, всеми вопросами выделения и освобождения памяти занимается Swift. Тут не обязательно присвоить nil, чтобы подсказать среде, что все, объект дальше не нужен, и если на него нет 
больше ссылок, то его можно удалить. Swift сам все посчитает и удалит.
Это почти все, что надо знать про Optionals для того, чтобы начать пробовать.
Дальше начинаются проблемы. Это грубо говоря функции (обычные, встроенные, конструкторы объектов и т.д.), которые могут возвращать что-то. Это что-то может быть Optionals. Вы ставите "!" чтоб не заморачиваться с обертками. А функция реально вернула nil. Тут "креш" неизбежен. Это надо понимать. 
Другой случай. Это преобразование типа (Downcast). Как это принято в полиморфизме, вы получаете переменную базового типа в качестве параметра функции. 
Вам надо преобразовать ее в конкретный тип, который возможно есть где-то в иерархии ниже, чтобы отработать ее конкретное поведение. 
Вы пытаетесь преобразовать. Тут тоже преобразование упирается в Optionals. Если преобразование возможно, то получаете переменную нужного типа, если нет, и
переменная объявлена как Optionals, можно развернуть и проверить. Если нет - "креш". Нельзя присвоить переменной nil.
Конечно если вы точно уверены, что нужный класс наследует базовый (выше в иерархии), то тут все в порядке. Ставьте "!" и все работает!
А если нет (не наследуется), то закрешится. Тут надо разворачивать и проверять. В этом безопасность Swift.
Например:
if let navController = self.menuController()?.mainViewController as? UINavigationController {
    if navController.topViewController.isKindOfClass(SKAboutViewController) {
        self.menuController()?.removeGestures()
    }
}
В первой строке такая вот конструкция: self.menuController()?.mainViewController называется Optional Chaining. Имеется ввиду "?.".
Здесь свойство self.menuController()? является Optionals, поэтому возвращает или объект или nil. Если nil, продолжаем выполнять программу после закрывающей скобки if. Если объект - обращаемся к свойству mainViewController, который должен что-то вернуть. Мы это что-то пытаемся преобразовать к типу UINavigationController. Точнее может сказать интерпретировать как тип UINavigationController. То, что получилось станет значением константы navController. Если удачно получилось преобразование, используем константу в дальнейших вычислениях. Если нет-продолжаем выполнять после закрывающей скобки if. Если бы мы поставили as! вместо as?, и при этом преобразование не удалось, то был бы "креш" (другими словами, as! буквально означает то, что написано: внимание! Возможна проблема). Преимущество as! как вы уже знаете,
в том, что не обязательно оборачивать. Можно было бы написать (уже без условий):
let navController = self.menuController()?.mainViewController as! UINavigationController 
 В Swift 2 Apple предпринял шаги по сокращению использования Optionals. Как сказано Swift blog:
"The SDKs have been updated to annotate API that cannot return nil so you don’t need to use optionals as often."
SDK не будет возвращать nil. Т.е. не будут возвращаться Optionals. 

Литература.

Руководство по Swift от Apple на сайте для разработчиков Apple. Оно же находится в бесплатном доступе на iBook store. Можно открыть через приложение iBook.   
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html.
https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11&ign-mpt=uo%3D4

Комментариев нет:

Отправить комментарий