คำตอบนี้เป็นวิกิพีเดียชุมชน หากคุณรู้สึกว่ามันจะดีขึ้นอย่าลังเลที่จะแก้ไขมัน !
พื้นหลัง: อะไรคือตัวเลือก
ใน Swift Optionalเป็นประเภททั่วไปที่สามารถมีค่า (ทุกชนิด) หรือไม่มีค่าเลย
ในหลายภาษาโปรแกรมอื่น ๆ โดยเฉพาะอย่างยิ่ง "แมวมอง" ค่ามักจะใช้เพื่อบ่งชี้ถึงการขาดของค่า ใน Objective-C เช่นnil( ตัวชี้โมฆะ ) บ่งชี้ว่าไม่มีวัตถุ แต่สิ่งนี้จะยุ่งยากมากขึ้นเมื่อทำงานกับประเภทดั้งเดิม - ควร-1ใช้เพื่อระบุว่าไม่มีจำนวนเต็มหรือบางทีINT_MINหรือจำนวนเต็มอื่น ๆ หากเลือกค่าใดโดยเฉพาะเพื่อหมายถึง "ไม่มีจำนวนเต็ม" นั่นหมายความว่าจะไม่สามารถใช้เป็นค่าที่ถูกต้องได้อีกต่อไป
Swift เป็นภาษาที่ปลอดภัยประเภทซึ่งหมายความว่าภาษาจะช่วยให้คุณมีความชัดเจนเกี่ยวกับประเภทของค่าที่รหัสของคุณสามารถทำงานได้ หากส่วนหนึ่งของรหัสของคุณคาดว่าจะเป็นสตริงความปลอดภัยประเภทจะป้องกันคุณจากการส่งค่า Int โดยไม่ได้ตั้งใจ
ใน Swift ประเภทใดก็ได้สามารถเลือกทำได้ ค่าตัวเลือกสามารถใช้ในค่าใด ๆ จากประเภทเดิมหรือnilค่าพิเศษ
ตัวเลือกจะถูกกำหนดด้วย?คำต่อท้ายในประเภท:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
การขาดค่าในตัวเลือกจะแสดงโดยnil:
anOptionalInt = nil
(โปรดทราบว่าสิ่งนี้nilไม่เหมือนกับnilใน Objective-C ใน Objective-C nilคือไม่มีตัวชี้วัตถุที่ถูกต้องใน Swift ตัวเลือกไม่ได้ จำกัด เฉพาะประเภทวัตถุ / การอ้างอิงตัวเลือกทำงานคล้ายกับอาจของ Haskell )
เหตุใดฉันจึงได้รับ“ ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่แกะค่าที่ไม่จำเป็นออก”
ในการเข้าถึงค่าของทางเลือก (ถ้ามีอย่างใดอย่างหนึ่ง) คุณต้องแกะมัน ค่าตัวเลือกสามารถถอดออกได้อย่างปลอดภัยหรือบังคับใช้ หากคุณบังคับให้แกะตัวเลือกและไม่มีค่าใด ๆ โปรแกรมของคุณจะทำงานผิดพลาดพร้อมกับข้อความด้านบน
Xcode จะแสดงให้คุณเห็นความผิดพลาดโดยการเน้นบรรทัดของรหัส ปัญหาเกิดขึ้นในบรรทัดนี้

ความผิดพลาดนี้สามารถเกิดขึ้นได้กับการบังคับให้แกะสองแบบ:
1. Force Unwrapping ที่ชัดเจน
สิ่งนี้ทำกับ!โอเปอเรเตอร์บนอุปกรณ์เสริม ตัวอย่างเช่น:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่แกะค่าที่ไม่จำเป็นออก
ในฐานะที่anOptionalStringเป็นnilที่นี่คุณจะได้รับความผิดพลาดในบรรทัดที่คุณบังคับให้แกะมัน
2. ตัวเลือก Unwrapped โดยนัย
สิ่งเหล่านี้ถูกนิยามด้วย a !, แทนที่จะเป็น a ?after
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
ตัวเลือกเหล่านี้จะถือว่ามีค่า ดังนั้นเมื่อใดก็ตามที่คุณเข้าถึงตัวเลือกที่ไม่ได้เปิดใช้งานโดยปริยายมันจะถูกบังคับโดยอัตโนมัติสำหรับคุณ หากไม่มีค่ามันจะผิดพลาด
print(optionalDouble) // <- CRASH
ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่คลายค่าทางเลือกโดยปริยาย
เพื่อหาว่าตัวแปรใดทำให้เกิดข้อผิดพลาดคุณสามารถกดค้างไว้⌥เพื่อคลิกเพื่อแสดงคำจำกัดความซึ่งคุณอาจพบประเภทที่เป็นทางเลือก

