“ ข้อผิดพลาดร้ายแรง: อะไรพบอย่างไม่คาดคิดขณะที่คลายค่าตัวเลือกเพิ่มเติม” หมายความว่าอะไร


415

โปรแกรม Swift ของฉันหยุดทำงานEXC_BAD_INSTRUCTIONและหนึ่งในข้อผิดพลาดที่คล้ายกันดังต่อไปนี้ ข้อผิดพลาดนี้หมายถึงอะไรและฉันจะแก้ไขได้อย่างไร

ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่แกะค่าที่ไม่จำเป็นออก

หรือ

ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่คลายค่าทางเลือกโดยปริยาย


โพสต์นี้มีวัตถุประสงค์เพื่อรวบรวมคำตอบสำหรับปัญหา "พบโดยไม่คาดคิด" เพื่อให้พวกเขาไม่กระจัดกระจายและหายาก อย่าลังเลที่จะเพิ่มคำตอบของคุณเองหรือแก้ไขคำตอบ wiki ที่มีอยู่


8
@RobertColumbia นี่เป็นคู่ถาม - ตอบ Wiki ชุมชนที่มีเอกสารครอบคลุมของปัญหาฉันไม่คิดว่าคำถามนี้ควรถูกปิดเพราะขาด MCVE
JAL

สร้างตัวแปรเช่นนี้ [var nameOfDaughter: String? ] และใช้ตัวแปรเช่นนี้ [ถ้าให้ _ = nameOfDaughter {}] เป็นวิธีที่ดีที่สุด
iOS

คำตอบ:


673

คำตอบนี้เป็นวิกิพีเดียชุมชน หากคุณรู้สึกว่ามันจะดีขึ้นอย่าลังเลที่จะแก้ไขมัน !

พื้นหลัง: อะไรคือตัวเลือก

ใน 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?ในกรณีที่ไม่ค่อยเกิดขึ้นซึ่งการจัดการข้อผิดพลาดนั้นไม่สำคัญ


ทรัพยากร


87
โดยส่วนตัวฉันขอขอบคุณที่พยายามเขียนทั้งหมดนี้ ฉันรู้สึกว่าสิ่งนี้จะเป็นประโยชน์สำหรับผู้เริ่มต้นและผู้เชี่ยวชาญอย่างรวดเร็วโดยใช้
Pranav Wadhwa

15
ฉันดีใจที่คุณพบว่ามีประโยชน์ คำตอบนี้เป็นวิกิชุมชนจึงเป็นความร่วมมือระหว่างคนจำนวนมาก (7 จนถึงตอนนี้)!
jtbandes

1
ข้อผิดพลาดนี้ไม่สม่ำเสมอในกรณีของฉัน ข้อเสนอแนะใด ๆ โค้ดในstackoverflow.com/questions/50933681/…
py_ios_dev

1
บางทีมันถึงเวลาที่จะปรับปรุงคำตอบนี้เพื่อใช้แทนcompactMap() flatMap()
Nicolas Miari

ดังนั้นฉันเดาว่าทางออกที่ดีที่สุดและสั้นที่สุดคือ "Nil Coalescing Operator" ใช่ไหม?
mehdigriche

65

TL; DR คำตอบ

ด้วยข้อยกเว้นน้อยมากกฎนี้มีสีทอง:

หลีกเลี่ยงการใช้ !

ประกาศตัวแปรที่เป็นตัวเลือก ( ?) ไม่ใช่ตัวเลือกที่ไม่ได้เปิดใช้งานโดยนัย (IUO) ( !)

กล่าวอีกนัยหนึ่งค่อนข้างใช้:
var nameOfDaughter: String?

แทน:
var nameOfDaughter: String!

แกะตัวแปรเสริมโดยใช้if letหรือguard let

ตัวแปรแกะเช่นนี้:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

หรือเช่นนี้

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

