Swift: ส่งอาร์เรย์โดยการอ้างอิง?


126

ฉันต้องการที่จะผ่านสวิฟท์ของฉัน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

คำตอบ:


73

โครงสร้างใน Swift จะถูกส่งผ่านด้วยค่า แต่คุณสามารถใช้inoutตัวปรับเปลี่ยนเพื่อแก้ไขอาร์เรย์ของคุณได้ (ดูคำตอบด้านล่าง) ชั้นเรียนผ่านการอ้างอิง ArrayและDictionaryใน Swift ถูกนำไปใช้เป็นโครงสร้าง


2
อาร์เรย์จะไม่ถูกคัดลอก / ส่งต่อด้วยค่าใน Swift - มีพฤติกรรมที่แตกต่างกันมากใน Swift เมื่อเทียบกับโครงสร้างปกติ ดูstackoverflow.com/questions/24450284/…
บุญ

14
@Boon อาร์เรย์จะยังคงคัดลอกความหมาย / ผ่านโดยค่า แต่เพียงการปรับให้เหมาะสมกับการใช้วัว
eonil

4
และฉันไม่แนะนำให้ใช้NSArrayเพราะNSArrayและอาร์เรย์ Swift มีความแตกต่างทางความหมายเล็กน้อย (เช่นประเภทการอ้างอิง) และอาจทำให้คุณมีข้อบกพร่องมากขึ้น
eonil

1
นี่เป็นการฆ่าฉันอย่างจริงจัง ฉันกำลังก้มหน้าว่าทำไมสิ่งต่างๆถึงไม่เป็นไปตามนั้น
khunshan

2
ถ้าใช้inoutกับ Structs ล่ะ?
Alston

137

สำหรับตัวดำเนินการพารามิเตอร์ฟังก์ชันที่เราใช้:

let (เป็นตัวดำเนินการเริ่มต้นดังนั้นเราจึงสามารถละเว้นlet ) เพื่อสร้างค่าคงที่ของพารามิเตอร์ (หมายความว่าเราไม่สามารถแก้ไขแม้แต่สำเนาในเครื่อง)

varเพื่อทำให้เป็นตัวแปร (เราสามารถแก้ไขได้ในเครื่อง แต่จะไม่มีผลกับตัวแปรภายนอกที่ถูกส่งไปยังฟังก์ชัน) และ

InOutที่จะทำให้มันพารามิเตอร์ InOut In-out หมายถึงการส่งผ่านตัวแปรโดยอ้างอิงไม่ใช่ตามค่า และไม่เพียง แต่ต้องยอมรับค่าโดยการอ้างอิงเท่านั้น แต่ยังต้องส่งต่อด้วยการอ้างอิงด้วยดังนั้นให้ส่งด้วย& - 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
สิ่งนี้ไม่ได้อธิบายถึงวิธีการใช้อาร์เรย์เป็นตัวแปรอินสแตนซ์ที่อ้างถึงไม่ได้คัดลอก
Matej Ukmar

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อินเทอร์เฟซจะมองเห็นได้สำหรับการอ้างอิงทั้งหมด


วิธีแก้ปัญหาที่น่ากลัวเล็กน้อย :) - ไม่สวยหรู - แต่ดูเหมือนว่าจะใช้ได้
Matej Ukmar

แน่นอนว่าดีกว่าการถอยกลับไปที่ 'Use NSArray' เพื่อรับ 'pass by reference semantics'!
GoZoner

13
ฉันแค่รู้สึกว่าการกำหนด Array เป็นโครงสร้างแทนที่จะเป็นคลาสคือความผิดพลาดในการออกแบบภาษา
Matej Ukmar

ฉันเห็นด้วย. นอกจากนี้ยังมีสิ่งที่น่ารังเกียจที่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)

???


4
ฉันคิดว่าคำถามกำลังขอให้มีคุณสมบัติของวัตถุสองชิ้นที่แตกต่างกันชี้ไปที่อาร์เรย์เดียวกัน หากเป็นเช่นนั้นคำตอบของ Kaan ก็ถูกต้อง: ต้องรวมอาร์เรย์ไว้ในคลาสหรือใช้ NSArray
Wes Campaigne

1
ถูกต้องการไม่เข้าใช้งานได้เฉพาะตลอดอายุการใช้งานของร่างกาย (ไม่มีพฤติกรรมการปิด)
Christian Dietrich

เล็กน้อย: มัน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
มีข้อเสียด้านประสิทธิภาพสำหรับสิ่งนี้ ใช้แบตเตอรี่โทรศัพท์น้อยลงเล็กน้อยเพื่อปรับเปลี่ยนอาร์เรย์ดั้งเดิม :)
David Rector

ฉันไม่เห็นด้วยอย่างเคารพ ในกรณีนี้ความสามารถในการอ่านได้ที่ไซต์การโทรโดยทั่วไปจะดีกว่าประสิทธิภาพที่ได้รับ เริ่มต้นด้วยโค้ดที่ไม่เปลี่ยนรูปแล้วปรับให้เหมาะสมโดยทำให้เปลี่ยนแปลงได้ในภายหลัง มีหลายกรณีที่มีอาร์เรย์ขนาดใหญ่ที่คุณถูกต้อง แต่ในแอปส่วนใหญ่นั้นเป็นกรณีขอบ 0.01%
ToddH

1
ฉันไม่รู้ว่าคุณไม่เห็นด้วยกับอะไร มีการลงโทษด้านประสิทธิภาพในการคัดลอกอาร์เรย์และการดำเนินการที่มีประสิทธิภาพน้อยกว่าจะใช้ CPU มากขึ้นและมีแบตเตอรี่มากขึ้น ฉันคิดว่าคุณคงคิดว่าฉันบอกว่ามันเป็นทางออกที่ดีกว่าซึ่งฉันไม่ได้เป็น ฉันแน่ใจว่าผู้คนมีข้อมูลทั้งหมดเพื่อทำการตัดสินใจอย่างชาญฉลาด
David Rector

1
ใช่คุณถูกต้องไม่มีคำถามใด ๆ ที่มีประสิทธิภาพมากกว่า ฉันคิดว่าคุณแนะนำว่าinoutดีกว่าในระดับสากลเพราะประหยัดแบตเตอรี่ ซึ่งฉันจะบอกว่าความสามารถในการอ่านไม่เปลี่ยนรูปและความปลอดภัยของเธรดของโซลูชันนี้ดีกว่าในระดับสากลและควรใช้การเพิ่มประสิทธิภาพในกรณีที่หายากเท่านั้นซึ่งกรณีการใช้งานรับประกัน
ToddH

0

ใช้ a NSMutableArrayหรือ a NSArrayซึ่งเป็นคลาส

ด้วยวิธีนี้คุณไม่จำเป็นต้องใส่สิ่งห่อหุ้มใด ๆ และสามารถใช้โครงสร้างในการเชื่อมต่อได้

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.