Swift: Vượt qua mảng bằng cách tham khảo?


125

Tôi muốn chuyển Swift của mình Array account.chatssang chatsViewController.chatstham chiếu (để khi tôi thêm trò chuyện vào account.chats, chatsViewController.chatsvẫn trỏ đến account.chats). Tức là tôi không muốn Swift tách hai mảng khi độ dài account.chatsthay đổi.


1
Tôi cuối cùng chỉ tạo accountmột biến toàn cục và xác định thuộc chatstính ChatsViewControllerlà : var chats: [Chat] { return account.chats }.
ma11hew28

Câu trả lời:


72

Các cấu trúc trong Swift được truyền theo giá trị, nhưng bạn có thể sử dụng công cụ inoutsửa đổi để sửa đổi mảng của mình (xem câu trả lời bên dưới). Các lớp học được thông qua tham khảo. ArrayDictionarytrong Swift được thực hiện như các cấu trúc.


2
Mảng không được sao chép / chuyển qua giá trị trong Swift - nó có hành vi rất khác trong Swift so với cấu trúc thông thường. Xem stackoverflow.com/questions/24450284/ Mạnh
Boon

14
@Boon Array vẫn được sao chép / truyền theo giá trị ngữ nghĩa, nhưng chỉ được tối ưu hóa để sử dụng COW .
eonil

4
Và tôi không khuyên bạn nên sử dụng NSArraybởi vì NSArrayvà mảng Swift có sự khác biệt về ngữ nghĩa tinh tế (như kiểu tham chiếu) và điều đó có thể dẫn bạn đến nhiều lỗi hơn.
eonil

1
Điều này đã nghiêm trọng giết chết tôi. Tôi đã đập đầu mình tại sao mọi thứ không hoạt động theo cách này.
khun Sơn

2
Điều gì nếu sử dụng inoutvới Structs?
Alston

137

Đối với toán tử tham số hàm chúng ta sử dụng:

let (đó là toán tử mặc định, vì vậy chúng ta có thể bỏ qua let ) để tạo một tham số không đổi (điều đó có nghĩa là chúng ta không thể sửa đổi ngay cả bản sao cục bộ);

var để làm cho nó biến (chúng ta có thể sửa đổi nó cục bộ, nhưng nó sẽ không ảnh hưởng đến biến bên ngoài đã được truyền cho hàm); và

inout để làm cho nó một tham số vào ra. Trong thực tế có nghĩa là vượt qua biến bằng tham chiếu, không phải theo giá trị. Và nó không chỉ đòi hỏi phải chấp nhận giá trị bằng tham chiếu, mà còn phải vượt qua nó bằng tham chiếu, vì vậy hãy chuyển nó bằng & - foo(&myVar)thay vì chỉfoo(myVar)

Vì vậy, làm điều đó như thế này:

var arr = [1, 2, 3]

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

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

Nói chính xác, nó không chỉ là một tham chiếu, mà còn là bí danh thực sự cho biến ngoài, vì vậy bạn có thể thực hiện một mẹo như vậy với bất kỳ loại biến nào, ví dụ với số nguyên (bạn có thể gán giá trị mới cho nó), mặc dù nó có thể không phải là một thực hành tốt và có thể gây nhầm lẫn khi sửa đổi các loại dữ liệu nguyên thủy như thế này.


11
Điều này không thực sự giải thích làm thế nào để sử dụng mảng như một biến thể hiện được tham chiếu không được sao chép.
Matej Ukmar

2
Tôi nghĩ rằng inout đã sử dụng một getter và setter để sao chép mảng thành tạm thời và sau đó thiết lập lại nó trên đường ra khỏi chức năng - tức là nó sao chép
dumbledad

2
Trên thực tế, sử dụng sao chép vào hoặc sao chép hoặc gọi theo kết quả giá trị. Tuy nhiên, như một tối ưu hóa, nó có thể sử dụng bằng cách tham khảo. "Là một tối ưu hóa, khi đối số là một giá trị được lưu trữ tại một địa chỉ vật lý trong bộ nhớ, cùng một vị trí bộ nhớ được sử dụng cả bên trong và bên ngoài thân hàm. Hành vi được tối ưu hóa được gọi là tham chiếu; nó đáp ứng tất cả các yêu cầu của mô hình sao chép sao chép trong khi loại bỏ chi phí sao chép. "
Tod Cickyham

11
Trong Swift 3, inoutvị trí đã thay đổi, tức làfunc addItem(localArr: inout [Int])
elquimista

3
Ngoài ra, varkhông còn có sẵn cho thuộc tính tham số chức năng.
elquimista

23

Xác định cho mình một giao diện BoxedArray<T>thực hiện Arraygiao diện nhưng ủy thác tất cả các chức năng cho một thuộc tính được lưu trữ. Như vậy

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

  // ...

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