คำตอบนี้มีวัตถุประสงค์เพื่อรัดกุมเพื่อความเข้าใจที่สมบูรณ์อ่านคำตอบที่ได้รับการยอมรับ


ทรัพยากร


40

คำถามนี้เกิดขึ้นตลอดเวลาดังนั้น นี่เป็นหนึ่งในสิ่งแรกที่นักพัฒนา Swift ใหม่ต้องเผชิญ

พื้นหลัง:

Swift ใช้แนวคิดของ "Optionals" เพื่อจัดการกับค่าที่อาจมีค่าหรือไม่ ในภาษาอื่นเช่น C คุณอาจเก็บค่า 0 ไว้ในตัวแปรเพื่อระบุว่าไม่มีค่า อย่างไรก็ตามจะเกิดอะไรขึ้นถ้า 0 เป็นค่าที่ถูกต้อง จากนั้นคุณอาจใช้ -1 ถ้า -1 เป็นค่าที่ถูกต้อง และอื่น ๆ

ตัวเลือก Swift ให้คุณตั้งค่าตัวแปรประเภทใดก็ได้เพื่อให้มีค่าที่ถูกต้องหรือไม่มีค่า

คุณใส่เครื่องหมายคำถามหลังประเภทเมื่อคุณประกาศตัวแปรให้เป็นค่าเฉลี่ย (ประเภท x หรือไม่มีค่า)

ตัวเลือกเป็นจริงภาชนะกว่าประกอบด้วยตัวแปรประเภทที่กำหนดหรือไม่มีอะไร

จำเป็นต้องมีตัวเลือก "ไม่ได้ห่อ" เพื่อดึงค่าภายใน

"!" โอเปอเรเตอร์คือโอเปอเรเตอร์ "force unwrap" มันบอกว่า "เชื่อฉันเถอะฉันรู้ว่าฉันกำลังทำอะไรอยู่ฉันรับประกันได้ว่าเมื่อโค้ดนี้ทำงานตัวแปรจะไม่มี nil" หากคุณผิดคุณผิดพลาด

หากคุณไม่ทราบว่ากำลังทำอะไรอยู่ให้หลีกเลี่ยง "!" บังคับให้ผู้ประกอบการแกะ อาจเป็นแหล่งที่มาของการล่มที่ใหญ่ที่สุดสำหรับการเริ่มต้นโปรแกรมเมอร์ Swift

วิธีจัดการกับทางเลือก:

มีวิธีอื่นอีกมากมายในการจัดการกับตัวเลือกที่ปลอดภัยกว่า นี่คือบางส่วน (ไม่ใช่รายการที่ครบถ้วนสมบูรณ์)

คุณสามารถใช้ "การรวมหรือไม่ก็ได้" หรือ "ถ้าให้" เพื่อบอกว่า "ถ้าตัวเลือกนี้มีค่าให้บันทึกค่านั้นลงในตัวแปรใหม่ที่ไม่บังคับเลือกถ้าตัวเลือกไม่มีค่าให้ข้ามเนื้อหาของคำสั่ง if "

นี่คือตัวอย่างของการผูกหรือไม่ก็ได้กับfooตัวเลือกของเรา:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

โปรดทราบว่าตัวแปรที่คุณกำหนดเมื่อคุณใช้การเลือกเสริมจะมีอยู่เท่านั้น (คือ "อยู่ในขอบเขต" เท่านั้น) ในเนื้อความของคำสั่ง if

อีกทางหนึ่งคุณสามารถใช้คำสั่งป้องกันซึ่งช่วยให้คุณออกจากฟังก์ชั่นของคุณถ้าตัวแปรเป็นศูนย์:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

เพิ่มคำสั่ง Guard ใน Swift 2 Guard ช่วยให้คุณรักษา "เส้นทางทอง" ผ่านโค้ดของคุณและหลีกเลี่ยงการเพิ่มระดับของ ifs ที่ซ้อนกันซึ่งบางครั้งเป็นผลมาจากการใช้ "ถ้าให้" การผูกหรือไม่ก็ได้

