Swift Pass By Value hay Pass By Reference


100

Tôi thực sự mới làm quen với Swift và tôi chỉ đọc rằng các lớp được truyền bằng tham chiếu và các mảng / chuỗi, v.v. được sao chép.

Quá trình chuyển bằng tham chiếu có giống như trong Objective-C hoặc Java, trong đó bạn thực sự chuyển tham chiếu "a" hay nó là chuyển qua tham chiếu thích hợp?


"Chuyển qua tham chiếu có giống như trong Objective-C hoặc Java không" Cả Objective-C và Java đều không có tham chiếu chuyển qua.
newacct

2
Đúng. Tôi biết điều đó. Bạn không vượt qua tham chiếu. Bạn chuyển tham chiếu theo giá trị. Tôi cho rằng điều đó đã được biết khi trả lời.
gran_profaci

Java chuyển theo giá trị, không phải tham chiếu.
6rchid

Câu trả lời:


167

Các loại thứ trong Swift

Quy tắc là:

  • Các cá thể lớp là các kiểu tham chiếu (nghĩa là tham chiếu của bạn đến một cá thể lớp thực sự là một con trỏ )

  • Các hàm là các loại tham chiếu

  • Mọi thứ khác là một kiểu giá trị ; "mọi thứ khác" chỉ đơn giản có nghĩa là các bản sao của cấu trúc và bản sao của enum, vì đó là tất cả những gì có trong Swift. Ví dụ, mảng và chuỗi là các thể hiện cấu trúc. Bạn có thể chuyển một tham chiếu đến một trong những thứ đó (như một đối số của hàm) bằng cách sử dụng inoutvà lấy địa chỉ, như newacct đã chỉ ra. Nhưng bản thân kiểu là một kiểu giá trị.

Loại tham chiếu nào có ý nghĩa đối với bạn

Một đối tượng kiểu tham chiếu là đặc biệt trong thực tế vì:

  • Chỉ gán hoặc chuyển tới hàm có thể mang lại nhiều tham chiếu đến cùng một đối tượng

  • Bản thân đối tượng có thể thay đổi ngay cả khi tham chiếu đến nó là một hằng số ( let, rõ ràng hoặc ngụ ý).

  • Một đột biến đối với đối tượng ảnh hưởng đến đối tượng đó như được thấy bởi tất cả các tham chiếu đến nó.

Đó có thể là những nguy hiểm, vì vậy hãy chú ý theo dõi. Mặt khác, việc truyền một kiểu tham chiếu rõ ràng là hiệu quả bởi vì chỉ một con trỏ được sao chép và truyền, điều này rất nhỏ.

Loại giá trị nào có ý nghĩa đối với bạn

Rõ ràng, việc chuyển một kiểu giá trị là "an toàn hơn" và letcó nghĩa là nó nói: bạn không thể thay đổi một cá thể struct hoặc cá thể enum thông qua một lettham chiếu. Mặt khác, sự an toàn đó đạt được bằng cách tạo một bản sao riêng biệt của giá trị, phải không? Điều đó không làm cho việc chuyển một loại giá trị có khả năng tốn kém?

Vâng, có và không. Nó không tệ như bạn nghĩ. Như Nate Cook đã nói, việc chuyển một loại giá trị không nhất thiết ngụ ý sao chép, bởi vì let(rõ ràng hoặc ngụ ý) đảm bảo tính bất biến nên không cần phải sao chép bất cứ thứ gì. Và ngay cả việc chuyển vào một vartham chiếu không có nghĩa là mọi thứ sẽ được sao chép, chỉ là chúng có thể được nếu cần thiết (vì có sự đột biến). Các tài liệu đặc biệt khuyên bạn không nên để quần lót bị xoắn.


6
"Các phiên bản của lớp được truyền bằng tham chiếu. Các hàm được truyền bằng tham chiếu" Không. Nó là giá trị truyền khi tham số không inoutphân biệt loại. Liệu một cái gì đó là tham chiếu truyền qua có trực giao với các kiểu hay không.
newacct