Sử dụng BoxedArraybất cứ nơi nào bạn sử dụng một Array. Việc gán một BoxedArraysẽ theo tham chiếu, nó là một lớp và do đó thay đổi đối với thuộc tính được lưu trữ, thông qua Arraygiao diện, sẽ hiển thị cho tất cả các tham chiếu.


Một giải pháp hơi đáng sợ :) - không chính xác thanh lịch - nhưng có vẻ như nó sẽ hoạt động.
Matej Ukmar

Chà, chắc chắn là tốt hơn là quay lại 'Sử dụng NSArray' để có được 'vượt qua ngữ nghĩa tham chiếu'!
GoZoner

13
Tôi chỉ có cảm giác định nghĩa Array là struct thay vì class là lỗi thiết kế ngôn ngữ.
Matej Ukmar

Tôi đồng ý. Ngoài ra còn có sự ghê tởm trong đó Stringlà một kiểu con của AnyNHƯNG nếu import Foundationsau đó bạn Stringtrở thành một kiểu con của AnyObject.
GoZoner

19

Đối với phiên bản Swift 3-4 (XCode 8-9), hãy sử dụng

var arr = [1, 2, 3]

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

addItem(&arr)
print(arr)

3

Cái gì đó như

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

???


3
Tôi nghĩ rằng câu hỏi là yêu cầu một phương tiện để có các thuộc tính của hai đối tượng khác nhau trỏ đến cùng một mảng. Nếu đó là trường hợp, câu trả lời của Kaan là chính xác: người ta phải bọc mảng trong một lớp hoặc sử dụng NSArray.
Wes Campaigne

1
đúng, inout chỉ hoạt động trong suốt vòng đời của cơ thể chức năng (không có hành vi đóng cửa)
Christian Dietrich

Tiểu nit: đó là func test(b: inout [Int])... có lẽ đây là một cú pháp cũ; Tôi chỉ tham gia Swift vào năm 2016 và câu trả lời này là từ năm 2014 vì vậy có lẽ mọi thứ đã khác đi?
Ray Toal

2

Một lựa chọn khác là để người tiêu dùng của mảng yêu cầu chủ sở hữu cho nó khi cần thiết. Ví dụ, một cái gì đó dọc theo dòng:

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

Sau đó, bạn có thể sửa đổi mảng bao nhiêu tùy thích trong lớp Tài khoản. Lưu ý rằng điều này không giúp ích gì cho bạn nếu bạn cũng muốn sửa đổi mảng từ lớp trình điều khiển xem.


0

Sử dụng inoutlà một giải pháp nhưng tôi cảm thấy không hợp lý vì mảng là loại giá trị. Về mặt phong cách, cá nhân tôi thích trả lại một bản sao đột biến:

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
Có một nhược điểm hiệu suất cho loại điều này. Nó sử dụng pin điện thoại ít hơn một chút để chỉ sửa đổi mảng ban đầu :)
David Rector

Tôi tôn trọng không đồng ý. Trong trường hợp này, khả năng đọc tại trang web cuộc gọi thường vượt qua hiệu năng. Bắt đầu với mã bất biến và sau đó tối ưu hóa bằng cách làm cho nó có thể thay đổi sau đó. Có những trường hợp với mảng lớn mà bạn đúng nhưng trong hầu hết các ứng dụng đó là trường hợp cạnh 0,01%.
ToddH

1
Tôi không biết bạn đang không đồng ý với điều gì. Có một hình phạt hiệu năng để sao chép mảng và các hoạt động hiệu suất thấp hơn sử dụng nhiều CPU hơn và do đó nhiều pin hơn. Tôi nghĩ rằng bạn cho rằng tôi đã nói rằng đó là một giải pháp tốt hơn, mà tôi thì không. Tôi đã đảm bảo mọi người có tất cả thông tin có sẵn để đưa ra lựa chọn sáng suốt.
David Hiệu trưởng

1
Đúng, bạn đã đúng, không có câu hỏi nào hiệu quả hơn. Tôi nghĩ rằng bạn đang đề xuất rằng nó inoutlà tốt hơn vì nó tiết kiệm pin. Tôi muốn nói rằng tính dễ đọc, tính bất biến và độ an toàn của luồng của giải pháp này là tốt hơn và chỉ nên sử dụng như là một tối ưu hóa trong những trường hợp hiếm hoi trong trường hợp sử dụng bảo đảm nó.
ToddH

0

sử dụng a NSMutableArrayhoặc a NSArray, đó là các lớp

bằng cách này, bạn không cần phải thực hiện bất kỳ trình bao bọc nào và có thể sử dụng bản dựng trong bắc cầu

open class NSArray : NSObject, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.