นอกจากนี้ยังมีโครงสร้างที่เรียกว่า "ตัวดำเนินการรวมศูนย์" มันใช้รูปแบบ "optional_var ?? replace_val" มันจะส่งกลับตัวแปรที่ไม่จำเป็นมีชนิดเดียวกันกับข้อมูลที่มีอยู่ในตัวเลือก ถ้าทางเลือกประกอบด้วย nil มันจะส่งกลับค่าของการแสดงออกหลังจาก "??" สัญลักษณ์.

ดังนั้นคุณสามารถใช้รหัสดังนี้:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

คุณยังสามารถใช้การลอง / จับหรือป้องกันการจัดการข้อผิดพลาดได้ แต่โดยทั่วไปหนึ่งในเทคนิคอื่น ๆ ข้างต้นนั้นสะอาดกว่า

แก้ไข:

อีกหนึ่ง gotcha ที่ลึกซึ้งยิ่งขึ้นเล็กน้อยที่มีตัวเลือกคือ "ตัวเลือกที่ยังไม่ได้เปิดโดยปริยายเมื่อเราประกาศ foo เราสามารถพูดได้:

var foo: String!

ในกรณีนั้น foo ยังคงเป็นทางเลือก แต่คุณไม่จำเป็นต้องแกะมันเพื่ออ้างอิง ซึ่งหมายความว่าเมื่อใดก็ตามที่คุณพยายามอ้างอิง foo คุณจะผิดพลาดหากไม่มี

ดังนั้นรหัสนี้:

var foo: String!


let upperFoo = foo.capitalizedString

จะผิดพลาดในการอ้างอิงถึงทรัพย์สินที่เป็นตัวแสบของ foo แม้ว่าเราจะไม่ได้ฝืนบังคับก็ตาม พิมพ์ดูดี แต่ก็ไม่ได้

ดังนั้นคุณต้องระวังอย่างมากกับตัวเลือกที่ไม่ได้เปิดใช้งานโดยปริยาย (และอาจหลีกเลี่ยงอย่างสมบูรณ์จนกว่าคุณจะมีความเข้าใจในตัวเลือก)

Bottom line: เมื่อคุณเรียนรู้ Swift เป็นครั้งแรกให้ทำเป็น "!" อักขระไม่ได้เป็นส่วนหนึ่งของภาษา มีโอกาสที่จะทำให้คุณเดือดร้อน


7
คุณควรพิจารณาทำให้เป็นวิกิสาธารณะ
vacawama

3
แก้ไขคำตอบของคุณและใต้กล่องแก้ไขทางด้านขวาคือช่องทำเครื่องหมายวิกิชุมชน คุณจะไม่ได้รับตัวแทนอีกต่อไป แต่ฉันรู้ว่านี่ไม่ใช่ตัวแทนที่โจ่งแจ้ง
vacawama

6
ระบุว่าคำถามนี้ถูกถามเป็นล้านครั้งบนเว็บไซต์หากฉันจะใช้เวลาในการโพสต์คำถามที่ตอบด้วยตนเองเป็นคำตอบที่ยอมรับได้สำหรับคำถามนี้ฉันหวังว่าคำตอบจะสมบูรณ์กว่านี้ ไม่มีอะไรเกี่ยวกับguardข้อ ไม่มีอะไรที่เกี่ยวกับการใช้เป็นซึ่งเป็นเช่นเดียวกับการสร้างที่ถูกต้องเป็นif var if letไม่มีอะไรเกี่ยวกับwhereอนุประโยคที่ฉันรู้สึกว่าควรพูดถึงเมื่อเรากำลังพูดถึงการif letเชื่อมโยง ไม่มีอะไรเกี่ยวกับการผูกมัดเสริม
nhgrif

