Tại sao chọn Struct Over Class?


476

Chơi xung quanh với Swift, đến từ nền Java, tại sao bạn muốn chọn Struct thay vì Class? Có vẻ như chúng giống nhau, với Struct cung cấp ít chức năng hơn. Tại sao chọn nó sau đó?


11
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 và không sử dụng tính tham chiếu. nguồn: developer.apple.com/lvern/prerelease/ios/documentation/swift/
mẹo

4
Tôi sẽ nói rằng các cấu trúc được chiếm dụng nhiều hơn để giữ dữ liệu, không logic. Để nói theo thuật ngữ Java, hãy tưởng tượng các cấu trúc là "Đối tượng giá trị".
Vincent Guerci

6
Tôi ngạc nhiên trong toàn bộ cuộc trò chuyện này, không có đề cập trực tiếp đến bản sao chép hay còn gọi là bản sao lười biếng . Bất kỳ mối quan tâm về hiệu suất sao chép cấu trúc chủ yếu là tranh luận về thiết kế này.
David James

3
Chọn một cấu trúc trên một lớp học không phải là một vấn đề quan điểm. Có những lý do cụ thể để chọn cái này hay cái khác.
David James

Tôi đặc biệt khuyên bạn nên xem Tại sao Array không phải là threadSafe . Nó liên quan vì Mảng & Structs đều là loại giá trị. Tất cả các câu trả lời ở đây đề cập rằng với các cấu trúc / mảng / loại giá trị sẽ không bao giờ có vấn đề An toàn luồng, nhưng có một trường hợp góc mà bạn sẽ làm.
Mật ong

Câu trả lời:


548

Theo Chương trình định hướng giao thức nói chuyện WWDC 2015 rất phổ biến trong Swift ( video , bảng điểm ), Swift cung cấp một số tính năng giúp cấu trúc tốt hơn các lớp trong nhiều trường hợp.

Cấu trúc được ưu tiên hơn nếu chúng tương đối nhỏ và có thể thay đổi được vì sao chép an toàn hơn nhiều so với việc có nhiều tham chiếu đến cùng một thể hiện như xảy ra với các lớp. Điều này đặc biệt quan trọng khi chuyển xung quanh một biến sang nhiều lớp và / hoặc trong môi trường đa luồng. Nếu bạn luôn có thể gửi một bản sao của biến của bạn đến những nơi khác, bạn không bao giờ phải lo lắng về nơi khác thay đổi giá trị của biến bên dưới bạn.

Với Structs, sẽ không cần phải lo lắng nhiều về việc rò rỉ bộ nhớ hoặc nhiều luồng chạy đua để truy cập / sửa đổi một thể hiện của một biến. .

Các lớp cũng có thể trở nên cồng kềnh vì một lớp chỉ có thể kế thừa từ một siêu lớp duy nhất. Điều đó khuyến khích chúng ta tạo ra những chiếc siêu xe khổng lồ bao gồm nhiều khả năng khác nhau chỉ liên quan một cách lỏng lẻo. Sử dụng các giao thức, đặc biệt là với các phần mở rộng giao thức nơi bạn có thể cung cấp các triển khai cho các giao thức, cho phép bạn loại bỏ sự cần thiết của các lớp để đạt được loại hành vi này.

Cuộc nói chuyện đưa ra các kịch bản trong đó các lớp được ưu tiên:

  • Sao chép hoặc so sánh các trường hợp không có ý nghĩa (ví dụ: Window)
  • Tuổi thọ của cá thể được gắn với các hiệu ứng bên ngoài (ví dụ: Tệp tạm thời)
  • Các thực thể chỉ là "chìm" - ống dẫn chỉ ghi vào trạng thái bên ngoài (egCGContext)

Nó ngụ ý rằng các cấu trúc nên là mặc định và các lớp nên là một dự phòng.

Mặt khác, tài liệu Ngôn ngữ lập trình Swift có phần mâu thuẫn:

Các thể hiện cấu trúc luôn được truyền theo giá trị và các thể hiện của lớp luôn được truyền bằng tham chiếu. Điều này có nghĩa là chúng phù hợp với các loại nhiệm vụ khác nhau. Khi bạn xem xét các cấu trúc dữ liệu và chức năng mà bạn cần cho một dự án, hãy quyết định xem mỗi cấu trúc dữ liệu nên được xác định là một lớp hay là một cấu trúc.

