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 đó?
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 đó?
Câu trả lời:
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.
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."
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 Int
kiểu dữ liệu bằng cách sử dụng struct
và class
. 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âystruct
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.159942142sstruct
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.788332333sstruct
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:
class
phiên bản mất 1,10082 giâystruct
phiên bản mất 0,02324 giây (50 lần nhanh hơn)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
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.
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.
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ố)
Dưới đây là một số lý do khác để xem xét:
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 ...
Các loại bộ sưu tập cơ bản như Array
là 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]
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
internal
phạm vi.
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 let
bạ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.
Giả sử rằng chúng ta biết Struct là một loại giá trị và 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à gì 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ẻ.
Một số ưu điểm:
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ị).
Here we can see that the FlightB instance is not changed
Structs
đang value type
và Classes
đangreference type
Sử dụng một value
loại khi:
Sử dụng một reference
loại khi:
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ác loại tham khảo:
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.
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.
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)")
}
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 NSCopying
giao 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 NSCopying
giao 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.
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.
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ể let
trong 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.
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.
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ố