การเล่นกับ 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 ...
ประเภทคอลเลกชันพื้นฐานเช่นArray
structs ยิ่งคุณใช้มันในรหัสของคุณเองมากเท่าไหร่คุณก็ยิ่งคุ้นเคยกับการส่งผ่านค่าโดยไม่ต้องอ้างอิง ตัวอย่างเช่น
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 ใช้สำหรับการใช้งานอีกครั้งในขณะที่โครงสร้างใช้สำหรับจัดกลุ่มข้อมูลในโครงสร้างเดียวกัน
ข้อมูลโครงสร้างสมาชิกไม่สามารถเริ่มต้นได้โดยตรง แต่สามารถกำหนดโดยโครงสร้างภายนอกได้
สมาชิกของคลาสข้อมูลสามารถเริ่มต้นได้โดยตรงโดยตัวสร้างพารามิเตอร์น้อยกว่าและกำหนดโดยตัวสร้างพารามิเตอร์