Theo nguyên tắc chung, hãy xem xét việc tạo cấu trúc khi áp dụng một hoặc nhiều điều kiện sau:

  • Mục đích chính của cấu trúc là gói gọn một vài giá trị dữ liệu tương đối đơn giản.
  • Thật hợp lý khi hy vọng rằng các giá trị được đóng gói sẽ được sao chép thay vì được tham chiếu khi bạn gán hoặc chuyển xung quanh một thể hiện của cấu trúc đó.
  • Bất kỳ thuộc tính nào được lưu trữ bởi cấu trúc đều là các loại giá trị, chúng cũng được dự kiến ​​sẽ được sao chép thay vì được tham chiếu.
  • Cấu trúc không cần kế thừa các thuộc tính hoặc hành vi từ một loại hiện có khác.

Ví dụ về các ứng cử viên tốt cho các cấu trúc bao gồm:

  • Kích thước của hình dạng hình học, có lẽ gói gọn một thuộc tính chiều rộng và thuộc tính chiều cao, cả hai loại Double.
  • Một cách để tham chiếu đến các phạm vi trong một chuỗi, có lẽ gói gọn một thuộc tính bắt đầu và thuộc tính độ dài, cả hai loại Int.
  • Một điểm trong hệ tọa độ 3D, có lẽ gói gọn các thuộc tính x, y và z, mỗi thuộc tính Double.

Trong tất cả các trường hợp khác, hãy định nghĩa một lớp và tạo các thể hiện của lớp đó để được quản lý và chuyển qua tham chiếu. Trong thực tế, điều này có nghĩa là hầu hết các cấu trúc dữ liệu tùy chỉnh phải là các lớp, không phải các cấu trúc.

Ở đây có tuyên bố rằng chúng ta nên mặc định sử dụng các lớp và chỉ sử dụng các cấu trúc trong các trường hợp cụ thể. Cuối cùng, bạn cần hiểu ý nghĩa của các loại giá trị trong thế giới thực so với các loại tham chiếu và sau đó bạn có thể đưa ra quyết định có căn cứ về thời điểm sử dụng cấu trúc hoặc các lớp. Ngoài ra, hãy nhớ rằng các khái niệm này luôn phát triển và Tài liệu Ngôn ngữ lập trình Swift đã được viết trước khi bài nói chuyện Lập trình hướng giao thức được đưa ra.


12
@ElgsQianChen toàn bộ điểm của bài viết này là cấu trúc nên được chọn theo mặc định và lớp chỉ nên được sử dụng khi cần thiết. Structs an toàn hơn nhiều và không có lỗi, đặc biệt là trong môi trường đa luồng. Có, bạn luôn có thể sử dụng một lớp thay cho cấu trúc, nhưng các cấu trúc được ưu tiên hơn.
vẽ

16
@drewag Điều đó dường như trái ngược hoàn toàn với những gì nó đang nói. Nó đã nói rằng một lớp nên là mặc định mà bạn sử dụng, không phải là một cấu trúc In practice, this means that most custom data constructs should be classes, not structures.Bạn có thể giải thích cho tôi làm thế nào, sau khi đọc nó, bạn nhận được rằng hầu hết các tập dữ liệu nên là các cấu trúc chứ không phải các lớp không? Họ đã đưa ra một bộ quy tắc cụ thể khi một cái gì đó nên là một cấu trúc và khá nhiều người nói rằng "tất cả các kịch bản khác một lớp là tốt hơn."
Matt

42
Dòng cuối cùng nên nói: "Lời khuyên cá nhân của tôi trái ngược với tài liệu:" ... và đó là một câu trả lời tuyệt vời!
Dan Rosenstark 16/1/2015

5
Cuốn sách Swift 2.2 vẫn cho biết sử dụng các lớp trong hầu hết các tình huống.
David James

6
Struct over Class chắc chắn làm giảm sự phức tạp. Nhưng ý nghĩa của việc sử dụng bộ nhớ là gì khi cấu trúc trở thành lựa chọn mặc định. Khi mọi thứ được sao chép ở mọi nơi thay vì tham chiếu, nó sẽ làm tăng mức sử dụng bộ nhớ của ứng dụng. Có nên không?
MadNik

164

Vì các thể hiện cấu trúc được phân bổ trên ngăn xếp và các thể hiện lớp được phân bổ trên heap, nên các cấu trúc đôi khi có thể nhanh hơn rất nhiều.

Tuy nhiên, bạn nên luôn tự đo nó và quyết định dựa trên trường hợp sử dụng duy nhất của bạn.

Xem xét ví dụ sau, thể hiện 2 chiến lược gói Intkiểu dữ liệu bằng cách sử dụng structclass. Tôi đang sử dụng 10 giá trị lặp lại là để phản ánh tốt hơn thế giới thực, nơi bạn có nhiều trường.

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

Hiệu suất được đo bằng cách sử dụng

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

Mã có thể được tìm thấy tại https://github.com/knguyen2708/SturationVsClassPerformance

