Optional

ในภาษา Swift กำหนดว่าตัวแปรทุกตัวที่ไม่ใช่ตัวแปรประเภท Optional จะไม่สามารถมีค่าเป็น nil ได้

เพื่อให้เวลาที่เราอ่านโค้ด สามารถรู้ได้ว่า เราต้อง ตรวจสอบหรือไม่ต้องตรวจสอบ nil ซึ่งจะช่วยลดการเกิด runtime error รวมทั้งลดการเขียนแบบ defensive programming ที่มากเกินไป

Optional เป็น Type ใหม่ที่มี associated type เป็น type ต่าง ๆ เช่น String Int หรืออื่น ๆ

ค่า default ของ Optional จะมีค่าเป็น nil เสมอ

ตัวแปรประเภทที่ไม่ใช่ optional จะไม่มีค่า default หรือ zero value ให้ทำให้ทุกครั้งที่ประกาศตัวแปรที่ไม่ใช่ optional จะต้องมีการ assign ค่าให้เสมอ

ในการประกาศให้ตัวแปรเป็น Optional ให้เราประกาศเหมือนตัวแปรปกติ แต่ที่ Type เราจะใส่ ? หรือ ! ลงท้าย เช่น

var payment: PaymentType?
var textField: UITextField!

ในการถอดค่าเพื่อเอา value ใน optional มาใช้ในรูปแบบปกติ เช่น การถอดจาก String? เป็น String หรือ String! เป็น String เรียกว่า unwraping ซึ่งวิธีการจะมีสิ่งที่เรียกว่า Optional Binding

ซึ่งก็คือ การทดสอบว่า nil ไหมนั่นเอง จะมีหน้าตาประมาณนี้

let amount = "34"
if let total = Int(amount) {
    print("Total items: \(total)")
} else {
    print("Invalid total items")
}

ถ้าจากตัวอย่างจะเห็นว่า ในการแปลงค่าจาก String เป็น Int สามารถที่จะแปลงกลายเป็น nil ได้เพราะว่า แปลงไม่สำเร็จ เช่น amount เป็น "abc" แทนที่จะเป็นตัวเลข

ทำให้ Int(amount) คืนผลลัพธ์ที่เป็น Int? นั่นคืออาจเป็น nil ได้

การที่เราจะทำให้เป็น Int ได้ เราจึงต้องใช้ if let มาช่วย จากตัวอย่างนั่นคือ ถ้า Int(amount) ให้ผลลัพธ์ที่ไม่ใช่ nil เราจะได้ตัวแปรใหม่ชื่อว่า total มี Type เป็น Int และใช้ค่าได้ภายใต้ scope ของ if นี้

ในกรณีที่ Int(amount) ให้ผลลัพธ์ nil โค้ดจะรันส่วน else แทน

ในบางครั้งเราสามารถถอด optional โดยกำหนดค่า default แทนได้ เช่น เราต้องการให้ค่าเป็น 0 กรณีที่เราไม่สามารถถอดค่าได้

let amount = Int(amountString) ?? 0

? กับ ! ต่างกันอย่างไร

ทั้งคู่ล้วนเป็น optional เหมือนกัน ต่างกันตรงที่การใช้ ! หมายความว่า ณ เวลาที่ใช้ผู้เขียนแน่ใจว่าค่าจะไม่เป็น nil นั่นคือ เมื่อเราประกาศแบบ ! เราจะสามารถเขียนโค้ดเหมือนตัวแปรแบบปกติ คือ เราสามารถ assign ให้โดยไม่ต้องทำ unwrap ได้ แบบนี้

var total = 0
let amount: Int! = 5
total = total + amount

นั่นคือ เราสามารถเขียน total + amount ได้เลยโดยที่ไม่ต้องทำแบบนี้

var total = 0
let amount: Int? = 5

if let amount = amount {
    total = total + amount
}

การใช้ ! ต้องแน่ใจว่าเวลาที่ใช้ค่าตัวแปรนั้น จะต้องไม่มีค่าเป็น nil เพราะถ้ามีค่าเป็น nil การใช้ค่าโดยไม่ unwrap ก่อนจะทำให้เกิด runtime error และ application ของเราก็จะ crash

โดยปกติเราจะไม่ใช้ ! ยกเว้น เช่นตัวแปรที่ใช้เชื่อมกับ view ซึ่งเรามั่นใจว่า view นั้นจะไม่ใช้ nil แน่นอนในเวลาที่รัน

ในบางครั้งจุดที่เราต้องการให้มีค่าจริง ๆ ถ้าไม่มีให้ crash ก็ช่วยในการ detect bug ได้ แต่นั่นเราต้องมั่นใจว่าเราเทสได้ครอบคลุม

นอกจาก Optional Binding แล้ว เรายังมี optional chaining นั่นคือในบางครั้งเราก็ไม่จำเป็นต้องถอดจนครบทีละตัว แต่สนใจที่ผลลัพธ์สุดท้าย ให้เราใส่ ? หรือ ! ไปตามจุดต่าง ๆ ใน statement เรา เช่น

if let roundedNumber = Double("123.45")?.rounded() {
}

ตัวอย่างนี้ จะเห็นว่าเราไม่ได้ unwrap Double? ก่อนแล้วค่อย rounded() ใน if ในกรณีนี้เราใส่ ? หลัง Double("123.45") แล้ว rounded() การทำแบบนี้ optional chaining ซึ่งการใส่ ? จะยังทำให้ผลลัพธ์ทั้งหมดของ statement นี้ยังเป็น Double? เราจึงยัง unwrap ด้วย optional binding ต่อด้วย if let

ถ้า Double("123.45") ให้ผลลัพธ์เป็น nil ฟังก์ชันrounded() จะไม่ถูกเรียก และให้ผลลัพธ์เป็น nil เลย

optional chaining ก็สามารถใช้ ! ได้ เรียกว่า force unwrap แต่ให้ผลลัพธ์เป็นตัวแปรชนิดที่ไม่ใช่ optional นั่นคือ เมื่อเรียกบนตัวแปรที่เป็น nil จะทำให้ application crash ได้

สามารถอ่านเพิ่มเติมได้ที่

https://developer.apple.com/documentation/swift/optional

Last updated