Swift: передать массив по ссылке?


126

Я хочу передать свой Swift Array account.chatsв качестве chatsViewController.chatsссылки (так что , когда я добавить чат в account.chats, по- chatsViewController.chatsпрежнему указывает на account.chats). Т.е. я не хочу, чтобы Swift разделял два массива при изменении длины account.chats.


1
Я в конечном итоге просто сделать accountглобальную переменную и определения chatsсвойства , ChatsViewControllerкак: var chats: [Chat] { return account.chats }.
ma11hew28 02

Ответы:


73

Структуры в Swift передаются по значению, но вы можете использовать inoutмодификатор для изменения вашего массива (см. Ответы ниже). Классы передаются по ссылке. Arrayа Dictionaryв Swift реализованы как структуры.


2
Массив не копируется / не передается по значению в Swift - он имеет совсем другое поведение в Swift по сравнению с обычной структурой. См. Stackoverflow.com/questions/24450284/…
Boon

14
@Boon Array все еще семантически копируется / передается по значению, но просто оптимизирован для использования COW .
eonil

4
И я не рекомендую использовать массивы of NSArraybecause NSArrayи Swift имеют тонкие семантические различия (например, ссылочный тип), что может привести к большему количеству ошибок.
eonil

1
Это меня серьезно убивало. Я бился головой, почему что-то не работает.
khunshan

2
Что делать, если использовать inoutсо структурами?
Alston

137

Для оператора параметра функции мы используем:

let (оператор по умолчанию, поэтому мы можем опустить let ), чтобы сделать параметр постоянным (это означает, что мы не можем изменить даже локальную копию);

var, чтобы сделать его переменной (мы можем изменить его локально, но это не повлияет на внешнюю переменную, переданную в функцию); и

inout, чтобы сделать его параметром входа -выхода. Фактически, ввод-вывод означает передачу переменной по ссылке, а не по значению. И для этого требуется не только принимать значение по ссылке, но и передавать его по ссылке, поэтому передайте его с помощью & - foo(&myVar)вместо простогоfoo(myVar)

Так сделайте это так:

var arr = [1, 2, 3]

func addItem(inout localArr: [Int]) {
    localArr.append(4)
}

addItem(&arr)    
println(arr) // it will print [1, 2, 3, 4]

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


11
Это на самом деле не объясняет, как использовать массив в качестве переменной экземпляра, на которую ссылаются, а не на копирование.
Матей Укмар

2
Я думал, что inout использовал геттер и сеттер, чтобы скопировать массив во временный, а затем сбросить его на выходе из функции - т.е. он копирует
Дамблдад

2
Фактически in-out использует copy-in-copy-out или вызов по результату значения. Однако в качестве оптимизации его можно использовать по ссылке. "В качестве оптимизации, когда аргумент представляет собой значение, хранящееся по физическому адресу в памяти, одна и та же ячейка памяти используется как внутри, так и вне тела функции. Оптимизированное поведение известно как вызов по ссылке; оно удовлетворяет всем требованиям модель «копирование-в-копирование-выход» при устранении накладных расходов на копирование ».
Тод Каннингем,

11
В Swift 3 inoutположение изменилось, то естьfunc addItem(localArr: inout [Int])
elquimista 01

3
Кроме того, varбольше не доступен атрибут параметра функции.
elquimista 01

23

Определите себя, BoxedArray<T>который реализует Arrayинтерфейс, но делегирует все функции сохраненному свойству. В качестве таких

class BoxedArray<T> : MutableCollection, Reflectable, ... {
  var array : Array<T>

  // ...

  subscript (index: Int) -> T { 
    get { return array[index] }
    set(newValue) { array[index] = newValue }
  }
}

Используйте BoxedArrayвезде, где вы бы использовали Array. Назначение BoxedArrayбудет по ссылке, это класс, и, таким образом, изменения сохраненного свойства через Arrayинтерфейс будут видны всем ссылкам.