CẬP NHẬT (27 tháng 3 năm 2018) :

Kể từ Swift 4.0, Xcode 9.2, đang chạy Bản dựng phát hành trên iPhone 6S, iOS 11.2.6, cài đặt Trình biên dịch Swift là -O -whole-module-optimization:

  • class phiên bản mất 2.06 giây
  • struct phiên bản mất 4,17e-08 giây (nhanh hơn 50.000.000 lần)

(Tôi không còn trung bình nhiều lần chạy, vì phương sai rất nhỏ, dưới 5%)

Lưu ý : sự khác biệt là ít kịch tính hơn nếu không tối ưu hóa toàn bộ mô-đun. Tôi rất vui nếu ai đó có thể chỉ ra những gì cờ thực sự làm.


CẬP NHẬT (ngày 7 tháng 5 năm 2016) :

Kể từ Swift 2.2.1, Xcode 7.3, chạy Bản dựng phát hành trên iPhone 6S, iOS 9.3.1, trung bình trên 5 lần chạy, cài đặt Trình biên dịch Swift là -O -whole-module-optimization:

  • class phiên bản lấy 2.159942142s
  • struct phiên bản mất 5,83E-08 (nhanh hơn 37.000.000 lần)

Lưu ý : như ai đó đã đề cập rằng trong các kịch bản trong thế giới thực, có thể có nhiều hơn 1 trường trong một cấu trúc, tôi đã thêm các bài kiểm tra cho các cấu trúc / lớp với 10 trường thay vì 1. Đáng ngạc nhiên, kết quả không thay đổi nhiều.


KẾT QUẢ NGUỒN GỐC (ngày 1 tháng 6 năm 2014):

(Chạy trên struct / class với 1 trường chứ không phải 10)

Kể từ Swift 1.2, Xcode 6.3.2, chạy Bản dựng phát hành trên iPhone 5S, iOS 8.3, trung bình trên 5 lần chạy

  • class phiên bản lấy 9.788332333s
  • struct phiên bản mất 0,010532942s (nhanh hơn 900 lần)

KẾT QUẢ OLD (từ thời gian không xác định)

(Chạy trên struct / class với 1 trường chứ không phải 10)

Với bản phát hành trên MacBook Pro của tôi:

  • Các classphiên bản mất 1,10082 giây
  • Các structphiên bản mất 0,02324 giây (50 lần nhanh hơn)

27
Đúng, nhưng có vẻ như việc sao chép một loạt các cấu trúc xung quanh sẽ chậm hơn so với việc sao chép một tham chiếu đến một đối tượng. Nói cách khác, sao chép một con trỏ xung quanh nhanh hơn là sao chép một khối bộ nhớ lớn tùy ý.
Tylerc230

14
-1 Thử nghiệm này không phải là một ví dụ hay vì chỉ có một var duy nhất trên struct. Lưu ý rằng nếu bạn thêm một vài giá trị và một hoặc hai đối tượng, phiên bản struct sẽ tương đương với phiên bản lớp. Bạn càng thêm nhiều vars, phiên bản struct càng chậm.
joshrl

6
@joshrl có quan điểm của bạn, nhưng một ví dụ là "tốt" hay không phụ thuộc vào tình huống cụ thể. Mã này được trích xuất từ ​​ứng dụng của riêng tôi, vì vậy đây là trường hợp sử dụng hợp lệ và việc sử dụng các cấu trúc đã cải thiện ồ ạt hiệu suất cho ứng dụng của tôi. Đây có lẽ không phải là trường hợp sử dụng phổ biến (tốt, trường hợp sử dụng phổ biến là, đối với hầu hết các ứng dụng, không ai quan tâm đến việc dữ liệu có thể được truyền qua nhanh như thế nào, vì tắc nghẽn xảy ra ở một nơi khác, ví dụ như kết nối mạng, dù sao, tối ưu hóa không phải là quan trọng khi bạn có thiết bị GHz có GB hoặc RAM).
Khánh Nguyễn

26
Theo như tôi hiểu thì việc sao chép trong swift được tối ưu hóa để xảy ra tại thời điểm VIẾT. Điều đó có nghĩa là không có bản sao bộ nhớ vật lý được thực hiện trừ khi bản sao mới sắp bị thay đổi.
Matjan

6
Câu trả lời này đang cho thấy một ví dụ cực kỳ tầm thường, đến mức không thực tế và do đó không chính xác trong nhiều trường hợp. Một câu trả lời tốt hơn sẽ là "nó phụ thuộc."
iwasrobbed

60

Sự tương đồng giữa các cấu trúc và các lớp.

