ตัวเลือกใน Swift เป็นประเภทที่สามารถเก็บค่าได้หรือไม่มีค่าก็ได้ ตัวเลือกจะถูกเขียนโดยการผนวก a ?
กับชนิดใด ๆ :
var name: String? = "Bertie"
ตัวเลือก (พร้อมกับ Generics) เป็นหนึ่งในแนวคิดที่เข้าใจยากที่สุดของ Swift เนื่องจากมีการเขียนและใช้งานอย่างไรจึงเป็นเรื่องง่ายที่จะเข้าใจความผิดของสิ่งที่พวกเขาทำ เปรียบเทียบตัวเลือกด้านบนเพื่อสร้างสตริงปกติ:
var name: String = "Bertie" // No "?" after String
จากไวยากรณ์มันดูเหมือนว่าเป็นตัวเลือกสตริงจะคล้ายกันมากกับสตริงธรรมดา มันไม่ใช่. สตริงตัวเลือกไม่ใช่สตริงที่เปิดใช้งานการตั้งค่า "ทางเลือก" บางอย่าง มันไม่ใช่ความหลากหลายของสตริงพิเศษ สตริงและสตริงเสริมเป็นประเภทที่แตกต่างอย่างสิ้นเชิง
นี่คือสิ่งสำคัญที่สุดที่ควรทราบ: ตัวเลือกเป็นคอนเทนเนอร์ชนิดหนึ่ง สตริงตัวเลือกเป็นภาชนะซึ่งอาจมีสตริง Int ซึ่งเป็นตัวเลือกคือคอนเทนเนอร์ที่อาจมี Int คิดว่าตัวเลือกเป็นพัสดุชนิดหนึ่ง ก่อนที่คุณจะเปิด (หรือ "แกะ" ในภาษาของตัวเลือก) คุณจะไม่ทราบว่ามีบางอย่างหรือไม่มีอะไร
คุณสามารถดูวิธีการใช้งาน optionalsในห้องสมุด Swift Standard โดยพิมพ์ "ทางเลือก" ลงในไฟล์ Swift และคลิก it นี่คือส่วนสำคัญของคำจำกัดความ:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
ตัวเลือกเป็นเพียงenum
ที่สามารถเป็นหนึ่งในสองกรณีหรือ.none
.some
หาก.some
มีค่าที่เกี่ยวข้องซึ่งในตัวอย่างด้านบนจะเป็นString
"Hello" ตัวเลือกใช้ Generics เพื่อให้ประเภทกับค่าที่เกี่ยวข้อง ประเภทของสตริงตัวเลือกไม่ได้String
ก็หรืออย่างแม่นยำมากขึ้นOptional
Optional<String>
ทุกสิ่งที่ Swift ทำได้ด้วยตัวเลือกเป็นสิ่งมหัศจรรย์ที่ทำให้การอ่านและการเขียนรหัสคล่องแคล่วยิ่งขึ้น น่าเสียดายที่นี่ปิดบังวิธีการใช้งานจริง ฉันจะไปดูเคล็ดลับในภายหลัง
หมายเหตุ:ฉันจะพูดถึงตัวแปรทางเลือกเป็นจำนวนมาก แต่ก็ยังดีในการสร้างค่าคงที่ทางเลือกเช่นกัน ฉันทำเครื่องหมายตัวแปรทั้งหมดด้วยประเภทของพวกเขาเพื่อให้ง่ายต่อการเข้าใจประเภทของประเภทที่ถูกสร้างขึ้น แต่คุณไม่จำเป็นต้องใช้รหัสของคุณเอง
วิธีการสร้างทางเลือก
หากต้องการสร้างทางเลือกให้เพิ่ม?
ประเภทที่คุณต้องการตัดต่อท้าย ประเภทใดก็ได้สามารถเลือกได้แม้กระทั่งประเภทที่กำหนดเอง ?
คุณไม่สามารถมีช่องว่างระหว่างชนิดและที่
var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)
// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}
ใช้ตัวเลือก
คุณสามารถเปรียบเทียบตัวเลือกnil
เพื่อดูว่ามีค่าหรือไม่:
var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
print("There is a name")
}
if name == nil { // Could also use an "else"
print("Name has no value")
}
นี่เป็นความสับสนเล็กน้อย มันก็หมายความว่าตัวเลือกเป็นสิ่งหนึ่งหรืออย่างอื่น มันเป็นศูนย์หรือเป็น "บ็อบ" สิ่งนี้ไม่เป็นความจริงตัวเลือกจะไม่เปลี่ยนเป็นอย่างอื่น การเปรียบเทียบกับศูนย์คือเคล็ดลับในการสร้างโค้ดที่อ่านง่ายขึ้น ถ้าไม่จำเป็นเท่ากับศูนย์นี้ก็หมายความว่า enum .none
จะถูกตั้งไว้ที่
ตัวเลือกเท่านั้นที่สามารถเป็นศูนย์ได้
หากคุณพยายามตั้งค่าตัวแปรที่ไม่ใช่ตัวเลือกให้เป็นศูนย์คุณจะได้รับข้อผิดพลาด
var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'
อีกวิธีในการดูตัวเลือกคือการเสริมให้กับตัวแปร Swift ปกติ พวกเขาเป็นคู่กับตัวแปรที่รับประกันว่าจะมีค่า สวิฟท์เป็นภาษาที่ระมัดระวังซึ่งเกลียดความคลุมเครือ ตัวแปรส่วนใหญ่ถูกกำหนดให้ไม่ใช่ตัวเลือก แต่บางครั้งก็ไม่สามารถทำได้ ตัวอย่างเช่นลองนึกภาพตัวควบคุมมุมมองที่โหลดรูปภาพจากแคชหรือจากเครือข่าย อาจหรือไม่อาจมีรูปนั้นในเวลาที่สร้างตัวควบคุมมุมมอง ไม่มีวิธีรับประกันค่าสำหรับตัวแปรรูปภาพ ในกรณีนี้คุณจะต้องทำให้เป็นตัวเลือก มันเริ่มต้นเป็นnil
และเมื่อมีการดึงภาพตัวเลือกได้รับค่า
การใช้ตัวเลือกเผยให้เห็นเจตนาของโปรแกรมเมอร์ เมื่อเปรียบเทียบกับ Objective-C ซึ่งวัตถุใด ๆ อาจเป็นศูนย์ได้ Swift ต้องการให้คุณชัดเจนเกี่ยวกับค่าที่หายไปและเมื่อรับประกันว่ามีอยู่
หากต้องการใช้ตัวเลือกคุณต้อง "แกะ" มัน
เป็นตัวเลือกที่ไม่สามารถนำมาใช้ในสถานที่ของจริงString
String
หากต้องการใช้ค่าที่ห่อไว้ภายในตัวเลือกคุณจะต้องแกะมันออกมา วิธีที่ง่ายที่สุดในการแกะตัวเลือกเพิ่มเติมคือการเพิ่ม!
ชื่อที่เป็นทางเลือก สิ่งนี้เรียกว่า "กำลังคลายกำลัง" มันส่งคืนค่าภายในตัวเลือก (เป็นชนิดดั้งเดิม) แต่ถ้าเป็นตัวเลือกnil
มันจะทำให้เกิดความผิดพลาดของรันไทม์ ก่อนคลายคุณควรแน่ใจว่ามีค่า
var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")
name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.
การตรวจสอบและใช้งานอุปกรณ์เสริม
เนื่องจากคุณควรตรวจสอบศูนย์ก่อนที่จะคลายและใช้ตัวเลือกนี่คือรูปแบบทั่วไป:
var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
let unwrappedMealPreference: String = mealPreference!
print("Meal: \(unwrappedMealPreference)") // or do something useful
}
ในรูปแบบนี้คุณจะตรวจสอบว่ามีค่าอยู่หรือไม่และเมื่อคุณแน่ใจแล้วว่าเป็นค่านั้น เนื่องจากสิ่งนี้เป็นเรื่องปกติที่ต้องทำ Swift เสนอทางลัดโดยใช้ "ถ้าให้" สิ่งนี้เรียกว่า "การรวมหรือไม่ก็ได้"
var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
print("Meal: \(unwrappedMealPreference)")
}
สิ่งนี้จะสร้างค่าคงที่ชั่วคราว (หรือตัวแปรถ้าคุณแทนที่let
ด้วยvar
) ซึ่งมีขอบเขตอยู่ภายในเครื่องหมายปีกกาของ if เท่านั้น เนื่องจากต้องใช้ชื่อเช่น "unwrappedMealPreference" หรือ "realMealPreference" เป็นภาระ Swift ช่วยให้คุณสามารถนำชื่อตัวแปรเดิมมาใช้ใหม่โดยสร้างชื่อชั่วคราวภายในขอบเขตวงเล็บ
var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // separate from the other mealPreference
}
นี่คือรหัสเพื่อแสดงให้เห็นว่าใช้ตัวแปรที่แตกต่างกัน:
var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"
การรวมตัวเลือกทำงานโดยการตรวจสอบเพื่อดูว่าตัวเลือกเท่ากับศูนย์ หากไม่เป็นเช่นนั้นก็จะทำการแยกส่วนเสริมเข้าไปในค่าคงที่ที่ให้ไว้และดำเนินการบล็อก ใน Xcode 8.3 และใหม่กว่า (Swift 3.1) การพยายามพิมพ์ตัวเลือกเช่นนี้จะทำให้เกิดการเตือนที่ไร้ประโยชน์ ใช้ตัวเลือกdebugDescription
เพื่อปิดเสียง:
print("\(mealPreference.debugDescription)")
ทางเลือกคืออะไร
ตัวเลือกมีสองกรณีการใช้งาน:
- สิ่งที่สามารถล้มเหลว (ฉันคาดหวังบางอย่าง แต่ฉันไม่มีอะไร)
- สิ่งที่ไม่ได้เป็นอยู่ในตอนนี้ แต่อาจเป็นบางสิ่งในภายหลัง (และในทางกลับกัน)
ตัวอย่างที่เป็นรูปธรรม:
- คุณสมบัติที่สามารถมีหรือไม่มีเช่น
middleName
หรือspouse
ในPerson
ชั้นเรียน
- วิธีการที่สามารถคืนค่าหรือไม่มีอะไรเช่นการค้นหาการจับคู่ในอาร์เรย์
- วิธีที่สามารถส่งคืนผลลัพธ์หรือได้รับข้อผิดพลาดและไม่ส่งคืนสิ่งใดเช่นพยายามอ่านเนื้อหาของไฟล์ (ซึ่งโดยปกติแล้วจะส่งคืนข้อมูลของไฟล์) แต่ไฟล์นั้นไม่มีอยู่
- มอบหมายคุณสมบัติซึ่งไม่จำเป็นต้องตั้งค่าเสมอและโดยทั่วไปจะตั้งค่าหลังจากการเริ่มต้น
- สำหรับ
weak
คุณสมบัติในคลาส สิ่งที่พวกเขาชี้ไปสามารถตั้งค่าnil
ได้ตลอดเวลา
- ทรัพยากรขนาดใหญ่ที่อาจต้องมีการเปิดตัวเพื่อเรียกคืนหน่วยความจำ
- เมื่อคุณต้องการทราบวิธีเมื่อตั้งค่า (ข้อมูลที่ยังไม่โหลด> ข้อมูล) แทนที่จะใช้ data แยกโหลด
Boolean
ทางเลือกไม่มีอยู่ใน Objective-C แต่มีแนวคิดที่เทียบเท่าโดยคืนค่าเป็นศูนย์ วิธีการที่สามารถคืนค่าวัตถุสามารถส่งคืนศูนย์แทน นี่หมายถึง "การไม่มีวัตถุที่ถูกต้อง" และมักใช้เพื่อบอกว่ามีบางอย่างผิดปกติ ใช้ได้เฉพาะกับออบเจกต์ Objective-C เท่านั้นไม่ใช่แบบดั้งเดิมหรือแบบ C พื้นฐาน (enums, structs) วัตถุประสงค์ -C มักจะมีประเภทพิเศษเพื่อแสดงถึงการขาดค่าเหล่านี้ ( NSNotFound
ซึ่งจริงๆNSIntegerMax
แล้วkCLLocationCoordinate2DInvalid
เพื่อเป็นตัวแทนของพิกัดที่ไม่ถูกต้อง-1
หรือใช้ค่าลบบางอย่างเช่นกัน) รหัสจะต้องรู้เกี่ยวกับค่าพิเศษเหล่านี้ดังนั้นพวกเขาจะต้องมีเอกสารและเรียนรู้สำหรับแต่ละกรณี หากวิธีการไม่สามารถใช้nil
เป็นพารามิเตอร์ได้จะต้องมีการบันทึกไว้ ในวัตถุประสงค์ -Cnil
เป็นตัวชี้เช่นเดียวกับวัตถุทั้งหมดที่ถูกกำหนดเป็นพอยน์เตอร์ แต่nil
ชี้ไปยังที่อยู่ (ศูนย์) ที่เฉพาะเจาะจง ใน Swift nil
เป็นตัวอักษรซึ่งหมายถึงการขาดประเภทที่แน่นอน
เปรียบเทียบกับ nil
คุณเคยสามารถใช้ตัวเลือกใด ๆ เป็นBoolean
:
let leatherTrim: CarExtras? = nil
if leatherTrim {
price = price + 1000
}
leatherTrim != nil
ในรุ่นล่าสุดของสวิฟท์ที่คุณต้องใช้ ทำไมนี้ ปัญหาคือBoolean
สามารถห่อในตัวเลือก หากคุณมีBoolean
สิ่งนี้:
var ambiguous: Boolean? = false
มันมีสองชนิดของ "false" ซึ่งเป็นหนึ่งในสถานที่ที่ไม่มีค่าและเป็นหนึ่งในที่ที่มันมีค่า false
แต่มีค่าเป็น Swift เกลียดความกำกวมดังนั้นตอนนี้คุณต้องตรวจสอบตัวเลือกnil
เสมอ
คุณอาจสงสัยว่าตัวเลือกBoolean
คืออะไร? เช่นเดียวกับตัวเลือกอื่น ๆ.none
รัฐสามารถระบุได้ว่าค่านี้ยังไม่ทราบ อาจมีบางอย่างในส่วนอื่น ๆ ของการโทรผ่านเครือข่ายซึ่งใช้เวลาพอสมควรในการสำรวจความคิดเห็น Booleans ที่เป็นตัวเลือกจะเรียกว่า " Booleans สามค่า "
เทคนิคสวิฟท์
Swift ใช้เทคนิคบางอย่างเพื่อให้ตัวเลือกทำงานได้ พิจารณาโค้ดเสริมสามบรรทัดที่ดูธรรมดาเหล่านี้
var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }
ไม่ควรรวบรวมบรรทัดเหล่านี้
- บรรทัดแรกตั้งค่าสตริงเสริมโดยใช้สตริงตามตัวอักษรสองประเภทที่แตกต่างกัน แม้ว่านี่จะเป็น
String
ประเภทที่แตกต่างกัน
- บรรทัดที่สองตั้งค่าสตริงเสริมให้เป็นศูนย์ซึ่งมีสองประเภทที่แตกต่างกัน
- บรรทัดที่สามเปรียบเทียบสตริงที่เป็นทางเลือกกับไม่มีสองประเภทที่แตกต่างกัน
ฉันจะดูรายละเอียดการใช้งานของตัวเลือกที่อนุญาตให้สายเหล่านี้ทำงานได้
การสร้างทางเลือก
การใช้?
เพื่อสร้างตัวเลือกคือน้ำตาลประโยคโดยเปิดใช้งานโดยคอมไพเลอร์ Swift หากคุณต้องการทำมันให้ไกลคุณสามารถสร้างทางเลือกเช่นนี้:
var name: Optional<String> = Optional("Bob")
Optional
initializer แรกของการโทรนี้public init(_ some: Wrapped)
ซึ่ง infers ประเภทที่เกี่ยวข้องของตัวเลือกจากประเภทที่ใช้ภายในวงเล็บ
วิธีสร้างและตั้งค่าทางเลือกที่ยาวนานยิ่งขึ้น:
var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")
การตั้งค่าทางเลือกเป็น nil
คุณสามารถสร้างทางเลือกโดยไม่มีค่าเริ่มต้นหรือสร้างทางเลือกที่มีค่าเริ่มต้นเป็นnil
(ทั้งคู่มีผลลัพธ์เหมือนกัน)
var name: String?
var name: String? = nil
อนุญาตให้ใช้ตัวเลือกที่เท่ากันnil
โดยโปรโตคอลExpressibleByNilLiteral
(ชื่อก่อนหน้านี้NilLiteralConvertible
) ตัวเลือกจะถูกสร้างขึ้นด้วยOptional
initializer ที่สองของ, public init(nilLiteral: ())
. เอกสารบอกว่าคุณไม่ควรใช้ExpressibleByNilLiteral
เพื่ออะไรยกเว้นตัวเลือกเนื่องจากจะเปลี่ยนความหมายของศูนย์ในรหัสของคุณ แต่เป็นไปได้ที่จะทำ:
class Clint: ExpressibleByNilLiteral {
var name: String?
required init(nilLiteral: ()) {
name = "The Man with No Name"
}
}
let clint: Clint = nil // Would normally give an error
print("\(clint.name)")
โปรโตคอลเดียวกันนี้อนุญาตให้คุณตั้งค่าตัวเลือกที่สร้างไว้nil
แล้ว แม้ว่าจะไม่แนะนำ แต่คุณสามารถใช้ initializer แบบไม่มีศูนย์ได้โดยตรง:
var name: Optional<String> = Optional(nilLiteral: ())
เปรียบเทียบตัวเลือกเพื่อ nil
ตัวเลือกจะกำหนดตัวดำเนินการพิเศษ "==" และ "! =" สองตัวซึ่งคุณสามารถเห็นได้ในOptional
คำจำกัดความ วิธีแรก==
ให้คุณตรวจสอบว่าตัวเลือกใดมีค่าเท่ากับศูนย์ สองตัวเลือกที่แตกต่างกันซึ่งถูกตั้งค่าเป็น. ไม่มีจะเท่ากันหากประเภทที่เกี่ยวข้องเหมือนกัน เมื่อคุณเปรียบเทียบกับศูนย์เบื้องหลัง Swift จะสร้างตัวเลือกประเภทที่เกี่ยวข้องเดียวกันตั้งค่าเป็น. ไม่มีแล้วใช้สิ่งนั้นสำหรับการเปรียบเทียบ
// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
print("tuxedoRequired is nil")
}
==
ผู้ประกอบการที่สองช่วยให้คุณสามารถเปรียบเทียบสองตัวเลือก ทั้งสองจะต้องเป็นประเภทเดียวกันและประเภทนั้นต้องเป็นไปตามEquatable
(โปรโตคอลซึ่งช่วยให้การเปรียบเทียบสิ่งต่างๆกับตัวดำเนินการปกติ "==") Swift (สมมุติ) ปล่อยค่าสองค่าออกมาและเปรียบเทียบโดยตรง นอกจากนี้ยังจัดการกรณีที่หนึ่งหรือทั้งสอง optionals .none
มี สังเกตความแตกต่างระหว่างการเปรียบเทียบกับnil
ตัวอักษร
นอกจากนี้ยังช่วยให้คุณสามารถเปรียบเทียบEquatable
ประเภทใดก็ได้กับการตัดที่เลือกได้:
let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
print("It's a match!") // Prints "It's a match!"
}
เบื้องหลัง Swift จะล้อมตัวเลือกที่ไม่จำเป็นเป็นตัวเลือกก่อนการเปรียบเทียบ มันใช้งานได้กับตัวอักษรด้วย ( if 23 == numberFromString {
)
ฉันบอกว่ามี==
ผู้ให้บริการสองราย แต่จริงๆแล้วมีหนึ่งในสามที่ให้คุณใส่nil
ทางด้านซ้ายของการเปรียบเทียบ
if nil == name { ... }
ตัวเลือกการตั้งชื่อ
ไม่มีแบบแผน Swift สำหรับการตั้งชื่อประเภทที่เป็นตัวเลือกแตกต่างจากประเภทที่ไม่ใช่ทางเลือก ผู้คนหลีกเลี่ยงการเพิ่มบางอย่างลงในชื่อเพื่อแสดงว่าเป็นตัวเลือก (เช่น "optionalMiddleName" หรือ "possibleNumberAsString") และให้การประกาศแสดงว่าเป็นประเภทที่เป็นตัวเลือก สิ่งนี้ยากเมื่อคุณต้องการตั้งชื่อบางอย่างเพื่อเก็บค่าจากตัวเลือก ชื่อ "middleName" หมายถึงว่าเป็นประเภทสตริงดังนั้นเมื่อคุณแยกค่าสตริงออกจากนั้นคุณมักจะลงท้ายด้วยชื่อเช่น "realMiddleName" หรือ "unwrappedMiddleName" หรือ "realMiddleName" ใช้การโยงหรือไม่ก็ได้และใช้ชื่อตัวแปรซ้ำเพื่อแก้ไข
ความหมายอย่างเป็นทางการ
จาก"พื้นฐาน" ในภาษาโปรแกรม Swift :
Swift ยังแนะนำประเภทที่เป็นตัวเลือกซึ่งจัดการกับการขาดค่า ตัวเลือกบอกว่า "มีค่าและมันเท่ากับ x" หรือ "ไม่มีค่าเลย" Optionals คล้ายกับการใช้ศูนย์กับพอยน์เตอร์ใน Objective-C แต่มันใช้ได้กับทุกประเภทไม่ใช่แค่คลาส ตัวเลือกมีความปลอดภัยและแสดงออกได้มากกว่าตัวชี้ไม่มีใน Objective-C และเป็นหัวใจของคุณสมบัติที่ทรงพลังที่สุดของ Swift
ตัวเลือกเป็นตัวอย่างของข้อเท็จจริงที่ว่า Swift เป็นภาษาที่ปลอดภัยประเภท Swift ช่วยให้คุณมีความชัดเจนเกี่ยวกับประเภทของค่าที่โค้ดของคุณสามารถทำงานได้ หากส่วนหนึ่งของรหัสของคุณคาดว่าจะเป็นสตริงความปลอดภัยประเภทจะป้องกันคุณจากการส่งค่า Int โดยไม่ตั้งใจ สิ่งนี้ช่วยให้คุณสามารถตรวจจับและแก้ไขข้อผิดพลาดได้เร็วที่สุดในกระบวนการพัฒนา
เพื่อให้จบบทกวีของ 1899 เกี่ยวกับตัวเลือกมีดังนี้:
เมื่อวานนี้บนบันได
ฉันได้พบกับชายคนหนึ่งที่ไม่ได้อยู่ที่นั่น
เขาไม่ได้อยู่ที่นั่นอีกในวันนี้
ฉันต้องการฉันหวังว่าเขาจะจากไปแอน
ติโกนิช
แหล่งข้อมูลเพิ่มเติม: