Необязательная цепочка и массив в swift

#swift

#swift

Вопрос:

Давайте рассмотрим эти два простых класса, чтобы показать мою проблему:

 class Object{  
    var name:String?
    // keep it simple... and useless
}

class TestClass {
    var objects:AnyObject[]? 

    func initializeObjects (){
        objects?.insert(Object(), atIndex:0) // Error
        objects?.insert(Object(), atIndex:1) // Error
        objects?.insert(Object(), atIndex:2) // Error
    }
}
 

С помощью этой реализации я получаю 3 ошибки Could not find member 'insert' , когда я пытаюсь добавить объект в objects массив.

Теперь, если я удалю необязательное из objects определения, и необязательная цепочка в initializeObjects нем будет работать без проблем (здесь рабочий код)

 class Object{
    var name:String?
}

class TestClass {
    var objects:AnyObject[] = AnyObject[]() // REMOVE optional and initialize an empty array

    func initializeObjects (){
        objects.insert(Object(), atIndex:0) // Remove Opt chaining 
        objects.insert(Object(), atIndex:1) // Remove Opt chaining
        objects.insert(Object(), atIndex:2) // Remove Opt chaining
    }
}
 

Я не могу понять, что не так в первой реализации.
Я думал, что он проверяет, objects? если objects нет nil , и на этом этапе он добавляет элемент, используя insert:atIndex: . Но я, вероятно, ошибаюсь — .-

Ответ №1:

Массивы в Swift — это структуры, а структуры — это типы значений.
Опции в Swift на самом деле являются перечислениями ( Optional<T> или ImplicitlyUnwrappedOptional<T> ).

Когда вы разворачиваете необязательный (неявно или явно) тип значения, то, что вы получаете, на самом деле является постоянной копией структуры. И вы не можете вызывать mutating методы для постоянной структуры.

Выполнение objects?.insert(Object(), atIndex:0) в основном означает это:

 if let tmp = objects {
    tmp.insert(Object(), atIndex:0)
}
 

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

Это воспроизводимо для любой структуры, а не только для массивов:

 struct S {
    var value: Int = 0
}

var varS: S = S()
varS.value = 10 //can be called

let constS: S = S()
constS.value = 10 //cannot be called - constant!

var optionalS: S? = S()
optionalS?.value = 10 //cannot be called, unwrapping makes a constant copy!

//workaround
if optionalS {
    var tmpS = optionalS!
    tmpS.value = 10
    optionalS = tmpS
}
 

Некоторые соответствующие обсуждения здесь: https://devforums.apple.com/thread/233111?tstart=60

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

1. Можем ли мы сказать, что инициализация массива в классе является своего рода хорошей практикой, позволяющей избежать обходного пути? (кстати, отличный ответ!)

2. @MatterGoal или NSArray ? Недавно об этом шла дискуссия на форумах разработчиков Apple. Также, похоже, возникает ошибка при вызове array?[index] = something , которая компилируется, но ничего не делает.

3. Откуда вы взяли это «Когда вы разворачиваете необязательный (неявный или явный) тип значения, то, что вы получаете, на самом деле является постоянной копией структуры» Я не могу найти это в руководстве! Это действительно важная информация.

4. @MatterGoal Это важно и недокументировано. Это связано с тем, что опции реализуются с использованием enum . Это не просто синтаксический сахар. Даже перечисление должно создавать копию типа значения при возврате. И постоянство возвращаемого значения связано с тем, что это временная переменная — если вы не присвоите ее чему-либо, вы не сможете ее изменить, потому что изменение будет потеряно.

5. @Sulthan, не должна ли строка optionalS?.value = 10 по optionalS!.value = 10