การเล่นกับ Swift ที่มาจากพื้นหลัง Java ทำไมคุณต้องการเลือก Struct แทนที่จะเป็น Class ดูเหมือนว่าพวกเขาจะเป็นสิ่งเดียวกันโดยมีโครงสร้างที่เสนอฟังก์ชันการทำงานน้อยลง ทำไมต้องเลือกมัน?
การเล่นกับ Swift ที่มาจากพื้นหลัง Java ทำไมคุณต้องการเลือก Struct แทนที่จะเป็น Class ดูเหมือนว่าพวกเขาจะเป็นสิ่งเดียวกันโดยมีโครงสร้างที่เสนอฟังก์ชันการทำงานน้อยลง ทำไมต้องเลือกมัน?
คำตอบ:
จากการที่ WWDC 2015 ได้รับความนิยมอย่างมากพูดคุยเกี่ยวกับการเขียนโปรแกรมเชิงโพรโทคอลใน Swift ( วิดีโอ , การถอดเสียง ) Swift มีคุณสมบัติหลายอย่างที่ทำให้โครงสร้างดีกว่าคลาสในหลาย ๆ สถานการณ์
มีโครงสร้างที่ดีกว่าถ้ามันมีขนาดค่อนข้างเล็กและสามารถจัดการได้เนื่องจากการคัดลอกนั้นปลอดภัยกว่าการอ้างอิงหลาย ๆ ตัวไปยังอินสแตนซ์เดียวกันกับที่เกิดขึ้นกับคลาส สิ่งนี้มีความสำคัญอย่างยิ่งเมื่อส่งผ่านตัวแปรไปยังคลาสต่างๆและ / หรือในสภาพแวดล้อมแบบมัลติเธรด หากคุณสามารถส่งสำเนาของตัวแปรของคุณไปยังที่อื่นคุณไม่ต้องกังวลเกี่ยวกับที่อื่นที่จะเปลี่ยนค่าของตัวแปรที่อยู่ข้างใต้คุณ
ด้วย Structs คุณไม่จำเป็นต้องกังวลเกี่ยวกับการรั่วไหลของหน่วยความจำหรือเธรดหลาย ๆ การแข่งขันเพื่อเข้าถึง / แก้ไขอินสแตนซ์เดียวของตัวแปร (สำหรับผู้ที่มีความรู้ด้านเทคนิคมากขึ้นข้อยกเว้นคือเมื่อทำการจับภาพโครงสร้างภายในการปิดเพราะจริง ๆ แล้วมันจะทำการบันทึกการอ้างอิงไปยังอินสแตนซ์เว้นแต่คุณจะทำเครื่องหมายอย่างชัดเจนเพื่อคัดลอก)
คลาสสามารถกลายเป็นป่องได้เนื่องจากคลาสสามารถสืบทอดจากซูเปอร์คลาสเดียวเท่านั้น นั่นกระตุ้นให้เราสร้างซูเปอร์คลาสขนาดใหญ่ที่รวมความสามารถที่แตกต่างกันมากมายที่เกี่ยวข้องอย่างหลวม ๆ การใช้โปรโตคอลโดยเฉพาะอย่างยิ่งกับส่วนขยายของโปรโตคอลที่คุณสามารถนำไปใช้งานกับโปรโตคอลได้ช่วยให้คุณไม่จำเป็นต้องใช้คลาสเพื่อให้เกิดพฤติกรรมเช่นนี้
การพูดวางโครงเรื่องเหล่านี้ในกรณีที่ต้องการเรียน:
- การคัดลอกหรือการเปรียบเทียบอินสแตนซ์ไม่สมเหตุสมผล (เช่นหน้าต่าง)
- อายุการใช้งานอินสแตนซ์เชื่อมโยงกับผลกระทบภายนอก (เช่น TemporaryFile)
- อินสแตนซ์เป็นเพียง "sinks" - conduits แบบเขียนอย่างเดียวไปยังสถานะภายนอก (เช่น CGGContext)
มันบอกเป็นนัยว่า structs ควรเป็นค่าเริ่มต้นและคลาสควรเป็นทางเลือก
ในทางตรงกันข้ามเอกสารภาษาการเขียนโปรแกรม Swiftค่อนข้างขัดแย้ง:
อินสแตนซ์ของโครงสร้างจะถูกส่งผ่านตามค่าเสมอและอินสแตนซ์ของคลาสจะถูกส่งผ่านโดยการอ้างอิงเสมอ ซึ่งหมายความว่าพวกเขาเหมาะสมกับงานประเภทต่าง ๆ เมื่อคุณพิจารณา data constructs และฟังก์ชันที่คุณต้องการสำหรับโครงการให้ตัดสินใจว่าแต่ละ data data ควรถูกกำหนดเป็นคลาสหรือเป็นโครงสร้าง
ตามแนวทางทั่วไปให้พิจารณาการสร้างโครงสร้างเมื่อมีเงื่อนไขเหล่านี้อย่างน้อยหนึ่งข้อ:
- วัตถุประสงค์หลักของโครงสร้างคือการสรุปค่าข้อมูลที่ค่อนข้างง่าย
- มีเหตุผลที่จะคาดหวังว่าค่าที่สรุปจะถูกคัดลอกแทนที่จะอ้างอิงเมื่อคุณกำหนดหรือส่งผ่านอินสแตนซ์ของโครงสร้างนั้น
- คุณสมบัติใด ๆ ที่จัดเก็บโดยโครงสร้างเป็นชนิดค่าตัวเองซึ่งคาดว่าจะถูกคัดลอกแทนที่จะอ้างอิง
- โครงสร้างไม่จำเป็นต้องสืบทอดคุณสมบัติหรือลักษณะการทำงานจากชนิดที่มีอยู่อื่น
ตัวอย่างของผู้สมัครที่ดีสำหรับโครงสร้างรวมถึง:
- ขนาดของรูปทรงเรขาคณิตซึ่งอาจห่อหุ้มคุณสมบัติความกว้างและคุณสมบัติความสูงทั้งสองประเภทเป็นคู่
- วิธีการอ้างถึงช่วงภายในชุดข้อมูลอาจจะห่อหุ้มคุณสมบัติเริ่มต้นและคุณสมบัติความยาวทั้งสองประเภทเป็น Int
- จุดหนึ่งในระบบพิกัด 3 มิติซึ่งอาจจะห่อหุ้มคุณสมบัติ x, y และ z ซึ่งเป็นประเภท Double แต่ละประเภท
ในกรณีอื่นทั้งหมดกำหนดคลาสและสร้างอินสแตนซ์ของคลาสนั้นเพื่อจัดการและส่งผ่านโดยการอ้างอิง ในทางปฏิบัติหมายความว่าการสร้างข้อมูลที่กำหนดเองส่วนใหญ่ควรเป็นคลาสไม่ใช่โครงสร้าง
นี่คือการอ้างว่าเราควรเริ่มต้นการใช้คลาสและโครงสร้างการใช้งานเฉพาะในสถานการณ์ที่เฉพาะเจาะจง ในที่สุดคุณต้องเข้าใจความหมายของประเภทค่าเทียบกับประเภทอ้างอิงแล้วคุณสามารถทำการตัดสินใจอย่างชาญฉลาดเกี่ยวกับเวลาที่จะใช้ structs หรือคลาส นอกจากนี้โปรดทราบว่าแนวคิดเหล่านี้มีการพัฒนาอยู่ตลอดเวลาและมีการเขียนเอกสารภาษา Swift Programming Language ก่อนที่จะมีการพูดคุยเกี่ยวกับ Protocol Oriented Programming
In practice, this means that most custom data constructs should be classes, not structures.คุณสามารถอธิบายให้ฉันฟังได้อย่างไรหลังจากอ่านแล้วคุณจะได้รับชุดข้อมูลส่วนใหญ่ที่ควรเป็นโครงสร้างและไม่ใช่คลาส พวกเขาให้ชุดของกฎเฉพาะเมื่อสิ่งที่ควรจะเป็นโครงสร้างและสวยมากพูดว่า "สถานการณ์อื่น ๆ ทั้งหมดชั้นดีกว่า"
เนื่องจากอินสแตนซ์ของโครงสร้างถูกจัดสรรบนสแต็กและอินสแตนซ์ของคลาสนั้นถูกจัดสรรบนฮีปบางครั้งโครงสร้างจึงสามารถเร็วขึ้นอย่างมาก
อย่างไรก็ตามคุณควรวัดด้วยตัวคุณเองและตัดสินใจตามกรณีการใช้งานเฉพาะของคุณ
พิจารณาตัวอย่างต่อไปนี้ซึ่งแสดงให้เห็นถึง 2 กลยุทธ์ของการตัดIntชนิดข้อมูลที่ใช้และstruct classฉันกำลังใช้ค่าซ้ำ 10 ค่าเพื่อสะท้อนโลกแห่งความเป็นจริงที่คุณมีหลายสาขา
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)
}
ประสิทธิภาพถูกวัดโดยใช้
// 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)")
}
สามารถดูรหัสได้ที่https://github.com/knguyen2708/StructVsClassPerformance
อัปเดต (27 มีนาคม 2018) :
ตั้งแต่ Swift 4.0, Xcode 9.2, กำลังรัน Release build บน iPhone 6S, iOS 11.2.6, การตั้งค่า Swift Compiler คือ-O -whole-module-optimization:
class รุ่นใช้เวลา 2.06 วินาทีstruct เวอร์ชั่นใช้เวลา 4.17e-08 วินาที (เร็วกว่า 50,000,000 ครั้ง)(ฉันไม่ได้ใช้การวิ่งหลายครั้งโดยเฉลี่ยอีกต่อไปเนื่องจากความต่างน้อยมากต่ำกว่า 5%)
หมายเหตุ : ความแตกต่างนั้นมีความสำคัญน้อยกว่ามากหากไม่มีการปรับแต่งโมดูลทั้งหมด ฉันจะดีใจถ้ามีคนชี้ให้เห็นว่าธงทำอะไรได้จริง
อัพเดท (7 พฤษภาคม 2559) :
ตั้งแต่ Swift 2.2.1, Xcode 7.3, รัน build build บน iPhone 6S, iOS 9.3.1, เฉลี่ยมากกว่า 5 run, การตั้งค่า Swift Compiler คือ-O -whole-module-optimization:
class รุ่นใช้เวลา 2.159942142 วินาทีstruct รุ่นใช้เวลา 5.83E-08s (เร็วขึ้น 37,000,000 เท่า)หมายเหตุ : เนื่องจากมีคนกล่าวว่าในสถานการณ์จริงจะมีมากกว่า 1 ฟิลด์ใน struct ฉันได้เพิ่มการทดสอบสำหรับ structs / คลาสที่มี 10 ฟิลด์แทนที่จะเป็น 1 น่าแปลกที่ผลลัพธ์ไม่ได้แตกต่างกันมากนัก
ผลลัพธ์ดั้งเดิม (1 มิถุนายน 2014):
(วิ่งบน struct / คลาสที่มี 1 ฟิลด์ไม่ใช่ 10)
ตั้งแต่ Swift 1.2, Xcode 6.3.2, กำลังรัน Release build บน iPhone 5S, iOS 8.3, เฉลี่ยมากกว่า 5 run
class รุ่นเอา 9.788332333sstruct รุ่นใช้เวลา 0.010532942s (เร็วขึ้น 900 เท่า)ผลลัพธ์เก่า (จากเวลาที่ไม่รู้จัก)
(วิ่งบน struct / คลาสที่มี 1 ฟิลด์ไม่ใช่ 10)
ด้วยรุ่นวางจำหน่ายใน MacBook Pro ของฉัน:
classรุ่นเอา 1.10082 วินาทีstructรุ่นเอา 0.02324 วินาที (50 ครั้งเร็วกว่า)ฉันสร้างส่วนสำคัญสำหรับสิ่งนี้ด้วยตัวอย่างง่ายๆ https://github.com/objc-swift/swift-classes-vs-structures
โครงสร้างไม่สามารถสืบทอดได้อย่างรวดเร็ว ถ้าคุณต้องการ
class Vehicle{
}
class Car : Vehicle{
}
ไปเรียน
โครงสร้าง Swift ส่งผ่านค่าและอินสแตนซ์ของคลาสส่งผ่านโดยอ้างอิง
โครงสร้างคงที่และตัวแปร
ตัวอย่าง (ใช้ที่ WWDC 2014)
struct Point{
var x = 0.0;
var y = 0.0;
}
กำหนด struct ชื่อ Point
var point = Point(x:0.0,y:2.0)
ทีนี้ถ้าฉันลองเปลี่ยน x มันเป็นการแสดงออกที่ถูกต้อง
point.x = 5
แต่ถ้าฉันกำหนดจุดเป็นค่าคงที่
let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.
ในกรณีนี้จุดทั้งหมดคงที่ไม่เปลี่ยนแปลง
ถ้าฉันใช้คลาสพอยต์แทนนี่เป็นนิพจน์ที่ถูกต้อง เพราะในค่าคงที่ไม่เปลี่ยนชั้นของคลาสเป็นการอ้างอิงถึงคลาสเองไม่ใช่ตัวแปรอินสแตนซ์ (ยกเว้นตัวแปรที่กำหนดเป็นค่าคงที่)
ต่อไปนี้เป็นเหตุผลอื่นที่ควรพิจารณา:
structs รับ initializer อัตโนมัติที่คุณไม่จำเป็นต้องบำรุงรักษาในโค้ดเลย
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")ในการรับสิ่งนี้ในคลาสคุณจะต้องเพิ่ม initializer และบำรุงรักษา intializer ...
ประเภทคอลเลกชันพื้นฐานเช่นArraystructs ยิ่งคุณใช้มันในรหัสของคุณเองมากเท่าไหร่คุณก็ยิ่งคุ้นเคยกับการส่งผ่านค่าโดยไม่ต้องอ้างอิง ตัวอย่างเช่น
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]เห็นได้ชัดว่าไม่สามารถเปลี่ยนแปลงได้และความไม่แน่นอนเป็นหัวข้อใหญ่ แต่คนฉลาดจำนวนมากคิดว่าการเปลี่ยนแปลงไม่ได้ - โครงสร้างในกรณีนี้ - เป็นที่นิยมมากกว่า วัตถุที่เปลี่ยนแปลงไม่ได้และไม่เปลี่ยนรูป
internalขอบเขต
mutatingเพื่อให้คุณชัดเจนเกี่ยวกับฟังก์ชั่นการเปลี่ยนแปลงสถานะของพวกเขา แต่ธรรมชาติของพวกเขาในฐานะประเภทคุณค่าคือสิ่งที่สำคัญ หากคุณประกาศโครงสร้างด้วยletคุณจะไม่สามารถเรียกใช้ฟังก์ชันการกลายพันธุ์ใด ๆ ได้ วิดีโอ WWDC 15 เกี่ยวกับการตั้งโปรแกรมที่ดีขึ้นผ่านประเภทค่าเป็นทรัพยากรที่ยอดเยี่ยมสำหรับสิ่งนี้
สมมติว่าเรารู้ว่าโครงสร้างเป็นประเภทค่าและชั้นเป็นชนิดการอ้างอิง
หากคุณไม่ทราบว่าประเภทค่าและประเภทอ้างอิงคืออะไรให้ดูความแตกต่างระหว่างการส่งต่อโดยอ้างอิงกับการผ่านตามค่าคืออะไร
ตามโพสต์ของ mikeash :
... ลองดูตัวอย่างที่ชัดเจนและชัดเจนก่อน จำนวนเต็มสามารถคัดลอกได้อย่างชัดเจน ควรเป็นประเภทค่า ซ็อกเก็ตเครือข่ายไม่สามารถคัดลอกได้อย่างสมเหตุสมผล ควรเป็นประเภทอ้างอิง คะแนนเช่นเดียวกับคู่ x, y สามารถคัดลอกได้ ควรเป็นประเภทค่า ไม่สามารถคัดลอกคอนโทรลเลอร์ที่ใช้แทนดิสก์ได้ นั่นควรเป็นประเภทอ้างอิง
บางประเภทสามารถคัดลอกได้ แต่อาจไม่ใช่สิ่งที่คุณต้องการให้เกิดขึ้นตลอดเวลา สิ่งนี้ชี้ให้เห็นว่าควรเป็นประเภทอ้างอิง ตัวอย่างเช่นปุ่มบนหน้าจอสามารถคัดลอกแนวคิด สำเนาจะไม่เหมือนกับต้นฉบับ การคลิกที่สำเนาจะไม่เปิดใช้งานต้นฉบับ การคัดลอกจะไม่ใช้ตำแหน่งเดียวกันบนหน้าจอ หากคุณส่งปุ่มไปรอบ ๆ หรือวางไว้ในตัวแปรใหม่คุณอาจต้องการอ้างถึงปุ่มเดิมและคุณต้องการทำสำเนาเฉพาะเมื่อได้รับการร้องขออย่างชัดเจนเท่านั้น นั่นหมายความว่าประเภทปุ่มของคุณควรเป็นประเภทอ้างอิง
ตัวควบคุมมุมมองและหน้าต่างเป็นตัวอย่างที่คล้ายกัน พวกเขาอาจจะคัดลอกเป็นไปได้ แต่แทบจะไม่เคยทำสิ่งที่คุณต้องการ ควรเป็นประเภทอ้างอิง
ชนิดของโมเดลล่ะ คุณอาจมีประเภทผู้ใช้ที่เป็นตัวแทนของผู้ใช้ในระบบของคุณหรือประเภทอาชญากรรมที่แสดงถึงการกระทำของผู้ใช้ สิ่งเหล่านี้สามารถคัดลอกได้สวยดังนั้นจึงควรเป็นประเภทค่า อย่างไรก็ตามคุณอาจต้องการอัปเดตอาชญากรรมของผู้ใช้ที่เกิดขึ้นในที่เดียวในโปรแกรมของคุณเพื่อให้ปรากฏในส่วนอื่น ๆ ของโปรแกรม นี้แสดงให้เห็นว่าผู้ใช้ของคุณควรจะจัดการโดยการเรียงลำดับของการควบคุมผู้ใช้บางอย่างที่จะเป็นชนิดการอ้างอิง เช่น
struct User {} class UserController { var users: [User] func add(user: User) { ... } func remove(userNamed: String) { ... } func ... }คอลเลกชันเป็นกรณีที่น่าสนใจ สิ่งเหล่านี้รวมถึงสิ่งต่างๆเช่นอาร์เรย์และพจนานุกรมเช่นเดียวกับสตริง พวกเขาสามารถคัดลอกได้หรือไม่ อย่างชัดเจน การทำสำเนาเป็นสิ่งที่คุณต้องการเกิดขึ้นอย่างง่ายดายและบ่อยครั้งหรือไม่? นั่นชัดเจนน้อยกว่า
ภาษาส่วนใหญ่พูดว่า "ไม่" สำหรับสิ่งนี้และสร้างประเภทอ้างอิงคอลเลกชัน สิ่งนี้เป็นจริงใน Objective-C และ Java และ Python และ JavaScript และเกือบทุกภาษาอื่น ๆ ที่ฉันนึกออก (ข้อยกเว้นหนึ่งที่สำคัญคือ C ++ ที่มีคอลเลกชันประเภท STL แต่ C ++ เป็นภาษาที่เพ้อคลั่งของโลกภาษาซึ่งทำทุกอย่างแปลก ๆ )
Swift กล่าวว่า "ใช่" ซึ่งหมายความว่าประเภทเช่น Array และ Dictionary และ String เป็น struct มากกว่าคลาส พวกเขาได้รับการคัดลอกในการมอบหมายและผ่านพวกเขาเป็นพารามิเตอร์ นี่เป็นตัวเลือกที่สมเหตุสมผลอย่างสมบูรณ์ตราบใดที่สำเนามีราคาถูกซึ่ง Swift พยายามอย่างหนักเพื่อให้บรรลุผลสำเร็จ ...
โดยส่วนตัวฉันไม่ตั้งชื่อชั้นเรียนของฉันแบบนั้น ฉันมักจะตั้งชื่อ Mine UserManagerแทนUserControllerแต่ความคิดนั้นเหมือนกัน
นอกจากนี้อย่าใช้คลาสเมื่อคุณต้องแทนที่แต่ละอินสแตนซ์ของฟังก์ชันเช่นพวกเขาไม่มีฟังก์ชั่นที่ใช้ร่วมกัน
ดังนั้นแทนที่จะมีคลาสย่อยหลายคลาส ใช้โครงสร้างหลายอย่างที่สอดคล้องกับโปรโตคอล
อีกกรณีที่เหมาะสมสำหรับ structs คือเมื่อคุณต้องการทำเดลต้า / ต่างรุ่นเก่าและใหม่ของคุณ ด้วยประเภทการอ้างอิงคุณไม่สามารถทำสิ่งนั้นได้นอกกรอบ ด้วยประเภทค่าการกลายพันธุ์จะไม่ถูกแชร์
ข้อดีบางประการ:
โครงสร้างนั้นเร็วกว่าคลาสมาก นอกจากนี้หากคุณต้องการรับมรดกคุณต้องใช้คลาส จุดที่สำคัญที่สุดคือ Class นั้นเป็นประเภทอ้างอิงในขณะที่โครงสร้างเป็นประเภทค่า ตัวอย่างเช่น,
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
}
ตอนนี้ให้สร้างตัวอย่างของทั้งคู่
var flightA = Flight()
var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )
ตอนนี้ให้ส่งผ่านอินสแตนซ์เหล่านี้ไปยังสองฟังก์ชันที่ปรับเปลี่ยน id คำอธิบายปลายทาง ฯลฯ
func modifyFlight(flight:Flight) -> Void {
flight.id = 200
flight.description = "second flight of Virgin Airlines"
flight.destination = "new york"
flight.airlines = "Virgin Airlines"
}
นอกจากนี้ยังมี
func modifyFlight2(flight2: Flight2) -> Void {
var passedFlight = flight2
passedFlight.id = 200
passedFlight.description = "second flight from virgin airlines"
}
ดังนั้น,
modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)
ตอนนี้ถ้าเราพิมพ์รหัสและคำอธิบายของ flightA เราก็จะได้
id = 200
description = "second flight of Virgin Airlines"
ที่นี่เราสามารถเห็น id และคำอธิบายของ FlightA มีการเปลี่ยนแปลงเนื่องจากพารามิเตอร์ที่ส่งผ่านไปยังวิธีการแก้ไขจริง ๆ แล้วชี้ไปยังที่อยู่หน่วยความจำของวัตถุ FlightA (ชนิดอ้างอิง)
ตอนนี้ถ้าเราพิมพ์ id และคำอธิบายของอินสแตนซ์ของ FLightB ที่เราได้รับ
id = 100
description = "first ever flight of Virgin Airlines"
ที่นี่เราจะเห็นได้ว่าอินสแตนซ์ของ FlightB จะไม่เปลี่ยนแปลงเพราะในเมธอด alterFlight2 อินสแตนซ์ที่แท้จริงของ Flight2 จะผ่านไปแทนที่จะอ้างอิง (ประเภทค่า)
Here we can see that the FlightB instance is not changed
Structsเป็นvalue typeและClassesเป็นreference type
ใช้valueประเภทเมื่อ:
ใช้referenceประเภทเมื่อ:
สามารถดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบของ Apple
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
ข้อมูลเพิ่มเติม
ประเภทค่า Swift จะถูกเก็บไว้ในสแต็ก ในกระบวนการแต่ละเธรดมีพื้นที่สแต็กของตัวเองดังนั้นจึงไม่มีเธรดอื่นใดที่สามารถเข้าถึงชนิดค่าของคุณได้โดยตรง ดังนั้นจึงไม่มีสภาพการแข่งขันล็อคการหยุดชะงักหรือความซับซ้อนในการซิงโครไนซ์เธรดที่เกี่ยวข้อง
ชนิดของค่าไม่จำเป็นต้องมีการจัดสรรหน่วยความจำแบบไดนามิกหรือการนับการอ้างอิงซึ่งทั้งสองเป็นการดำเนินการที่มีราคาแพง ในเวลาเดียวกันวิธีการในประเภทค่าจะถูกส่งแบบคงที่ สิ่งเหล่านี้สร้างความได้เปรียบอย่างมากต่อประเภทของมูลค่าในแง่ของประสิทธิภาพ
เพื่อเป็นการเตือนความจำนี่คือรายการของ Swift
ประเภทค่า:
ประเภทอ้างอิง:
ตอบคำถามจากมุมมองของประเภทค่าเทียบกับประเภทอ้างอิงจากโพสต์บล็อกของ Apple นี้มันจะง่ายมาก:
ใช้ประเภทค่า [เช่น struct, enum] เมื่อ:
- การเปรียบเทียบข้อมูลอินสแตนซ์กับ == สมเหตุสมผล
- คุณต้องการให้สำเนามีสถานะเป็นอิสระ
- ข้อมูลจะถูกใช้ในรหัสในหลายกระทู้
ใช้ประเภทการอ้างอิง [เช่นคลาส] เมื่อ:
- เปรียบเทียบตัวตนของอินสแตนซ์กับ === สมเหตุสมผลแล้ว
- คุณต้องการสร้างสถานะที่แชร์และไม่แน่นอน
ดังที่ได้กล่าวไว้ในบทความนั้นคลาสที่ไม่มีคุณสมบัติที่เขียนได้จะทำงานเหมือนกับ struct ด้วย (ฉันจะเพิ่ม) หนึ่ง caveat: structs ดีที่สุดสำหรับโมเดลที่ปลอดภัยสำหรับเธรด - ข้อกำหนดที่ใกล้เข้ามามากขึ้นในสถาปัตยกรรมแอปสมัยใหม่
ด้วยคลาสที่คุณได้รับการสืบทอดและถูกส่งผ่านโดยการอ้างอิง structs ไม่มีการสืบทอดและถูกส่งผ่านโดยค่า
มีเซสชัน WWDC ที่ยอดเยี่ยมสำหรับ Swift คำถามเฉพาะนี้ได้รับคำตอบโดยละเอียดในหนึ่งในนั้น ตรวจสอบให้แน่ใจว่าคุณได้ดูสิ่งเหล่านี้เพราะมันจะช่วยให้คุณเพิ่มความเร็วได้เร็วขึ้นกว่าเดิมจากคู่มือภาษาหรือ iBook
ฉันจะไม่พูดว่า structs มีฟังก์ชั่นน้อยลง
แน่นอนว่าตนเองนั้นไม่สามารถเปลี่ยนแปลงได้ยกเว้นในฟังก์ชั่นการกลายพันธุ์ แต่มันเกี่ยวกับมัน
การรับมรดกทำงานได้ดีตราบใดที่คุณยึดติดกับแนวคิดเก่าที่ดีว่าทุกชั้นควรเป็นนามธรรมหรือสุดท้าย
ใช้คลาสนามธรรมเป็นโปรโตคอลและคลาสสุดท้ายเป็นโครงสร้าง
สิ่งที่ดีเกี่ยวกับ structs คือคุณสามารถทำให้เขตข้อมูลของคุณไม่แน่นอนโดยไม่ต้องสร้างสถานะที่ไม่แน่นอนที่ใช้ร่วมกันเพราะการคัดลอกเมื่อเขียนจะดูแล :)
นั่นเป็นเหตุผลที่คุณสมบัติ / เขตข้อมูลในตัวอย่างต่อไปนี้ทุกคนแน่นอนซึ่งผมจะไม่ทำใน Java หรือ C # หรือรวดเร็วเรียน
ตัวอย่างโครงสร้างการสืบทอดที่มีการใช้งานที่สกปรกและตรงไปตรงมาที่ด้านล่างในฟังก์ชันชื่อ "ตัวอย่าง":
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)")
}
ใน Swift รูปแบบการเขียนโปรแกรมใหม่ได้รับการแนะนำให้รู้จักกับการเขียนโปรแกรมเชิงโพรโทคอล
รูปแบบ Creational:
ใน swift, Struct เป็นชนิดของค่าที่ถูกโคลนโดยอัตโนมัติ ดังนั้นเราจึงได้รับพฤติกรรมที่จำเป็นในการใช้รูปแบบต้นแบบได้ฟรี
ในขณะที่คลาสเป็นประเภทอ้างอิงซึ่งไม่ได้ถูกโคลนโดยอัตโนมัติระหว่างการกำหนด ในการนำรูปแบบต้นแบบมาใช้คลาสต้องนำNSCopyingโปรโตคอลมาใช้
ตื้นคัดลอกซ้ำการอ้างอิงเท่านั้นที่ชี้ไปที่วัตถุเหล่านั้นในขณะที่การคัดลอกลึกทำซ้ำการอ้างอิงของวัตถุ
การทำสำเนาลึกสำหรับการอ้างอิงแต่ละประเภทได้กลายเป็นงานที่น่าเบื่อ หากคลาสมีประเภทการอ้างอิงเพิ่มเติมเราต้องใช้รูปแบบต้นแบบสำหรับคุณสมบัติการอ้างอิงแต่ละรายการ จากนั้นเราต้องคัดลอกกราฟวัตถุทั้งหมดโดยใช้NSCopyingโปรโตคอล
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
ด้วยการใช้structs และ enumsเราทำให้โค้ดของเราง่ายขึ้นเนื่องจากเราไม่ต้องใช้ตรรกะในการคัดลอก
Cocoa APIs จำนวนมากต้องการคลาสย่อย NSObject ซึ่งบังคับให้คุณใช้คลาส แต่นอกเหนือจากนั้นคุณสามารถใช้กรณีต่อไปนี้จากบล็อก Swift ของ Apple เพื่อตัดสินใจว่าจะใช้ประเภทค่า struct / enum หรือประเภทการอ้างอิงระดับ
จุดหนึ่งที่ไม่ได้รับความสนใจในคำตอบเหล่านี้คือตัวแปรที่ถือคลาสกับโครงสร้างสามารถอยู่ได้letในขณะที่ยังยอมให้เปลี่ยนแปลงคุณสมบัติของวัตถุได้ในขณะที่คุณไม่สามารถทำสิ่งนี้ได้ด้วยโครงสร้าง
สิ่งนี้มีประโยชน์หากคุณไม่ต้องการให้ตัวแปรชี้ไปยังวัตถุอื่น แต่ยังคงต้องแก้ไขวัตถุเช่นในกรณีที่มีตัวแปรอินสแตนซ์จำนวนมากที่คุณต้องการอัปเดตหลังจากนั้น หากเป็นโครงสร้างคุณต้องอนุญาตให้ตัวแปรถูกรีเซ็ตเป็นวัตถุอื่นโดยสิ้นเชิงvarเพื่อใช้ในการทำเช่นนี้เนื่องจากประเภทค่าคงที่ใน Swift จะช่วยให้การกลายพันธุ์เป็นศูนย์ได้อย่างถูกต้องในขณะที่ประเภทอ้างอิง (คลาส) ไม่ทำงานในลักษณะนี้
เนื่องจาก struct เป็นชนิดของค่าและคุณสามารถสร้างหน่วยความจำได้อย่างง่ายดายซึ่งเก็บลงในสแต็กโครงสร้างสามารถเข้าถึงได้ง่ายและหลังจากขอบเขตของงานมันจะถูกจัดสรรคืนอย่างง่ายดายจากหน่วยความจำสแต็คผ่านป๊อปจากด้านบนของสแต็ค ในอีกทางหนึ่งคลาสเป็นประเภทอ้างอิงที่เก็บในกองและการเปลี่ยนแปลงที่เกิดขึ้นในวัตถุชั้นหนึ่งจะส่งผลกระทบต่อวัตถุอื่น ๆ ที่พวกเขาเป็นคู่อย่างแน่นหนาและประเภทอ้างอิงสมาชิกทั้งหมดของโครงสร้างเป็นสาธารณะในขณะที่สมาชิกทั้งหมดของชั้นเป็นส่วนตัว .
ข้อเสียของโครงสร้างคือไม่สามารถสืบทอดได้
โครงสร้างและคลาสเป็นประเภทข้อมูลที่ผู้ใช้ระบุ
โดยค่าเริ่มต้นโครงสร้างเป็นสาธารณะในขณะที่คลาสเป็นส่วนตัว
ชั้นเรียนดำเนินการหลักของการห่อหุ้ม
วัตถุของคลาสที่ถูกสร้างขึ้นในหน่วยความจำฮีป
Class ใช้สำหรับการใช้งานอีกครั้งในขณะที่โครงสร้างใช้สำหรับจัดกลุ่มข้อมูลในโครงสร้างเดียวกัน
ข้อมูลโครงสร้างสมาชิกไม่สามารถเริ่มต้นได้โดยตรง แต่สามารถกำหนดโดยโครงสร้างภายนอกได้
สมาชิกของคลาสข้อมูลสามารถเริ่มต้นได้โดยตรงโดยตัวสร้างพารามิเตอร์น้อยกว่าและกำหนดโดยตัวสร้างพารามิเตอร์