โดยเฉพาะอย่างยิ่ง IBOutlets เป็นตัวเลือกที่ไม่ได้เปิดใช้งานโดยปริยาย นี่เป็นเพราะ xib หรือกระดานเรื่องราวของคุณจะเชื่อมโยงร้านค้าที่รันไทม์หลังจากเริ่มต้น ดังนั้นคุณควรตรวจสอบให้แน่ใจว่าคุณไม่ได้เข้าถึงร้านค้าก่อนที่พวกเขาจะถูกโหลดคุณควรตรวจสอบว่าการเชื่อมต่อนั้นถูกต้องในไฟล์สตอรีบอร์ด / xib มิฉะนั้นค่าจะอยู่nilในช่วงรันไทม์ . เมื่อแก้ไขการเชื่อมต่อให้ลองลบบรรทัดของรหัสที่กำหนดร้านค้าของคุณจากนั้นเชื่อมต่อใหม่
เมื่อใดที่ฉันควรบังคับให้แกะตัวเลือกเพิ่มเติม
อย่างชัดเจน Force Unwrapping
ตามกฎทั่วไปคุณไม่ควรบังคับให้แกะตัวเลือกเพิ่มเติมกับตัว!ดำเนินการอย่างชัดเจน อาจมีบางกรณีที่การใช้งาน!เป็นที่ยอมรับ - แต่คุณควรจะใช้มันหากคุณมั่นใจ 100% ว่าตัวเลือกมีค่า
ในขณะที่มีอาจจะเป็นโอกาสที่คุณสามารถใช้กำลังแกะที่คุณรู้ว่าเป็นความจริงที่ตัวเลือกมีค่า - มีไม่ได้เป็นคนเดียวสถานที่ที่คุณไม่สามารถได้อย่างปลอดภัยแกะที่ตัวเลือกแทน
ตัวเลือก Unwrapped โดยนัย
ตัวแปรเหล่านี้ได้รับการออกแบบเพื่อให้คุณสามารถเลื่อนการมอบหมายของพวกเขาจนกระทั่งในภายหลังในรหัสของคุณ เป็นความรับผิดชอบของคุณเพื่อให้แน่ใจว่าพวกเขามีคุณค่าก่อนที่คุณจะเข้าถึงพวกเขา อย่างไรก็ตามเนื่องจากพวกเขาเกี่ยวข้องกับการบังคับให้เปิดออกพวกเขายังคงไม่ปลอดภัยโดยเนื้อแท้ - เนื่องจากพวกเขาถือว่าคุณค่าของคุณไม่ใช่ศูนย์แม้ว่าการกำหนดศูนย์จะถูกต้องก็ตาม
คุณควรใช้ตัวเลือกที่ยังไม่ได้เปิดเป็นทางเลือกสุดท้ายเท่านั้น หากคุณสามารถใช้ตัวแปรขี้เกียจหรือให้ค่าเริ่มต้นสำหรับตัวแปร - คุณควรทำเช่นนั้นแทนที่จะใช้ทางเลือกที่ไม่ได้เปิดใช้งานโดยปริยาย
อย่างไรก็ตามมีบางสถานการณ์ที่ยังไม่ได้เปิดโดยปริยาย optionals เป็นประโยชน์และคุณจะยังคงสามารถที่จะใช้วิธีการต่างๆอย่างปลอดภัย unwrapping พวกเขาตามที่ระบุไว้ดังต่อไปนี้ - แต่คุณควรเสมอใช้พวกเขาด้วยความระมัดระวังตามสมควร
ฉันจะจัดการกับ Optionals ได้อย่างปลอดภัยได้อย่างไร
nilวิธีที่ง่ายที่สุดเพื่อตรวจสอบว่าเป็นตัวเลือกที่มีค่าคือการเปรียบเทียบกับ
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
อย่างไรก็ตาม 99.9% ของเวลาที่ทำงานกับตัวเลือกคุณจะต้องการเข้าถึงค่าที่มีอยู่หากมีอย่างใดอย่างหนึ่ง เมื่อต้องการทำเช่นนี้คุณสามารถใช้การผูกหรือไม่ก็ได้
ตัวเลือกการผูก
ตัวเลือกการผูกช่วยให้คุณตรวจสอบว่าตัวเลือกมีค่า - และช่วยให้คุณสามารถกำหนดค่าที่ไม่ได้ห่อให้กับตัวแปรใหม่หรือค่าคงที่ มันใช้ไวยากรณ์if let x = anOptional {...}หรือif var x = anOptional {...}ขึ้นอยู่กับว่าคุณจำเป็นต้องปรับเปลี่ยนค่าของตัวแปรใหม่หลังจากผูกพันมัน
ตัวอย่างเช่น:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
สิ่งนี้จะตรวจสอบก่อนว่าตัวเลือกมีค่า ถ้ามันไม่ได้แล้ว 'ยังไม่ได้เปิด' ค่าถูกกำหนดให้กับตัวแปรใหม่ ( number) - ซึ่งคุณสามารถใช้อย่างอิสระราวกับว่ามันก็ไม่ใช่ตัวเลือก หากทางเลือกไม่มีค่าดังนั้นประโยคอื่นจะถูกเรียกใช้ตามที่คุณคาดหวัง
มีอะไรที่เรียบร้อยเกี่ยวกับการรวมตัวเลือกคือคุณสามารถแกะตัวเลือกหลายตัวพร้อมกันได้ คุณสามารถแยกงบด้วยเครื่องหมายจุลภาค คำสั่งจะประสบความสำเร็จหากตัวเลือกทั้งหมดไม่ได้เปิดออก
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
เคล็ดลับเรียบร้อยอีกอย่างหนึ่งก็คือคุณสามารถใช้เครื่องหมายจุลภาคเพื่อตรวจสอบเงื่อนไขบางอย่างเกี่ยวกับมูลค่าหลังจากแกะมัน
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
catch เท่านั้นที่มีการใช้การเชื่อมโยงทางเลือกภายในคำสั่ง if คือคุณสามารถเข้าถึงค่าที่ยังไม่ได้เปิดจากภายในขอบเขตของคำสั่ง หากคุณจำเป็นต้องเข้าถึงค่าจากนอกขอบเขตของคำสั่งที่คุณสามารถใช้คำสั่งยาม
งบยามช่วยให้คุณกำหนดเงื่อนไขในการประสบความสำเร็จ - และขอบเขตปัจจุบันจะยังคงดำเนินการถ้าเงื่อนไขที่จะพบ guard condition else {...}พวกเขาจะถูกกำหนดด้วยไวยากรณ์
ดังนั้นหากต้องการใช้กับการรวมตัวเลือกคุณสามารถทำได้:
guard let number = anOptionalInt else {
return
}
(โปรดทราบว่าภายในตัวป้องกันคุณต้องใช้หนึ่งในคำสั่งควบคุมการถ่ายโอนเพื่อที่จะออกจากขอบเขตของรหัสการดำเนินการในปัจจุบัน)
หากanOptionalIntมีค่ามันจะถูกปลดออกและกำหนดให้กับnumberค่าคงที่ใหม่ รหัสหลังจากที่ยามจะดำเนินการต่อ หากไม่มีค่า - การ์ดจะรันโค้ดภายในวงเล็บซึ่งจะนำไปสู่การถ่ายโอนการควบคุมเพื่อให้โค้ดในทันทีหลังจากนั้นจะไม่ถูกดำเนินการ
สิ่งที่เรียบร้อยจริงเกี่ยวกับงบยามเป็นค่าที่ยังไม่ได้เปิดอยู่ในขณะนี้ที่จะใช้ในรหัสที่เป็นไปตามคำสั่ง (ที่เรารู้ว่ารหัสในอนาคตที่สามารถเพียงรันถ้าตัวเลือกมีค่า) นี่เป็นวิธีที่ยอดเยี่ยมสำหรับการกำจัด'ปิรามิดแห่งการลงโทษ' ที่สร้างขึ้นโดยสร้างหลาย ๆ คำสั่ง
ตัวอย่างเช่น:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
ผู้คุมยังสนับสนุนกลอุบายแบบเดียวกับที่ใช้คำสั่ง if เช่นแกะตัวเลือกหลายตัวพร้อมกันและใช้whereประโยค
ไม่ว่าคุณจะใช้คำสั่ง if หรือคำสั่ง Guard อย่างสมบูรณ์นั้นขึ้นอยู่กับว่ารหัสในอนาคตใด ๆ ที่ต้องการตัวเลือกเพื่อให้มีค่า
ตัวดำเนินการรวมศูนย์
ตัวดำเนินการNil Coalescingเป็นเวอร์ชั่นย่อของผู้ประกอบการเงื่อนไขที่สามซึ่งออกแบบมาเพื่อแปลงตัวเลือกให้เป็นตัวเลือกที่ไม่ใช่ตัวเลือก มันมีไวยากรณ์a ?? bซึ่งaเป็นประเภทที่เป็นตัวเลือกและbเป็นชนิดเดียวกับa(แม้ว่ามักจะไม่ใช่ตัวเลือก)
มันช่วยให้คุณพูดว่า“ ถ้าaมีค่าให้แกะมันออกมา ถ้ามันไม่กลับมาbแทน” ตัวอย่างเช่นคุณสามารถใช้สิ่งนี้:
let number = anOptionalInt ?? 0
สิ่งนี้จะกำหนดnumberค่าคงที่ของIntประเภทซึ่งอาจมีค่าเป็นanOptionalIntถ้ามีค่าหรือ0อย่างอื่น
มันเป็นเพียงการจดชวเลขสำหรับ:
let number = anOptionalInt != nil ? anOptionalInt! : 0
ผูกมัดตัวเลือก
คุณสามารถใช้การเชื่อมต่อเสริมเพื่อเรียกวิธีหรือเข้าถึงคุณสมบัติบนตัวเลือก สิ่งนี้ทำได้ง่ายๆโดยการเติมชื่อตัวแปรด้วย a ?เมื่อใช้งาน
ตัวอย่างเช่นสมมติว่าเรามีตัวแปรfooให้พิมพ์Fooอินสแตนซ์เผื่อเลือก
var foo : Foo?
หากเราต้องการเรียกวิธีการfooที่ไม่ส่งคืนสิ่งใดเราสามารถทำได้โดยง่าย:
foo?.doSomethingInteresting()
หากfooมีค่าวิธีนี้จะถูกเรียกมัน หากไม่เป็นเช่นนั้นจะไม่มีอะไรเลวร้ายเกิดขึ้น - รหัสจะดำเนินการต่อไป
(นี่เป็นพฤติกรรมที่คล้ายกันกับการส่งข้อความไปยังnilใน Objective-C)
ดังนั้นจึงสามารถใช้เพื่อตั้งค่าคุณสมบัติเช่นเดียวกับวิธีการโทร ตัวอย่างเช่น:
foo?.bar = Bar()
อีกครั้งไม่มีอะไรเลวร้ายที่จะเกิดขึ้นที่นี่ถ้าเป็นfoo nilรหัสของคุณจะดำเนินการต่อไป
เคล็ดลับอีกอย่างที่การผูกมัดเสริมช่วยให้คุณสามารถตรวจสอบได้ว่าการตั้งค่าคุณสมบัติหรือการเรียกใช้วิธีการนั้นสำเร็จหรือไม่ nilคุณสามารถทำได้โดยการเปรียบเทียบค่าที่ส่งกลับไป
(นี่เป็นเพราะค่าที่เป็นทางเลือกจะส่งคืนVoid?แทนที่จะVoidเป็นวิธีที่ไม่ส่งคืนสิ่งใด)
ตัวอย่างเช่น:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
อย่างไรก็ตามสิ่งต่าง ๆ มีความยุ่งยากเล็กน้อยเมื่อพยายามเข้าถึงคุณสมบัติหรือวิธีการโทรที่ส่งคืนค่า เนื่องจากfooเป็นตัวเลือกสิ่งที่ส่งคืนจากนั้นจะเป็นตัวเลือก ในการจัดการกับสิ่งนี้คุณสามารถแกะตัวเลือกที่ได้รับคืนโดยใช้หนึ่งในวิธีการข้างต้น - หรือแกะfooตัวเองออกก่อนที่จะเข้าถึงวิธีการหรือวิธีการโทรที่ส่งคืนค่า
นอกจากนี้ตามชื่อที่แนะนำคุณสามารถ 'โยง' ข้อความเหล่านี้เข้าด้วยกัน ซึ่งหมายความว่าหากfooมีคุณสมบัติที่เป็นตัวเลือกbazซึ่งมีคุณสมบัติqux- คุณสามารถเขียนสิ่งต่อไปนี้:
let optionalQux = foo?.baz?.qux
อีกครั้งเนื่องจากfooและbazเป็นทางเลือกค่าที่ส่งคืนจากquxจะเป็นทางเลือกเสมอโดยไม่คำนึงว่าquxตัวเลือกนั้นเป็นตัวเลือกหรือไม่
map และ flatMap
คุณลักษณะที่มีการใช้บ่อยๆพร้อมตัวเลือกคือความสามารถในการใช้งานmapและflatMapฟังก์ชั่น สิ่งเหล่านี้ช่วยให้คุณสามารถใช้การแปลงแบบไม่บังคับกับตัวแปรเสริม หากทางเลือกมีค่าคุณสามารถใช้การแปลงที่กำหนดกับมัน nilถ้ามันไม่ได้มีค่าก็จะยังคงอยู่
ตัวอย่างเช่นสมมติว่าคุณมีสตริงตัวเลือก:
let anOptionalString:String?
โดยการใช้mapฟังก์ชั่นกับมัน - เราสามารถใช้stringByAppendingStringฟังก์ชั่นเพื่อเชื่อมต่อกับสตริงอื่น
เนื่องจากstringByAppendingStringรับอาร์กิวเมนต์สตริงที่ไม่มีตัวเลือกเราจึงไม่สามารถป้อนสตริงที่เป็นตัวเลือกของเราโดยตรง อย่างไรก็ตามโดยใช้mapเราสามารถใช้อนุญาตให้stringByAppendingStringใช้หากanOptionalStringมีค่า
ตัวอย่างเช่น:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
แต่ถ้าanOptionalStringไม่ได้มีค่าจะกลับมาmap nilตัวอย่างเช่น:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMapทำงานคล้ายกับmapยกเว้นจะอนุญาตให้คุณส่งคืนตัวเลือกอื่นจากภายในตัวปิด ซึ่งหมายความว่าคุณสามารถป้อนข้อมูลทางเลือกลงในกระบวนการที่ต้องการอินพุตที่ไม่ใช่ทางเลือก แต่สามารถส่งออกทางเลือกได้เอง
try!
ระบบการจัดการข้อผิดพลาดของ Swift สามารถใช้ได้อย่างปลอดภัยกับDo-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
หากsomeThrowingFunc()โยนข้อผิดพลาดข้อผิดพลาดจะถูกจับได้อย่างปลอดภัยในcatchบล็อก
errorคงที่คุณเห็นในcatchบล็อกยังไม่ได้รับการประกาศโดยเรา - catchมันสร้างขึ้นโดยอัตโนมัติ
นอกจากนี้คุณยังสามารถประกาศerrorตัวเองว่ามันมีความได้เปรียบในการส่งมันไปเป็นรูปแบบที่มีประโยชน์ตัวอย่างเช่น:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
การใช้tryวิธีนี้เป็นวิธีที่เหมาะสมในการลองจับและจัดการข้อผิดพลาดที่มาจากการขว้างฟังก์ชั่น
นอกจากนี้ยังtry?มีข้อผิดพลาดที่ดูดซับ:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
แต่ระบบการจัดการข้อผิดพลาดของ Swift ก็มีวิธี "บังคับลอง" ด้วยtry!:
let result = try! someThrowingFunc()
แนวคิดที่อธิบายในโพสต์นี้ใช้ที่นี่ด้วย: หากมีข้อผิดพลาดเกิดขึ้นแอปพลิเคชันจะทำงานล้มเหลว
คุณควรจะใช้ก็ต่อtry!เมื่อคุณสามารถพิสูจน์ได้ว่าผลลัพธ์ของมันจะไม่ล้มเหลวในบริบทของคุณ - และนี่เป็นสิ่งที่หายากมาก
เวลาส่วนใหญ่คุณจะใช้ระบบ Do-Try-Catch ที่สมบูรณ์และtry?ในกรณีที่ไม่ค่อยเกิดขึ้นซึ่งการจัดการข้อผิดพลาดนั้นไม่สำคัญ
ทรัพยากร