4
@newacct Chà, tất nhiên là bạn đúng theo nghĩa chặt chẽ! Nghiêm túc mà nói, người ta nên nói rằng mọi thứ đều là giá trị truyền nhưng rằng các cá thể enum và các cá thể cấu trúc là các kiểu giá trị và các cá thể và hàm của lớp là các kiểu tham chiếu . Ví dụ: xem developer.apple.com/swift/blog/?id=10 - Cũng xem developer.apple.com/library/ios/documentation/Swift/Conceptual/… Tuy nhiên, tôi nghĩ những gì tôi đã nói phù hợp với chung nghĩa của các từ có nghĩa.
matt

6
Không nên nhầm lẫn các loại giá trị / kiểu tham chiếu với giá trị chuyển qua / giá trị chuyển qua tham chiếu, bởi vì các loại giá trị có thể được chuyển theo giá trị hoặc bằng tham chiếu và các loại tham chiếu cũng có thể được chuyển theo giá trị hoặc tham chiếu.
newacct

1
@newacct thảo luận rất hữu ích; Tôi đang viết lại bản tóm tắt của mình để tránh gây hiểu lầm.
matt

43

luôngiá trị truyền khi không phải là tham số inout.

luôntham chiếu truyền nếu là tham số inout. Tuy nhiên, điều này hơi phức tạp bởi thực tế là bạn cần sử dụng &toán tử một cách rõ ràng trên đối số khi chuyển tới một inouttham số, vì vậy nó có thể không phù hợp với định nghĩa truyền thống về tham chiếu truyền, nơi bạn truyền trực tiếp biến.


3
Câu trả lời này, kết hợp với Nate Cook, đã rõ ràng hơn đối với tôi (đến từ C ++) trên thực tế là ngay cả một "loại tài liệu tham khảo" sẽ không được sửa đổi nằm ngoài phạm vi chức năng trừ khi bạn chỉ định rõ ràng nó (bằng cách sử dụng inout)
gobe

10
inoutthực sự không được chuyển qua tham chiếu mà là copy-in copy-out Nó chỉ đảm bảo rằng giá trị sửa đổi sau khi gọi hàm sẽ được gán cho đối số ban đầu. Tham số In-Out
Dalija Prasnikar

mặc dù đúng là mọi thứ đều trôi qua theo giá trị. Các thuộc tính kiểu tham chiếu có thể được sửa đổi bên trong hàm vì bản sao tham chiếu đến cùng một thể hiện.
MrAn3

42

Mọi thứ trong Swift đều được truyền bằng "copy" theo mặc định, vì vậy khi bạn chuyển một kiểu giá trị, bạn sẽ nhận được một bản sao của giá trị và khi bạn chuyển một kiểu tham chiếu, bạn sẽ nhận được một bản sao của tham chiếu, với tất cả những gì ngụ ý. (Có nghĩa là, bản sao của tham chiếu vẫn trỏ đến cùng một trường hợp với tham chiếu gốc.)

Tôi sử dụng các trích dẫn đáng sợ xung quanh "bản sao" ở trên vì Swift thực hiện rất nhiều tối ưu hóa; bất cứ nơi nào có thể, nó sẽ không sao chép cho đến khi có đột biến hoặc khả năng đột biến. Vì các tham số là không thay đổi theo mặc định, điều này có nghĩa là hầu hết thời gian không có bản sao thực sự xảy ra.


Đối với tôi, đây là câu trả lời tốt nhất vì nó đã làm rõ rằng các thuộc tính ví dụ có thể được sửa đổi bên trong hàm ngay cả khi tham số là một bản sao (chuyển theo giá trị) vì nó trỏ đến cùng một tham chiếu.
MrAn3

9

Đây là một mẫu mã nhỏ để tham khảo. Tránh làm điều này, trừ khi bạn có lý do chính đáng.

func ComputeSomeValues(_ value1: inout String, _ value2: inout Int){
    value1 = "my great computation 1";
    value2 = 123456;
}

Gọi nó như thế này

var val1: String = "";
var val2: Int = -1;
ComputeSomeValues(&val1, &val2);