Tôi đã tạo ra ý chính cho điều này với các ví dụ đơn giản. https://github.com/objc-swift/swift-groupes-vs- cấu trúc

Và sự khác biệt

1. Kế thừa.

cấu trúc không thể kế thừa trong nhanh chóng. Nếu bạn muốn

class Vehicle{
}

class Car : Vehicle{
}

Đi cho một lớp học.

2. Đi qua

Các cấu trúc Swift truyền theo giá trị và các thể hiện lớp chuyển qua tham chiếu.

Sự khác biệt về bối cảnh

Cấu trúc hằng và biến

Ví dụ (Được sử dụng tại WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Xác định một cấu trúc gọi là Điểm.

var point = Point(x:0.0,y:2.0)

Bây giờ nếu tôi cố gắng thay đổi x. Đó là một biểu thức hợp lệ.

point.x = 5

Nhưng nếu tôi xác định một điểm là hằng số.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

Trong trường hợp này toàn bộ điểm là hằng số bất biến.

Nếu tôi đã sử dụng Điểm lớp thay vì đây là biểu thức hợp lệ. Bởi vì trong một hằng số bất biến của lớp là tham chiếu đến chính lớp đó không phải là các biến đối tượng của nó (Trừ khi các biến đó được định nghĩa là hằng số)


Bạn có thể kế thừa cấu trúc trong Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
đó là

12
Ý chính ở trên là về cách chúng ta có thể đạt được các hương vị kế thừa cho struct. Bạn sẽ thấy cú pháp như thế nào. A: B. Nó được cấu trúc gọi là Giao thức thực hiện có tên B. Tài liệu của Apple đề cập rõ ràng rằng struct không hỗ trợ kế thừa thuần túy và nó không hỗ trợ.
MadNik

2
người đàn ông mà đoạn cuối của bạn là tuyệt vời. Tôi luôn biết rằng bạn có thể thay đổi hằng số ... nhưng thỉnh thoảng tôi thấy bạn không thể ở đâu nên tôi thấy bối rối. Sự khác biệt này làm cho nó hiển thị
Mật ong

28

Dưới đây là một số lý do khác để xem xét:

  1. cấu trúc có được một trình khởi tạo tự động mà bạn hoàn toàn không phải duy trì mã.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")

Để có được điều này trong một lớp, bạn sẽ phải thêm trình khởi tạo và duy trì intializer ...

  1. Các loại bộ sưu tập cơ bản như Arraylà structs. Bạn càng sử dụng chúng trong mã của riêng mình, bạn sẽ càng quen với việc chuyển theo giá trị trái ngược với tham chiếu. Ví dụ:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Rõ ràng sự bất biến so với khả năng biến đổi là một chủ đề lớn, nhưng rất nhiều người thông minh nghĩ rằng sự bất biến - cấu trúc trong trường hợp này - là thích hợp hơn. Đối tượng biến đổi và bất biến


4
Đúng là bạn có bộ khởi động tự động. Bạn cũng nhận được một trình khởi tạo trống khi tất cả các thuộc tính là Tùy chọn. Nhưng, nếu bạn có một cấu trúc trong Khung, bạn cần phải tự viết trình khởi tạo nếu bạn muốn nó có sẵn ngoài internalphạm vi.
Abizern 11/03/2016

2
@Abizern đã xác nhận - stackoverflow.com/a/26224873/8047 - và người đàn ông đó thật phiền phức.
Dan Rosenstark

2
@Abizern có những lý do tuyệt vời cho mọi thứ trong Swift, nhưng mỗi khi điều gì đó đúng ở một nơi và không phải ở nơi khác, nhà phát triển phải biết nhiều thứ hơn. Tôi đoán đây là nơi tôi phải nói, "thật thú vị khi làm việc trong một ngôn ngữ đầy thách thức như vậy!"
Dan Rosenstark

4
Tôi cũng có thể nói thêm rằng đó không phải là sự bất biến của các cấu trúc làm cho chúng hữu ích (mặc dù đó là một điều rất tốt). Bạn có thể thay đổi cấu trúc, nhưng bạn phải đánh dấu các phương thức mutatingđể bạn rõ ràng về những chức năng thay đổi trạng thái của chúng. Nhưng bản chất của chúng là các loại giá trị là điều quan trọng. Nếu bạn khai báo một cấu trúc với letbạn, bạn không thể gọi bất kỳ hàm đột biến nào trên nó. Video WWDC 15 về lập trình tốt hơn thông qua các loại giá trị là một tài nguyên tuyệt vời về điều này.
Abizern

1
Cảm ơn @Abizern, tôi chưa bao giờ thực sự hiểu điều này trước khi đọc bình luận của bạn. Đối với các đối tượng, let vs var không có nhiều sự khác biệt, nhưng đối với các cấu trúc thì nó rất lớn. Cảm ơn đã chỉ ra điều này.
Dan Rosenstark 15/03/2016

27

Giả sử rằng chúng ta biết Struct là một loại giá trịClass là một loại tham chiếu .

Nếu bạn không biết loại giá trị và loại tham chiếu là thì hãy xem Sự khác biệt giữa chuyển qua tham chiếu so với chuyển qua giá trị là gì?

Dựa trên bài đăng của mikeash :

... Trước tiên hãy xem xét một số ví dụ cực kỳ rõ ràng. Số nguyên rõ ràng là có thể sao chép. Chúng nên là loại giá trị. Ổ cắm mạng không thể được sao chép hợp lý. Chúng nên là loại tham khảo. Điểm, như trong cặp x, y, có thể sao chép. Chúng nên là loại giá trị. Một bộ điều khiển đại diện cho một đĩa không thể được sao chép hợp lý. Đó phải là một loại tài liệu tham khảo.

Một số loại có thể được sao chép nhưng nó có thể không phải là thứ bạn muốn xảy ra mọi lúc. Điều này cho thấy rằng chúng nên là loại tham khảo. Ví dụ, một nút trên màn hình về mặt khái niệm có thể được sao chép. Bản sao sẽ không hoàn toàn giống với bản gốc. Một nhấp chuột vào bản sao sẽ không kích hoạt bản gốc. Bản sao sẽ không chiếm cùng một vị trí trên màn hình. Nếu bạn chuyển nút xung quanh hoặc đặt nó vào một biến mới, bạn có thể muốn tham khảo nút gốc và bạn chỉ muốn tạo một bản sao khi được yêu cầu rõ ràng. Điều đó có nghĩa là loại nút của bạn phải là loại tham chiếu.

Xem và điều khiển cửa sổ là một ví dụ tương tự. Chúng có thể được sao chép, có thể hiểu được, nhưng hầu như không bao giờ bạn muốn làm gì. Chúng nên là loại tham khảo.

Còn các kiểu mẫu thì sao? Bạn có thể có loại Người dùng đại diện cho người dùng trên hệ thống của bạn hoặc Loại Tội phạm đại diện cho một hành động được thực hiện bởi Người dùng. Đây là những bản sao đẹp, vì vậy chúng có thể là loại giá trị. Tuy nhiên, bạn có thể muốn các bản cập nhật cho Tội phạm của Người dùng được thực hiện ở một nơi trong chương trình của bạn để hiển thị cho các phần khác của chương trình. Điều này cho thấy rằng Người dùng của bạn nên được quản lý bởi một số loại bộ điều khiển người dùng sẽ là loại tham chiếu . ví dụ

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

Bộ sưu tập là một trường hợp thú vị. Chúng bao gồm những thứ như mảng và từ điển, cũng như chuỗi. Họ có thể sao chép? Chắc chắn. Là sao chép một cái gì đó bạn muốn xảy ra dễ dàng và thường xuyên? Điều đó ít rõ ràng hơn.

Hầu hết các ngôn ngữ nói "không" với điều này và làm cho các kiểu tham chiếu bộ sưu tập của họ. Điều này đúng trong Objective-C, Java và Python và JavaScript và hầu hết mọi ngôn ngữ khác mà tôi có thể nghĩ ra. (Một ngoại lệ chính là C ++ với các loại bộ sưu tập STL, nhưng C ++ là sự điên cuồng của thế giới ngôn ngữ, thứ làm mọi thứ trở nên kỳ lạ.)

Swift nói "có", điều đó có nghĩa là các loại như Array và Dictionary và String là các cấu trúc chứ không phải là các lớp. Chúng được sao chép khi gán và chuyển chúng dưới dạng tham số. Đây là một lựa chọn hoàn toàn hợp lý miễn là bản sao rẻ tiền, điều mà Swift rất cố gắng thực hiện. ...

Cá nhân tôi không đặt tên lớp học của tôi như thế. Tôi thường đặt tên cho mình là UserManager thay vì UserControll nhưng ý tưởng là như nhau

Ngoài ra, không sử dụng lớp khi bạn phải ghi đè từng và mọi phiên bản của hàm, tức là chúng không có bất kỳ chức năng chia sẻ nào .

Vì vậy, thay vì có một vài lớp con của một lớp. Sử dụng một số cấu trúc phù hợp với một giao thức.


Một trường hợp hợp lý khác cho các cấu trúc là khi bạn muốn thực hiện một delta / diff của mô hình cũ và mới. Với các loại tài liệu tham khảo, bạn không thể làm điều đó ra khỏi hộp. Với các loại giá trị, các đột biến không được chia sẻ.


1
Chính xác là loại giải thích tôi đang tìm kiếm. Rất vui được viết lên :)
androCoder-BD

