Swift: Передати масив за посиланням?


125

Я хочу передати свій 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

Відповіді:


72

Структури Swift передаються за значенням, але ви можете використовувати inoutмодифікатор, щоб змінити масив (див. Відповіді нижче). Заняття проходять шляхом посилання. Arrayі Dictionaryв Swift реалізовані як структури.


2
Масив не скопійований / переданий за значенням у Swift - він має дуже іншу поведінку в Swift порівняно зі звичайною структурою. Дивіться stackoverflow.com/questions/24450284/…
Бун

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

4
І я не рекомендую використовувати, NSArrayоскільки NSArrayмасив Swift має тонкі смислові відмінності (наприклад, тип посилання), і це, можливо, призведе до появи більше помилок.
eonil

1
Це мене серйозно вбило. Я стукав головою, чому все не працює так.
хуншань

2
Що робити, якщо використовувати inoutразом із Structs?
Елстон

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 використовував getter та setter, щоб скопіювати масив у тимчасовий, а потім скинути його на виході з функції - тобто копіює
dumbledad

2
Насправді вхідний вихід використовує копіювання копіювання або виклик за значенням результату. Однак в якості оптимізації він може використовуватись шляхом посилання. "В якості оптимізації, коли аргумент - це значення, яке зберігається за фізичною адресою в пам'яті, використовується одне і те ж місце пам'яті як всередині, так і зовні функціонального тіла. Оптимізована поведінка відома як виклик за посиланням; вона відповідає всім вимогам модель копіювання копіювальної копії, видаляючи накладні копії. "
Tod Cunningham

11
У Swift 3 inoutпозиція змінилася, тобтоfunc addItem(localArr: inout [Int])
elquimista

3
Також varбільше не доступний атрибут функції параметра.
elquimista

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.
Уес

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
    }
}

Потім ви можете змінити масив на скільки завгодно в класі облікового запису. Зверніть увагу, що це не допоможе вам, якщо ви також хочете змінити масив з класу контролера перегляду.


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.