2015-07-28 3 views
1

Я новичок в кодировании Swift и iOS и работаю над написанием своего первого приложения. В то время как мой фон программирования довольно значителен, я исхожу из фона Python и C#, где почти все может быть None или null, и это зависит от пользователя, чтобы проверить его во время выполнения для null. Я нахожу всю эту концепцию «nullable vs. non-nullable types» или «необязательные типы» в Swift, чтобы запутать.Swift: путают о нулевых/необязательных типах

Я понимаю, что основная концепция заключается в том, что переменная, объявленная как тип myObject, не может быть установлена ​​в nil. Однако, если я определяю его как тип myObject?, тогда значение может быть установлено на nil.

Проблема в том, что, когда я смотрю на свои проекты кода, кажется, что все должно быть «обнулено» в моем коде. Кажется, что это означает, что я не думаю правильно, как должен работать мой код, или что мне не хватает какого-то важного понимания.

Давайте рассмотрим простейший пример того, чем я смущен. Предположим, у меня есть два класса: один, который хранит и управляет некоторыми данными, а другой - обеспечивает доступ к этим данным. (Например, это может быть что-то вроде соединения с базой данных, или дескриптор файла или что-то подобное.) Назовем класс, содержащий данные myData, и класс, который работает с этими данными myObject.

myObject потребуется ссылка на уровне класса на myData, поскольку многие из его методов зависят от локальной ссылки на класс. Итак, первое, что делает конструктор, это создать соединение с данными, а затем сохранить его в локальной переменной dataConnection. Переменная должна быть определена на уровне класса, чтобы другие методы могли получить к ней доступ, но она будет назначена в конструкторе. Неспособность получить соединение приведет к некоторому исключению, которое будет мешать самому созданию класса.

Я знаю, что Swift имеет два способа определения переменного: var и let, с let быть аналогичны const директивы некоторых Языков. Поскольку соединение данных будет сохраняться на протяжении всей жизни класса, let кажется очевидным выбором. Однако я не знаю, как определить переменную уровня класса через let, которая будет назначена во время выполнения. Поэтому я использую что-то вроде

var dataConnection: myData? 

в классе вне любых функций.

Но теперь мне приходится иметь дело с типом данных с нулевым значением и делать явные разворачивающиеся каждый раз, когда Я использую его в любом месте. Нехорошо говорить и сбивать с толку.

func dealWithData() { 
    self.dataConnection.someFunctionToGetData() <- results in an unwrapping error. 
    self.dataConnection!.someFunctionToGetData() <- works. 
    let someOtherObjectUsingData: otherObject = self.getOtherObject() <- may result in error unless type includes ? 
    someOtherObjectUsingData.someMethod(self.dataConnection) <- unwrap error if type included ? 
    var myData = self.dataConnection! 
    someOtherObjectUsingData.someMethod(myData) <- works 
} 

func somethingNeedingDataObject(dataObject: myData?) { 
    // now have to explicitly unwrap 
    let myDataUnwrapped = myData! 
    ... 
} 

Это, по-видимому, очень подробный способ решения проблемы. Если объект равен nil, не может ли явное разворачивание само по себе вызвать ошибку времени выполнения (которую можно поймать и обработать)? Это, как правило, является кошмаром при навязывании вещей вместе. Я должен был сделать что-то вроде:

self.dataConnection!.somethingReturningAnObject!.thatObjectsVariable!.someMethod() 

var myData? = self.dataConnection 
var anotherObject? = myData!.somethingReturningAnObject 
... 

То, как я привык делать это, что вы просто определить переменную, и если он установлен в нуль, и вы пытаетесь сделать что-то с ним, исключение (которое вы можете поймать и обработать). Разве это просто не так, как все работает в Свифте?Это довольно смутило меня, что почти каждый раз, когда я пытаюсь скомпилировать приложение, я получаю массу ошибок об этом (и я просто позволяю Xcode исправлять их). Но это не может быть лучшим способом справиться с этим.

Должен ли я последовательно обрабатывать переменные обертывания и разворота - даже те, которые, как ожидается, никогда не будут нулевыми, но просто не могут быть назначены во время компиляции?

+0

Я настоятельно предлагаю вам использовать типы заглавных букв, чтобы мы могли легко идентифицировать их, когда вы пишете о них. Также это соглашение в Swift;) – Fantattitude

+0

Если вы хотите добавить свойство к классу, который не может быть установлен во время создания, но вы можете гарантировать, что: 1. Как только он будет установлен, он никогда не станет 'nil' и 2. You никогда не пытайтесь получить к нему доступ до того, как он будет установлен, безопасно использовать неявно отключенные опции: 'var dataConnection: MyData!' –

ответ

1

Однако я не знаю, как определить переменную уровня класса через let, которая будет назначаться во время выполнения.

Эта часть проста. Просто используйте let вместо var. С Swift 1.2 и более поздними версиями вы можете отложить фактическое присвоение let. Компилятор достаточно умен, чтобы провести анализ потока и убедиться, что он назначен один раз и только один раз, на всех путях. Таким образом, в случае класса let, назначение может также выполняться в конструкторе.

Но теперь мне приходится иметь дело с типом данных с нулевым значением и делать явное разворачивание каждый раз, когда я его использую в любом месте.

Но это то, что неявно разворачивается. Например, StoryBoard определяет все @IBOutlets как неявно развернутые, потому что семантика очень ясна: при входе в viewDidLoad() и везде после этого разворачивание безопасно. Если вы можете доказать ясную семантику себе, вы можете сделать то же самое.

Так у вас есть примерно 4 варианта:

A) объявить на уровне класса неявно развернутого:

let dataConnection: MyData! 

И вынужден инициализировать его в конструкторе:

init() { 
    let whateverObj = someInitialCalculation() 
    dataConnection = whateverObj.someWayOfGettingTheConnection() 
} 

И с этого момента вам не нужно «!»; должно быть ясно, что неявное разворот всегда безопасно.

B) Инициализировать его прямо в его декларации, если его инициализации является надежным и разумным в этой точке, что позволяет отказаться от всей концепции OPTIONALS:

let dataConnection = SomeClass.someStaticMethod() 

C) Объявите на уровне класса, как var, как неявный факультативно:

var dataConnection: MyData! 

Вам не нужно включать его в конструктор; пусть это будет nil, пока его значение не может/должно быть вычислено. Вам по-прежнему нужен некоторый анализ потока, который будет доказан после определенного момента, так как в случае @IBOutlets доступ к нему всегда будет действителен

D) Самый «непредсказуемый» случай. Объявите это как явный необязательными, потому что на протяжении всего жизненного цикла класса, соединение данных будут приходить и уходить:

var dataConnection: MyData? 

func someMethodThatHandlesData() { 
    if let dC = dataConnection { 
     dc.handleSomeData() 
    } 
    else { 
     alert("Sorry, no data connection at the moment. Try again later.") 
    } 
} 

Я думаю, вы воображая, что Swift всегда заставляет вас вниз путь D).

Что касается кода вашей спагетти-строки, вы хотите изучить опциональную цепочку, и только нужно проверить конечный результат для nil.

+0

Обратите внимание, что случай B) действительно очень крут: анализ потока происходит полностью через цепочку вызовов, поэтому, если код компилируется без упоминания опций, компилятор проверяет, что присваивание не может привести к «nil». – BaseZen

Смежные вопросы