Подтвердить что ты не робот

Как хранить неизменяемые массивы в переменной хранимой собственности в Swift?

Я хотел бы, чтобы мой класс имел хранимое свойство, которому можно назначить неизменяемые массивы. Если я это сделаю:

class MyClass{
  var myItems:[String]
}

Я могу назначить разные массивы для моего свойства, но массивы будут изменчивыми. Если я это сделаю:

class MyClass{
  let myItems:[String]
}

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

Самое лучшее, что я придумал, это создать оболочку вокруг массива, а затем использовать этот тип для свойства, например:

class MyClass{
  struct ImmutableWrapper{
    let array:[String]
  }
  var myItems:ImmutableWrapper
}

... который не совсем изящный.

4b9b3361

Ответ 1

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

Swift не отличает mutable от неизменяемых массивов способом Cocoa NSArray. Но зачем вам это нужно? Cocoa нуждается в нем, потому что NSArray - это класс, поэтому, если он изменен, он может быть изменен за MyClass. Но в Swift массив является типом значений, поэтому этого не может быть.

Итак, какую проблему вы действительно пытаетесь решить? Вы должны спросить себя, какой контракт вы пытаетесь выполнить за пределами объектов, и попытаться написать этот контракт в структуру MyClass. Ваш массив структур является вполне разумным подходом. Я думаю, однако, что то, что вы здесь, может быть конфиденциальностью, а не неизменностью. Например, я могу сказать следующее:

class MyClass{
    private var myItemsHidden = [String]()
    var myItems:[String] {
        get {
            return myItemsHidden
        }
        set {
            myItemsHidden = newValue
        }
    }
}

Теперь, полагая, что MyClass остался один в своем собственном файле (потому что private в Swift означает private для файла), myItemsHidden можно манипулировать любым способом из MyClass, поэтому, возможно, MyClass не хочет мутировать никаких субмарины или что-то еще, что вы пытаетесь предотвратить. Это зависит от MyClass, что он позволяет себе делать с myItemsHidden.

Но извне MyClass myItemsHidden не существует; существует только myItems, и единственное, что можно сделать с ним, это получить его и установить. (Если они его получают и меняют, оригинал не изменяется, они не могут мутировать оригинал.) Если ваша цель состоит в том, чтобы запретить кому-либо из внешних настроек myItems вообще, удалите функцию setter:

class MyClass{
    private var myItemsHidden = [String]()
    var myItems:[String] {
        get {
            return myItemsHidden
        }
    }
}

Теперь myItems представляет собой просто выданный (расширенный) массив и не более.

Если идея состоит в том, что другим классам должно быть разрешено добавлять новые массивы в массив массивов, вы можете добавить метод MyClass, который позволит им это сделать. Другими словами, теперь, когда эта вещь является частной, вам решать, какой доступ вы предоставляете другим. MyClass становится привратником для того, что другие классы могут сделать с этим значением, потому что само значение скрыто за стеной private.

Ответ 2

Я бы предложил несколько более сжатое решение, чем отличный ответ @matt, который снова использует модификаторы доступа. Вместо указания отдельной частной переменной и отображения публичной/внутренней вычисляемой переменной, которая предоставляет только getter, вы можете позволить Swift сделать это для вас, используя private(set):

class MyClass {
    private(set) var myItems = [String]()
}

Указывает, что установщик должен быть закрыт для текущей области файла. Геттер сохраняет уровень доступа по умолчанию (общедоступный/внутренний), поэтому внешние классы все равно могут получить копию myItems (копия, поскольку массивы являются структурами, поэтому передаются значением *).

Но вы можете подумать, что можете просто вызвать мутирующий метод, например append() on myItems, и при этом изменить его извне класса (в конце концов, мы получаем var myItems, а не let myItems). Но Swift достаточно умен, чтобы признать, что массив должен быть неизменным извне MyClass:

let object = MyClass()
object.myItems.append("change me")
//     ^       ~~~~~~
// Immutable value of type '[(String)]' only has mutating members named 'append'


Таким образом, это очень похоже на старую Objective-C парадигму поддержки private NSMutableArray и публикацию открытого readonly NSArray, который создает неизменяемую копию частного массива при вызове. Гораздо более элегантно, не так ли?


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

Ответ 3

Вероятно, вы можете попробовать NSArray. Сам массив неизменен, и переменная может быть снова назначена.

class MyClass{
  var myItems:NSArray
}