Немного пугающее решение :) - не совсем изящное - но вроде сработает.
Матей Укмар

Что ж, это определенно лучше, чем возвращаться к «Использовать NSArray», чтобы получить «семантику передачи по ссылке»!
GoZoner

13
Мне просто кажется, что определение массива как структуры вместо класса - это ошибка языкового дизайна.
Матей Укмар

Я согласен. Существует также мерзость, где Stringесть подтип AnyНО, если вы import Foundationзатем Stringстановитесь подтипом AnyObject.
GoZoner

19

Для Swift версий 3-4 (XCode 8-9) используйте

var arr = [1, 2, 3]

func addItem(_ localArr: inout [Int]) {
    localArr.append(4)
}

addItem(&arr)
print(arr)

3

Что-то вроде

var a : Int[] = []
func test(inout b : Int[]) {
    b += [1,2,3,4,5]
}
test(&a)
println(a)

???


3
Я думаю, что вопрос заключается в том, чтобы свойства двух разных объектов указывали на один и тот же массив. В таком случае ответ Каана правильный: нужно либо обернуть массив в класс, либо использовать NSArray.
Wes Campaigne

1
правильно, inout работает только в течение всего времени жизни тела функции (без поведения закрытия)
Кристиан Дитрих

Незначительная нить: это func test(b: inout [Int])... может быть, это старый синтаксис; Я попал в Swift только в 2016 году, а этот ответ - из 2014, так что, может быть, раньше все было иначе?
Ray Toal

2

Еще один вариант - попросить потребителя массива при необходимости запросить его у владельца. Например, что-то вроде:

class Account {
    var chats : [String]!
    var chatsViewController : ChatsViewController!

    func InitViewController() {
        chatsViewController.getChats = { return self.chats }
    }

}

class ChatsViewController {
    var getChats: (() -> ([String]))!

    func doSomethingWithChats() {
        let chats = getChats()
        // use it as needed
    }
}

Затем вы можете изменять массив сколько угодно внутри класса Account. Обратите внимание, что это не поможет вам, если вы хотите также изменить массив из класса контроллера представления.


0

Использование inout- это одно из решений, но мне оно не кажется очень быстрым, поскольку массивы являются типами значений. Стилистически я предпочитаю вернуть мутированную копию:

func doSomething(to arr: [Int]) -> [Int] {
    var arr = arr
    arr.append(3) // or likely some more complex operation
    return arr
}

var ids = [1, 2]
ids = doSomething(to: ids)
print(ids) // [1,2,3]

1
У такого рода вещей есть обратная сторона медали. Он использует немного меньшую батарею телефона, чтобы просто изменить исходный массив :)
Дэвид Ректор,

Я с уважением не согласен. В этом случае удобочитаемость на месте вызова обычно превышает снижение производительности. Начните с неизменяемого кода, а затем оптимизируйте, сделав его изменяемым позже. Есть случаи с массивными массивами, когда вы правы, но в большинстве приложений это крайний случай 0,01%.
ToddH

1
Я не знаю, с чем вы не согласны. Копирование массива приводит к снижению производительности, а операции с меньшей производительностью используют больше ЦП и, следовательно, больше батареи. Думаю, вы предположили, что я говорил, что это лучшее решение, а я не был. Я следил за тем, чтобы у людей была вся доступная информация, чтобы сделать осознанный выбор.
Дэвид Ректор

1
Да, вы правы, нет сомнений в том, что он эффективнее. Я думал, вы предполагаете, что inoutэто универсально лучше, потому что это экономит батарею. На что я бы сказал, что читабельность, неизменяемость и безопасность потоков этого решения повсеместно лучше, и что inout следует использовать только в качестве оптимизации в тех редких случаях, когда этого требует вариант использования.
ToddH

0

используйте a NSMutableArrayили a NSArray, которые являются классами

таким образом вам не нужно внедрять какую-либо оболочку и можно использовать сборку в мосте

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.