Ví dụ về bộ điều khiển rất hữu ích
Hỏi P

1
@AskP Tôi đã gửi email cho mike mình và nhận thêm đoạn mã đó :)
Honey

18

Một số ưu điểm:

  • tự động chủ đề an toàn do không thể chia sẻ
  • sử dụng ít bộ nhớ hơn do không có isa và refcount (và trên thực tế là stack được phân bổ chung)
  • các phương thức luôn được gửi tĩnh, vì vậy có thể được nội tuyến (mặc dù @final có thể làm điều này cho các lớp)
  • lý do dễ dàng hơn về (không cần "sao chép phòng thủ" như điển hình với NSArray, NSString, v.v ...) vì lý do tương tự như an toàn luồng

Không chắc chắn nếu nó nằm ngoài phạm vi của câu trả lời này, nhưng bạn có thể giải thích (hoặc liên kết, tôi đoán) điểm "phương thức luôn được gửi tĩnh" không?
Dan Rosenstark

2
Chắc chắn rồi. Tôi cũng có thể đính kèm một cảnh báo cho nó. Mục đích của công văn động là chọn một triển khai khi bạn không biết nên sử dụng công cụ nào. Trong Swift, đó có thể là do thừa kế (có thể bị ghi đè trong lớp con) hoặc do hàm là chung (bạn không biết tham số chung sẽ là gì). Các cấu trúc không thể được kế thừa từ và tối ưu hóa toàn bộ mô-đun + chuyên môn hóa chung hầu như loại bỏ các tổng quát chưa biết, vì vậy các phương thức có thể được gọi trực tiếp thay vì phải tìm kiếm những gì cần gọi. Mặc dù vậy, thuốc generic không chuyên biệt vẫn thực hiện công văn động cho các cấu trúc
Catfish_Man