6
และเมื่อคุณพูดถึงตัวเลือกที่ยังไม่ได้เปิดโดยปริยายคุณไม่ได้พูดถึงว่าคุณสามารถใช้เทคนิคทางเลือกดังกล่าวทั้งหมด นอกจากนี้เรายังไม่ครอบคลุมการยกเลิกการเลือกหลายตัวเลือกในข้อเดียวกัน
nhgrif

1
@ nhgrif ฉันพูดว่า "คุณสามารถใช้ลอง / จับหรือป้องกันข้อผิดพลาดในการจัดการได้ แต่โดยทั่วไปแล้วหนึ่งในเทคนิคอื่น ๆ ข้างต้นนั้นสะอาดกว่า" คุณมีคะแนนที่ดีอย่างแน่นอน นี่เป็นหัวข้อที่ค่อนข้างใหญ่ วิธีการเกี่ยวกับการให้คำตอบของคุณเองซึ่งครอบคลุมสิ่งต่าง ๆ ที่ฉันไม่ได้ทำแทนที่จะแสดงความคิดเห็น? วิธีนี้ทำให้คำถามและคำตอบกลายเป็นสินทรัพย์ที่ดีกว่าสำหรับเว็บไซต์
Duncan C

13

เนื่องจากคำตอบข้างต้นอธิบายวิธีการเล่นอย่างปลอดภัยด้วย Optionals ฉันจะลองอธิบายว่า Optionals เร็วแค่ไหน

อีกวิธีในการประกาศตัวแปรทางเลือกคือ

var i : Optional<Int>

และตัวเลือกประเภทคืออะไรนอกจากการแจงนับที่มีสองกรณีคือ

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

ดังนั้นในการกำหนดศูนย์ให้ตัวแปร 'i' ของเรา เราสามารถทำ var i = Optional<Int>.none หรือกำหนดค่าเราจะผ่านค่าบางอย่าง var i = Optional<Int>.some(28)

ตามที่รวดเร็ว 'ไม่มี' คือการขาดมูลค่า และในการสร้างอินสแตนซ์ที่เริ่มต้นด้วยnilเราจะต้องปฏิบัติตามโปรโตคอลที่เรียกว่าExpressibleByNilLiteralดีเยี่ยมหากคุณเดาได้ว่าจะไม่แนะนำให้OptionalsทำตามExpressibleByNilLiteralและสอดคล้องกับประเภทอื่นเท่านั้น

ExpressibleByNilLiteralมีวิธีการเดียวที่เรียกว่าinit(nilLiteral:)เริ่มต้น instace กับศูนย์ คุณมักจะไม่เรียกวิธีนี้และตามเอกสาร swift มันเป็นกำลังใจที่จะเรียก initializer นี้โดยตรงในขณะที่คอมไพเลอร์เรียกมันว่าเมื่อใดก็ตามที่คุณเริ่มต้นประเภทตัวเลือกที่มีnilตัวอักษร

แม้ตัวเองจะต้องห่อ (ไม่มีเล่นสำนวนเจตนา) หัวของฉันรอบ optionals: D มีความสุข Swfting ทั้งหมด


12

ก่อนอื่นคุณควรทราบว่าค่าตัวเลือกคืออะไร คุณสามารถไปที่The Swift Programming Languageเพื่อดูรายละเอียด

ประการที่สองคุณควรรู้ว่าค่าทางเลือกมีสองสถานะ หนึ่งคือค่าเต็มและอื่น ๆ เป็นค่าศูนย์ ดังนั้นก่อนที่คุณจะใช้ค่าที่เป็นทางเลือกคุณควรตรวจสอบสถานะของมัน

คุณสามารถใช้if let ...หรือguard let ... elseอื่น ๆ

อีกวิธีหนึ่งถ้าคุณไม่ต้องการตรวจสอบสถานะของตัวแปรก่อนการใช้งานคุณสามารถใช้var buildingName = buildingName ?? "buildingName"แทนได้


8

ฉันมีข้อผิดพลาดนี้หนึ่งครั้งเมื่อฉันพยายามตั้งค่า Outlets ของฉันจากการเตรียมการสำหรับวิธีแยกย่อยดังนี้:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // This line pops up the error
            destination.nameLabel.text = item.name
        }
    }
}

จากนั้นฉันก็พบว่าฉันไม่สามารถตั้งค่าของช่องเสียบปลายทางได้เนื่องจากคอนโทรลเลอร์ยังไม่ได้โหลดหรือเริ่มต้นใช้งาน

ดังนั้นฉันจึงแก้ไขด้วยวิธีนี้:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // Created this method in the destination Controller to update its outlets after it's being initialized and loaded
            destination.updateView(itemData:  item)
        }
    }
}

ปลายทางควบคุม:

// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""

// Outlets
@IBOutlet weak var nameLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    nameLabel.text = name
}

func updateView(itemDate: ObjectModel) {
    name = itemDate.name
}

ฉันหวังว่าคำตอบนี้จะช่วยให้ทุกคนที่มีปัญหาเดียวกันกับที่ฉันพบคำตอบที่ทำเครื่องหมายไว้เป็นทรัพยากรที่ดีในการทำความเข้าใจกับตัวเลือกและวิธีการทำงาน แต่ไม่ได้แก้ไขปัญหาโดยตรง


เพียงอย่าลืมที่จะพูดถึงสถานที่ที่จะโทรupdateViewในตัวควบคุมปลายทาง;)
Ahmad F

@ AhmadF จุดดี แต่การเรียกupdateViewในการควบคุมปลายทางไม่จำเป็นต้องใช้ในกรณีนี้เป็นฉันใช้nameตัวแปรที่จะตั้งในnameLabel.text viewDidLoadแต่ถ้าเราทำการเซ็ตอัพจำนวนมากมันจะเป็นการสร้างฟังก์ชั่นอื่นที่ดีกว่าและเรียกมันจากที่อื่นviewDidLoadแทน
Wissa

7

โดยทั่วไปคุณพยายามใช้ค่า nil ในสถานที่ที่ Swift อนุญาตเฉพาะค่าที่ไม่เป็นศูนย์โดยบอกให้คอมไพเลอร์เชื่อใจคุณว่าจะไม่มีค่า nil ที่นั่นทำให้แอปของคุณสามารถคอมไพล์ได้

