คำตอบนี้เป็นวิกิพีเดียชุมชน หากคุณรู้สึกว่ามันจะดีขึ้นอย่าลังเลที่จะแก้ไขมัน !
พื้นหลัง: อะไรคือตัวเลือก
ใน 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?
ในกรณีที่ไม่ค่อยเกิดขึ้นซึ่งการจัดการข้อผิดพลาดนั้นไม่สำคัญ
ทรัพยากร