1
Cảm ơn, giải thích tuyệt vời. Vì vậy, chúng tôi mong đợi tốc độ thời gian chạy nhiều hơn, hoặc ít mơ hồ hơn từ góc độ IDE hoặc cả hai?
Dan Rosenstark 15/03/2016

1
Chủ yếu là trước đây.
Catfish_Man

Chỉ cần lưu ý rằng các phương thức không được gửi tĩnh nếu bạn tham chiếu cấu trúc thông qua một giao thức.
Cristik

12

Cấu trúc nhanh hơn nhiều so với Class. Ngoài ra, nếu bạn cần kế thừa thì bạn phải sử dụng Class. Điểm quan trọng nhất là Class là loại tham chiếu trong khi Cấu trúc là loại giá trị. ví dụ,

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    } 
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String  
}

bây giờ cho phép tạo cá thể của cả hai.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

bây giờ cho phép chuyển các thể hiện này đến hai hàm sửa đổi id, mô tả, đích, v.v.

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

cũng thế,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines" 
}

vì thế,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

Bây giờ nếu chúng tôi in id và mô tả của chuyến bay, chúng tôi sẽ nhận được

id = 200
description = "second flight of Virgin Airlines"

Ở đây, chúng ta có thể thấy id và mô tả của FlightA bị thay đổi do tham số được truyền cho phương thức sửa đổi thực sự trỏ đến địa chỉ bộ nhớ của đối tượng FlightA (kiểu tham chiếu).

bây giờ nếu chúng ta in id và mô tả về cá thể FLightB chúng ta nhận được,

id = 100
description = "first ever flight of Virgin Airlines"

Ở đây chúng ta có thể thấy rằng thể hiện của FlightB không bị thay đổi bởi vì trong phương thức redirectFlight2, thể hiện thực tế của Flight2 được truyền thay vì tham chiếu (kiểu giá trị).


2
bạn chưa bao giờ tạo một phiên bản của FLightB
David Seek

1
Vậy tại sao bạn lại nói về FlightB bro? Here we can see that the FlightB instance is not changed
David Seek

@ManojKarki, câu trả lời tuyệt vời. Chỉ muốn chỉ ra rằng bạn đã khai báo chuyến bayA hai lần khi tôi nghĩ rằng bạn muốn khai báo FlightA, sau đó là FlightB.
ScottyBlades

11

Structsđang value typeClassesđangreference type

  • Các loại giá trị nhanh hơn các loại Tham chiếu
  • Các thể hiện loại giá trị là an toàn trong môi trường đa luồng vì nhiều luồng có thể biến đổi thể hiện mà không phải lo lắng về các điều kiện cuộc đua hoặc bế tắc
  • Loại giá trị không có tham chiếu không giống như loại tham chiếu; do đó không có rò rỉ bộ nhớ.