มีหลายสถานการณ์ที่ทำให้เกิดข้อผิดพลาดร้ายแรงประเภทนี้:

  1. บังคับห่อ

    let user = someVariable!

    ถ้าsomeVariableไม่มีแล้วคุณจะได้รับความผิดพลาด ด้วยการบังคับแกะคุณย้ายความรับผิดชอบในการตรวจสอบศูนย์จากคอมไพเลอร์มาถึงคุณโดยการบังคับแกะให้บังคับคุณรับรองกับคอมไพเลอร์ว่าคุณจะไม่มีคุณค่าใด ๆ ที่นั่น และคาดเดาสิ่งที่มันเกิดขึ้นถ้าใดไม่มีค่าในปลายในsomeVariable?

    สารละลาย? ใช้การรวมหรือไม่ก็ได้ (aka if-let) ทำการประมวลผลตัวแปรที่นั่น:

    if user = someVariable {
        // do your stuff
    }
  2. บังคับ (ลง) ปลดเปลื้อง:

    let myRectangle = someShape as! Rectangle

    โดยการบังคับส่งคุณบอกคอมไพเลอร์ว่าไม่ต้องกังวลอีกต่อไปเนื่องจากคุณจะมีRectangleตัวอย่างอยู่เสมอ และตราบใดที่คุณไม่ต้องกังวล ปัญหาเริ่มต้นเมื่อคุณหรือเพื่อนร่วมงานของคุณจากโครงการเริ่มหมุนเวียนค่าที่ไม่ใช่รูปสี่เหลี่ยมผืนผ้า

    สารละลาย? ใช้การรวมหรือไม่ก็ได้ (aka if-let) ทำการประมวลผลตัวแปรที่นั่น:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. ตัวเลือกที่ไม่ได้เปิดใช้โดยนัย สมมติว่าคุณมีคำจำกัดความของคลาสต่อไปนี้:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }

    ตอนนี้ถ้าไม่มีใครยุ่งกับnameคุณสมบัติโดยการตั้งค่าให้nilมันทำงานตามที่คาดไว้อย่างไรก็ตามถ้าUserเริ่มต้นจาก JSON ที่ขาดnameคีย์แล้วคุณจะได้รับข้อผิดพลาดร้ายแรงเมื่อพยายามใช้คุณสมบัติ

    สารละลาย? อย่าใช้พวกเขา :) เว้นแต่ว่าคุณจะแน่ใจว่า 102% คุณสมบัติจะมีค่าที่ไม่ใช่ศูนย์ตามเวลาที่จะต้องใช้ ในกรณีส่วนใหญ่การแปลงเป็นตัวเลือกหรือไม่ก็ได้จะใช้งานได้ การทำให้มันไม่เป็นตัวเลือกจะส่งผลให้คอมไพเลอร์ช่วยคุณโดยบอกเส้นทางของโค้ดที่คุณพลาดให้ค่าแก่คุณสมบัติ

  4. ร้านที่ไม่ได้เชื่อมต่อหรือยังไม่ได้เชื่อมต่อ นี่เป็นกรณีเฉพาะของสถานการณ์ # 3 โดยทั่วไปคุณมีคลาสที่โหลด XIB บางส่วนที่คุณต้องการใช้

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }

    ตอนนี้ถ้าคุณพลาดการเชื่อมต่อเต้าเสียบจากเครื่องมือแก้ไข XIB ดังนั้นแอปจะหยุดทำงานทันทีที่คุณต้องการใช้เต้าเสียบ สารละลาย? ตรวจสอบให้แน่ใจว่าเชื่อมต่อทุกช่อง หรือใช้?โอเปอเรเตอร์กับพวกเขา: emailTextField?.text = "my@email.com". หรือประกาศเต้าเสียบเป็นตัวเลือกถึงแม้ว่าในกรณีนี้คอมไพเลอร์จะบังคับให้คุณแกะรหัสทั้งหมด

  5. ค่าที่มาจาก Objective-C และนั่นไม่มีคำอธิบายประกอบที่ไม่มีความสามารถ สมมติว่าเรามีคลาส Objective-C ต่อไปนี้:

    @interface MyUser: NSObject
    @property NSString *name;
    @end

    ตอนนี้หากไม่มีการระบุคำอธิบายประกอบความสามารถ null ได้ (อย่างชัดเจนหรือผ่านNS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END) แล้วnameทรัพย์สินจะถูกนำเข้าใน Swift เป็นString!(IUO - โดยปริยายไม่จำเป็นเลือก) ทันทีที่โค้ด swift บางตัวต้องการใช้ค่ามันจะผิดพลาดหากnameไม่มี

    สารละลาย? เพิ่มคำอธิบายประกอบ nullability ไปยังรหัส Objective-C ของคุณ ระวัง แต่คอมไพเลอร์ Objective-C คืออนุญาตนิด ๆ หน่อย ๆ เมื่อมันมาถึง nullability nonnullคุณอาจจบลงด้วยค่าศูนย์แม้ว่าคุณจะระบุอย่างชัดเจนว่าพวกเขาเป็น


2

นี่เป็นความคิดเห็นที่สำคัญมากกว่าและทำไมตัวเลือกที่ไม่ได้เปิดใช้งานโดยนัยสามารถหลอกลวงได้เมื่อมันมาถึงnilค่าการดีบัก