Tại sao bạn nên tránh làm điều này?
Brainless

1
@Brainless vì nó thêm phức tạp không cần thiết vào mã. Tốt nhất là lấy các tham số và trả về một kết quả duy nhất. Phải làm điều này, thường báo hiệu thiết kế kém. Một cách khác để đặt nó là các tác dụng phụ ẩn trong các biến được tham chiếu được truyền vào không minh bạch đối với người gọi.
Chris Amelinckx

Điều này không vượt qua tham chiếu. inoutlà toán tử copy in, copy out. Đầu tiên nó sẽ sao chép trong đối tượng, sau đó ghi đè đối tượng gốc sau khi hàm trả về. Mặc dù nó có vẻ giống nhau, nhưng có những khác biệt nhỏ.
Hannes Hertach

7

Các nhà phát triển của Apple Swift blog có một bài gọi là giá trị gia tăng và tham khảo các loại cung cấp một cuộc thảo luận rõ ràng và chi tiết về chủ đề này rất.

Để trích:

Các loại trong Swift thuộc một trong hai loại: thứ nhất, “loại giá trị”, trong đó mỗi phiên bản giữ một bản sao duy nhất của dữ liệu của nó, thường được định nghĩa là cấu trúc, enum hoặc tuple. Thứ hai, "kiểu tham chiếu", trong đó các cá thể chia sẻ một bản sao dữ liệu duy nhất và kiểu thường được định nghĩa là một lớp.

Bài đăng trên blog Swift tiếp tục giải thích sự khác biệt với các ví dụ và gợi ý khi nào bạn nên sử dụng cái này thay cho cái kia.


1
Điều này không trả lời câu hỏi. Câu hỏi là về truyền qua giá trị so với tham chiếu chuyển qua hoàn toàn trực giao với các loại giá trị so với các loại tham chiếu.
Jörg W Mittag

2

Các lớp được truyền bởi các tham chiếu và các lớp khác được chuyển theo giá trị theo mặc định. Bạn có thể chuyển qua tham chiếu bằng cách sử dụng inouttừ khóa.


Điều này là không đúng. inoutlà toán tử copy in, copy out. Đầu tiên nó sẽ sao chép trong đối tượng, sau đó ghi đè đối tượng gốc sau khi hàm trả về. Mặc dù nó có vẻ giống nhau, nhưng có những khác biệt nhỏ.
Hannes Hertach

2

Khi bạn sử dụng inout với toán tử infix như + = thì biểu tượng & địa chỉ có thể bị bỏ qua. Tôi đoán trình biên dịch giả sử chuyển qua tham chiếu?

extension Dictionary {
    static func += (left: inout Dictionary, right: Dictionary) {
        for (key, value) in right {
            left[key] = value
        }
    }
}

origDictionary + = newDictionaryToAdd

Và độc đáo là từ điển này 'thêm' chỉ một từ ghi vào tài liệu tham khảo gốc, rất tuyệt vời để khóa!


2

Các lớp và cấu trúc

Một trong những điểm khác biệt quan trọng nhất giữa cấu trúc và lớp là các cấu trúc luôn được sao chép khi chúng được truyền xung quanh trong mã của bạn, nhưng các lớp được chuyển bằng tham chiếu.

Đóng cửa

Nếu bạn gán một bao đóng cho một thuộc tính của một cá thể lớp và bao đóng bắt cá thể đó bằng cách tham chiếu đến cá thể đó hoặc các thành viên của nó, bạn sẽ tạo ra một chu trình tham chiếu mạnh mẽ giữa bao đóng và cá thể. Swift sử dụng danh sách nắm bắt để phá vỡ các chu kỳ tham chiếu mạnh mẽ này

ARC (Đếm tham chiếu tự động)

Việc đếm tham chiếu chỉ áp dụng cho các trường hợp của lớp. Cấu trúc và kiểu liệt kê là kiểu giá trị, không phải kiểu tham chiếu, và không được lưu trữ và chuyển qua tham chiếu.

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.