Sử dụng một valueloại khi:

  • Bạn muốn các bản sao có trạng thái độc lập, dữ liệu sẽ được sử dụng trong mã trên nhiều luồng

Sử dụng một referenceloại khi:

  • Bạn muốn tạo trạng thái chia sẻ, có thể thay đổi.

Thông tin thêm cũng có thể được tìm thấy trong tài liệu của Apple

https://docs.swift.org/swift-book/L LanguageGuide / C gốmAndSt Structures.html


thông tin thêm

Các loại giá trị Swift được giữ trong ngăn xếp. Trong một quy trình, mỗi luồng có không gian ngăn xếp riêng, do đó, không có luồng nào khác có thể truy cập trực tiếp vào loại giá trị của bạn. Do đó không có điều kiện chủng tộc, khóa, khóa chết hoặc bất kỳ sự phức tạp đồng bộ hóa chủ đề liên quan.

Các loại giá trị không cần phân bổ bộ nhớ động hoặc đếm tham chiếu, cả hai đều là các hoạt động đắt tiền. Đồng thời phương thức trên các loại giá trị được gửi tĩnh. Những điều này tạo ra một lợi thế rất lớn trong việc ủng hộ các loại giá trị về hiệu suất.

Xin nhắc lại ở đây là danh sách Swift

Các loại giá trị:

  • Cấu trúc
  • Enum
  • Tuple
  • Nguyên thủy (Int, Double, Bool, v.v.)
  • Bộ sưu tập (Mảng, Chuỗi, Từ điển, Bộ)

Các loại tham khảo:

  • Lớp học
  • Bất cứ điều gì đến từ NSObject
  • Chức năng
  • Khép kín

5

Trả lời câu hỏi từ góc độ của các loại giá trị so với các loại tham chiếu, từ bài đăng trên blog này của Apple, nó sẽ xuất hiện rất đơn giản:

Sử dụng loại giá trị [ví dụ struct, enum] khi:

  • So sánh dữ liệu cá thể với == có ý nghĩa
  • Bạn muốn bản sao có trạng thái độc lập
  • Dữ liệu sẽ được sử dụng trong mã trên nhiều luồng

Sử dụng loại tham chiếu [ví dụ: lớp] khi:

  • So sánh danh tính cá thể với === có ý nghĩa
  • Bạn muốn tạo trạng thái chia sẻ, có thể thay đổi

Như đã đề cập trong bài viết đó, một lớp không có thuộc tính có thể ghi sẽ hoạt động giống hệt với cấu trúc, với (tôi sẽ thêm) một cảnh báo: cấu trúc là tốt nhất cho các mô hình an toàn luồng - một yêu cầu ngày càng sắp xảy ra trong kiến ​​trúc ứng dụng hiện đại.


3

Với các lớp bạn nhận được sự kế thừa và được truyền bằng tham chiếu, các cấu trúc không có sự kế thừa và được truyền theo giá trị.

Có những phiên WWDC tuyệt vời trên Swift, câu hỏi cụ thể này được trả lời rất chi tiết trong một trong số đó. Hãy chắc chắn rằng bạn xem những thứ đó, vì nó sẽ giúp bạn tăng tốc nhanh hơn nhiều so với hướng dẫn Ngôn ngữ hoặc iBook.


Bạn có thể cung cấp một số liên kết từ những gì bạn đề cập? Nguyên nhân trên WWDC có khá nhiều lựa chọn, tôi muốn xem một người nói về chủ đề cụ thể này
MMachinegun

Đối với tôi đây là một khởi đầu tốt ở đây: github.com/raywenderlich/ từ
MMachinegun

2
Có lẽ anh ấy đang nói về phiên tuyệt vời này: Lập trình hướng đến giao thức trong Swift. (Liên kết: video , bảng điểm )
zekel

2

Tôi sẽ không nói rằng structs cung cấp ít chức năng hơn.

Chắc chắn, bản thân là bất biến ngoại trừ trong một chức năng đột biến, nhưng đó là về nó.

Kế thừa hoạt động tốt miễn là bạn tuân theo ý tưởng cũ tốt rằng mọi lớp nên là trừu tượng hoặc cuối cùng.

Thực hiện các lớp trừu tượng như các giao thức và các lớp cuối cùng như các cấu trúc.

Điều thú vị về cấu trúc là bạn có thể làm cho các trường của mình có thể thay đổi mà không cần tạo trạng thái có thể thay đổi được chia sẻ vì sao chép trên ghi chú quan tâm đến điều đó :)