คิดว่ารหัสต่อไปนี้: มันรวบรวมโดยไม่มีข้อผิดพลาด / คำเตือน:

c1.address.city = c3.address.city

แต่ ณ รันไทม์มันให้ข้อผิดพลาดต่อไปนี้: ข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่แกะค่าตัวเลือก

คุณบอกฉันได้ไหมว่าวัตถุnilอะไร?

คุณทำไม่ได้!

รหัสเต็มจะเป็น:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c3 = BadContact()

        c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct BadContact {
    var address : Address!
}

struct Address {
    var city : String
}

เรื่องสั้นโดยใช้var address : Address!คุณซ่อนความเป็นไปได้ที่ตัวแปรอาจมาnilจากผู้อ่านรายอื่น และเมื่อมันเกิดปัญหาคุณจะต้องเป็น "อะไรกันเนี่ย?! ฉันaddressไม่ใช่ตัวเลือกดังนั้นทำไมฉันถึงต้องล้มเหลว"!

ดังนั้นจึงเป็นการดีกว่าที่จะเขียนเช่น:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

ตอนนี้คุณช่วยบอกฉันได้ไหมว่ามันคือnilอะไร?

เวลานี้รหัสได้ชัดเจนยิ่งขึ้นสำหรับคุณ คุณสามารถหาเหตุผลเข้าข้างตนเองและคิดว่าเป็นไปได้ว่ามันเป็นaddressพารามิเตอร์ที่ไม่ได้เปิดใช้งานแรง

รหัสเต็มจะเป็น:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c2 = GoodContact()

        c1.address.city = c2.address!.city
        c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
        c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
        if let city = c2.address?.city {  // safest approach. But that's not what I'm talking about here. 
            c1.address.city = city
        }

    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct GoodContact {
    var address : Address?
}

struct Address {
    var city : String
}

2

ข้อผิดพลาดEXC_BAD_INSTRUCTIONและfatal error: unexpectedly found nil while implicitly unwrapping an Optional valueปรากฏมากที่สุดเมื่อคุณได้ประกาศ@IBOutletแต่ไม่ได้เชื่อมต่อกับสตอรี่บอร์ด

คุณควรเรียนรู้เกี่ยวกับวิธีการทำงานของOptionals ที่กล่าวถึงในคำตอบอื่น ๆ แต่นี่เป็นเพียงครั้งเดียวที่ฉันจะได้เห็น


@IBOutletสาเหตุของข้อผิดพลาดนี้ไม่ควรมีข้อผิดพลาดร้ายแรง: พบศูนย์โดยไม่คาดคิดในขณะที่เปิดรุ่นข้อผิดพลาดทางเลือกค่าโดยนัยหรือไม่
pkamb

1
นั่นเป็นเรื่องจริง บางทีเมื่อฉันส่งคำตอบนั่นคือสิ่งที่ฉันหมายถึงและฉันคัดลอกวางข้อผิดพลาดร้ายแรงครั้งแรก คำตอบจาก Hamish ดูสมบูรณ์มากเกี่ยวกับเรื่องนี้
Ale Mohamad

2

หากคุณได้รับข้อผิดพลาดใน CollectionView ให้ลองสร้างไฟล์ CustomCell และ Custom xib ด้วย

เพิ่มรหัสนี้ใน ViewDidLoad () ที่ mainVC

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

0

ฉันเจอข้อผิดพลาดนี้ในขณะที่สร้างส่วนเสริมจากตัวควบคุมมุมมองตารางเป็นตัวควบคุมมุมมองเนื่องจากฉันลืมระบุชื่อคลาสแบบกำหนดเองสำหรับตัวควบคุมมุมมองในกระดานเรื่องราวหลัก

สิ่งง่าย ๆ ที่ควรค่าแก่การตรวจสอบถ้าทุกอย่างดูโอเค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.