Đó là lý do tại sao các thuộc tính / lĩnh vực trong ví dụ sau đều có thể thay đổi, mà tôi sẽ không làm trong Java hoặc C # hoặc nhanh chóng lớp .

Cấu trúc kế thừa ví dụ với một chút sử dụng bẩn và đơn giản ở phía dưới trong hàm có tên "ví dụ":

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}

2

Trong Swift, một mẫu lập trình mới đã được giới thiệu là Lập trình hướng giao thức.

Mô hình sáng tạo:

Trong swift, Struct là một loại giá trị được tự động nhân bản. Do đó, chúng tôi nhận được các hành vi cần thiết để thực hiện mẫu nguyên mẫu miễn phí.

Trong khi đó các lớp là kiểu tham chiếu, không được tự động nhân bản trong quá trình gán. Để thực hiện mẫu nguyên mẫu, các lớp phải áp dụng NSCopyinggiao thức.


Bản sao nông chỉ sao chép tham chiếu, trỏ đến các đối tượng đó trong khi sao chép sâu sao chép tham chiếu của đối tượng.


Thực hiện sao chép sâu cho từng loại tham chiếu đã trở thành một nhiệm vụ tẻ nhạt. Nếu các lớp bao gồm loại tham chiếu xa hơn, chúng ta phải triển khai mẫu nguyên mẫu cho từng thuộc tính tham chiếu. Và sau đó chúng ta phải thực sự sao chép toàn bộ biểu đồ đối tượng bằng cách thực hiện NSCopyinggiao thức.

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

Bằng cách sử dụng các cấu trúc và enum , chúng tôi đã làm cho mã của chúng tôi đơn giản hơn vì chúng tôi không phải thực hiện logic sao chép.


1

Nhiều API ca cao yêu cầu các lớp con NSObject, điều này buộc bạn phải sử dụng lớp. Nhưng ngoài ra, bạn có thể sử dụng các trường hợp sau từ blog Swift của Apple để quyết định nên sử dụng loại giá trị struct / enum hay loại tham chiếu lớp.

https://developer.apple.com/swift/blog/?id=10


0

Một điểm không nhận được sự chú ý trong các câu trả lời này là một biến giữ một lớp so với một cấu trúc có thể lettrong một thời gian vẫn cho phép thay đổi các thuộc tính của đối tượng, trong khi bạn không thể làm điều này với một cấu trúc.

Điều này hữu ích nếu bạn không muốn biến bao giờ trỏ đến một đối tượng khác, nhưng vẫn cần sửa đổi đối tượng, tức là trong trường hợp có nhiều biến đối tượng mà bạn muốn cập nhật lần lượt từng đối tượng. Nếu nó là một cấu trúc, bạn phải cho phép biến được đặt lại thành một đối tượng khác hoàn toàn bằng cách sử dụng varđể thực hiện điều này, vì một loại giá trị không đổi trong Swift đúng cho phép không có đột biến, trong khi các loại tham chiếu (các lớp) không hành xử theo cách này.


0

Vì struct là các loại giá trị và bạn có thể tạo bộ nhớ rất dễ dàng lưu trữ vào stack. Cấu trúc có thể dễ dàng truy cập và sau phạm vi công việc, nó dễ dàng được giải phóng khỏi bộ nhớ ngăn xếp thông qua pop từ đầu ngăn xếp. Mặt khác, lớp là kiểu tham chiếu lưu trữ trong heap và các thay đổi được thực hiện trong một đối tượng lớp sẽ tác động đến đối tượng khác vì chúng được liên kết chặt chẽ và kiểu tham chiếu. Tất cả các thành viên của cấu trúc đều công khai trong khi tất cả các thành viên của lớp là riêng tư .

Nhược điểm của struct là nó không thể được kế thừa.


-7
  • Cấu trúc và lớp là các kiểu dữ liệu người dùng thách thức

  • Theo mặc định, cấu trúc là công khai trong khi lớp là riêng tư

  • Lớp thực hiện hiệu trưởng đóng gói

  • Các đối tượng của một lớp được tạo trên bộ nhớ heap

  • Lớp được sử dụng để tái sử dụng trong khi cấu trúc được sử dụng để nhóm dữ liệu trong cùng cấu trúc

  • Các thành viên dữ liệu cấu trúc không thể được khởi tạo trực tiếp nhưng chúng có thể được chỉ định bởi bên ngoài cấu trúc

  • Các thành viên dữ liệu lớp có thể được khởi tạo trực tiếp bởi hàm tạo ít tham số và được gán bởi hàm tạo tham số


2
Câu trả lời tồi tệ nhất!
J. Doe

sao chép câu trả lời dán
jawadAli
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.