ค่าทางเลือกใน Swift คืออะไร


267

จากเอกสารของ Apple :

คุณสามารถใช้ifและletร่วมกันทำงานกับค่าที่อาจหายไป ค่าเหล่านี้แสดงเป็นตัวเลือก ค่าที่เป็นทางเลือกอาจมีค่าหรือมีnilเพื่อระบุว่าค่านั้นหายไป เขียนเครื่องหมายคำถาม ( ?) หลังประเภทของค่าเพื่อทำเครื่องหมายค่าเป็นตัวเลือก

ทำไมคุณต้องการใช้ค่าที่เป็นทางเลือก


ที่เกี่ยวข้อง: stackoverflow.com/questions/24034483/…
Bigood

1
ตัวเลือกยังสามารถมองได้ว่าการดำเนินการของตัวเลือก / อาจจะ monad นี้บล็อกที่นี่ไม่ได้งานที่ดีของการพยายามที่จะอธิบายสิ่งที่เป็นมิฉะนั้นแนวคิดที่ยาก
StuartLC

tldr:“ Swift ต้องการให้คุณชัดเจนเกี่ยวกับเวลาที่ค่าอาจหายไปและเมื่อรับประกันว่ามีอยู่” จากคำตอบที่ยอดเยี่ยม
jonatan

คำตอบ:


531

ตัวเลือกใน 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ก็หรืออย่างแม่นยำมากขึ้นOptionalOptional<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)")

ทางเลือกคืออะไร

ตัวเลือกมีสองกรณีการใช้งาน:

  1. สิ่งที่สามารถล้มเหลว (ฉันคาดหวังบางอย่าง แต่ฉันไม่มีอะไร)
  2. สิ่งที่ไม่ได้เป็นอยู่ในตอนนี้ แต่อาจเป็นบางสิ่งในภายหลัง (และในทางกลับกัน)

ตัวอย่างที่เป็นรูปธรรม:

  • คุณสมบัติที่สามารถมีหรือไม่มีเช่น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")

Optionalinitializer แรกของการโทรนี้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) ตัวเลือกจะถูกสร้างขึ้นด้วยOptionalinitializer ที่สองของ, 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 เกี่ยวกับตัวเลือกมีดังนี้:

เมื่อวานนี้บนบันได
ฉันได้พบกับชายคนหนึ่งที่ไม่ได้อยู่ที่นั่น
เขาไม่ได้อยู่ที่นั่นอีกในวันนี้
ฉันต้องการฉันหวังว่าเขาจะจากไปแอน

ติโกนิช


แหล่งข้อมูลเพิ่มเติม:


5
@ KaanDedeoglu น่าเสียดายที่ Steve เป็นทางเลือกเป็นอย่างมาก เขาอยู่ที่นี่และตอนนี้เขาไม่ได้
nevan king

19
if myStringไม่ได้รวบรวมอีกต่อไป if myString != nilคุณจำเป็นต้อง โปรดดูเอกสาร
ปัง

5
คำอธิบายที่ดีที่สุดและชัดเจนที่สุดสำหรับ? และ! ใช้ใน Swift ที่ฉันพบบนเว็บ ขอบคุณ
mindbomb

2
mateoอธิบายในเชิงลึกตัวเลือกถึงในตัวอย่างความลึกและง่าย
iluvatar_GR

4
ขอบคุณสำหรับคำอธิบายนี้ชัดเจนกว่าเอกสารของ Apple
yesthisisjoe

16

ลองมาตัวอย่างของNSErrorถ้าไม่มีข้อผิดพลาดการส่งคืนคุณต้องการให้มันเป็นตัวเลือกที่จะกลับ Nil ไม่มีจุดในการกำหนดค่าให้กับมันหากไม่มีข้อผิดพลาด ..

var error: NSError? = nil

นอกจากนี้ยังช่วยให้คุณมีค่าเริ่มต้น ดังนั้นคุณสามารถกำหนดวิธีการเป็นค่าเริ่มต้นหากฟังก์ชั่นไม่ได้ผ่านอะไร

func doesntEnterNumber(x: Int? = 5) -> Bool {
    if (x == 5){
        return true
    } else {
        return false
    }
}

ประโยค "ถ้าเป็นศูนย์ผลของการแสดงออกใด ๆ ก็เป็นศูนย์เช่นกัน" ก็ผิด func isNil<T>(t: T?) -> Bool { return t == nil }จะส่งคืนtrueแม้ว่าจะมีค่าเผื่อเลือกซึ่งอยู่nilในนิพจน์
ส่งคืนจริง

ตัวอย่างรหัสไม่ดีอย่างไม่น่าเชื่อ คุณนึกถึงบางสิ่งที่ดีกว่าไม่ได้เหรอ? ทำไมไม่ง่าย ๆreturn x == 5? อะไรที่พิเศษประมาณ 5?
Atomosk

ไม่ 2 ปีที่แล้วฉันนึกอะไรไม่ออกเลย วันนี้ใช่ แต่สิ่งนี้ได้รับการข้าม? ใช่. ขอบคุณสำหรับการป้อนข้อมูล @Atomosk มันมีประโยชน์จริงๆ
John Riselvato

12

คุณไม่สามารถมีตัวแปรที่ชี้ไปที่nilSwift - ไม่มีพอยน์เตอร์และไม่มีพอยน์เตอร์พอยน์เตอร์ แต่ใน API คุณมักต้องการระบุค่าเฉพาะชนิดหรือการขาดค่าเช่นหน้าต่างของฉันมีผู้รับมอบสิทธิ์และถ้าเป็นเช่นนั้นใครเป็นใคร ตัวเลือกเป็นวิธีที่ปลอดภัยและปลอดภัยสำหรับหน่วยความจำประเภท Swift


11

ฉันทำคำตอบสั้น ๆ ซึ่งสรุปมาจากข้างต้นเพื่อทำความสะอาดความไม่แน่นอนที่อยู่ในหัวของฉันในฐานะผู้เริ่มต้น:

ตรงข้ามกับ Objective-C ไม่มีตัวแปรใดสามารถมีศูนย์ใน Swift ดังนั้นจึงเพิ่มประเภทตัวแปรเสริม (ตัวแปรที่ต่อท้ายด้วย "?"):

    var aString = nil //error

ความแตกต่างใหญ่คือตัวแปรทางเลือกไม่ได้เก็บค่าโดยตรง (เหมือนตัวแปร Obj-C ปกติ) พวกเขามีสองสถานะ : " มีค่า " หรือ " มีศูนย์ ":

    var aString: String? = "Hello, World!"
    aString = nil //correct, now it contains the state "has nil"

คุณสามารถตรวจสอบตัวแปรเหล่านั้นในสถานการณ์ต่าง ๆ :

if let myString = aString? {
     println(myString)
}
else { 
     println("It's nil") // this will print in our case
}

โดยใช้ "!" ต่อท้ายคุณยังสามารถเข้าถึงค่าห่อในพวกเขาเฉพาะในกรณีที่มีอยู่เหล่านั้น (กล่าวคือไม่ใช่ศูนย์ ):

let aString: String? = "Hello, World!"
// var anotherString: String = aString //error
var anotherString: String = aString!

println(anotherString) //it will print "Hello, World!"

นั่นเป็นเหตุผลที่คุณต้องใช้ "?" และ "!" และไม่ใช้ทั้งหมดตามค่าเริ่มต้น (นี่คือความสับสนที่ใหญ่ที่สุดของฉัน)

ฉันเห็นด้วยกับคำตอบข้างต้น: ประเภทตัวเลือกไม่สามารถใช้เป็นบูลีนได้


7

ในวัตถุประสงค์ตัวแปร C ที่ไม่มีค่าเท่ากับ 'ไม่มี' (มันเป็นไปได้ที่จะใช้ค่า 'ศูนย์' เช่นเดียวกับ 0 และเท็จ) ดังนั้นจึงเป็นไปได้ที่จะใช้ตัวแปรในงบตามเงื่อนไข (ตัวแปรที่มีค่าเหมือนกันกับ 'TRUE 'และผู้ที่ไม่มีค่าเท่ากับ' FALSE ')

Swift ให้ความปลอดภัยประเภทโดยการให้ 'ค่าเสริม' ie มันป้องกันข้อผิดพลาดที่เกิดขึ้นจากการกำหนดตัวแปรประเภทต่าง ๆ

ดังนั้นใน Swift จะสามารถให้บริการบูลีนได้ในงบที่มีเงื่อนไข

var hw = "Hello World"

ที่นี่แม้ว่า 'hw' เป็นสตริงมันไม่สามารถใช้ในคำสั่ง if เหมือนในวัตถุประสงค์ C

//This is an error

if hw

 {..}

เพื่อที่จะต้องสร้างเป็น

var nhw : String? = "Hello World"

//This is correct

if nhw

 {..}

5

ค่าที่เป็นทางเลือกอนุญาตให้คุณแสดงว่าไม่มีค่า เล็กน้อยเช่น NULL ใน SQL หรือ NSNull ใน Objective-C ฉันเดาว่านี่จะเป็นการปรับปรุงเนื่องจากคุณสามารถใช้สิ่งนี้ได้แม้กับประเภท "ดั้งเดิม"

// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
    case None
    case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

ข้อความที่ตัดตอนมาจาก: Apple Inc. “ ภาษาการเขียนโปรแกรม Swift” iBooks https://itun.es/gb/jEUH0.l


6
nilเป็นเพียงน้ำตาล syntactic สำหรับค่าคงที่ enum OptionalValue<T>.None( Tชนิดที่เหมาะสมกับบริบทที่คุณใช้อยู่nil) เป็นทางลัดสำหรับ? OptionalValue<T>.Some(T)
rickster

4

หมายถึงทางเลือกที่ Swift ไม่แน่ใจทั้งหมดถ้าค่านั้นสอดคล้องกับประเภท: ตัวอย่างเช่น Int? หมายความว่า Swift ไม่แน่ใจว่าหมายเลขนั้นเป็น Int หรือไม่

หากต้องการลบออกมีสามวิธีที่คุณสามารถใช้ได้

1) หากคุณแน่ใจในประเภทคุณสามารถใช้เครื่องหมายอัศเจรีย์เพื่อบังคับให้แกะได้เช่นนี้

// Here is an optional variable:

var age: Int?

// Here is how you would force unwrap it:

var unwrappedAge = age!

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

ป้อนคำอธิบายรูปภาพที่นี่

นี่ไม่จำเป็นต้องปลอดภัยดังนั้นนี่เป็นวิธีการที่อาจป้องกันการหยุดทำงานในกรณีที่คุณไม่แน่ใจประเภทและค่า:

วิธีที่ 2 และสามป้องกันปัญหานี้

2) ตัวเลือก Unwrapped โดยนัย

 if let unwrappedAge = age {

 // continue in here

 }

โปรดทราบว่าชนิดที่ยังไม่ได้เปิดอยู่ในขณะนี้Intมากกว่าInt? .

3) คำสั่งยาม

 guard let unwrappedAge = age else { 
   // continue in here
 }

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

โชคดีกับโครงการของคุณ!


1

เมื่อผมเริ่มที่จะเรียนรู้Swiftมันเป็นเรื่องยากมากที่จะตระหนักถึงเหตุผลที่ไม่จำเป็น

ให้คิดแบบนี้ ลองพิจารณาคลาสPersonที่มีคุณสมบัติสองอย่างnameและcompany.

class Person: NSObject {

    var name : String //Person must have a value so its no marked as optional
    var companyName : String? ///Company is optional as a person can be unemployed that is nil value is possible

    init(name:String,company:String?) {

        self.name = name
        self.companyName = company

    }
}

ตอนนี้ให้สร้างวัตถุสองสามอัน Person

var tom:Person = Person.init(name: "Tom", company: "Apple")//posible
var bob:Person = Person.init(name: "Bob", company:nil) // also Possible because company is marked as optional so we can give Nil

แต่เราไม่สามารถผ่านNilไปได้name

var personWithNoName:Person = Person.init(name: nil, company: nil)

optional?ตอนนี้ให้พูดคุยเกี่ยวกับสาเหตุที่เราใช้ ช่วยให้พิจารณาสถานการณ์ที่เราต้องการเพิ่มIncหลังชื่อ บริษัท เช่นจะapple apple Incเราต้องต่อท้ายIncชื่อ บริษัท และพิมพ์

print(tom.companyName+" Inc") ///Error saying optional is not unwrapped.
print(tom.companyName!+" Inc") ///Error Gone..we have forcefully unwrap it which is wrong approach..Will look in Next line
print(bob.companyName!+" Inc") ///Crash!!!because bob has no company and nil can be unwrapped.

ตอนนี้ให้ศึกษาว่าทำไมถึงเลือกที่จะเกิดขึ้น

if let companyString:String = bob.companyName{///Compiler safely unwrap company if not nil.If nil,no unwrap.

    print(companyString+" Inc") //Will never executed and no crash!!!
}

ให้แทนที่bobด้วยtom

if let companyString:String = tom.companyName{///Compiler safely unwrap company if not nil.If nil,no unwrap.

    print(companyString+" Inc") //Will never executed and no crash!!!
}

และขอแสดงความยินดีด้วย! เราจัดการอย่างเหมาะสมoptional?

ดังนั้นจุดรับรู้คือ

  1. เราจะทำเครื่องหมายตัวแปรว่าเป็นตัวเลือกถ้าเป็นไปได้ nil
  2. nilถ้าเราต้องการที่จะใช้นี้ที่ไหนสักแห่งตัวแปรในรหัสคอมไพเลอร์จะเตือนคุณว่าเราจำเป็นต้องตรวจสอบว่าเรามีการจัดการที่เหมาะสมกับตัวแปรว่าถ้ามันมี

ขอบคุณ ... Happy Coding


0

ให้การทดสอบด้วยโค้ดสนามเด็กเล่นด้านล่าง ฉันหวังว่าจะเข้าใจได้ว่าอะไรคือตัวเลือกและเหตุผลในการใช้

var sampleString: String? ///Optional, Possible to be nil

sampleString = nil ////perfactly valid as its optional

sampleString = "some value"  //Will hold the value

if let value = sampleString{ /// the sampleString is placed into value with auto force upwraped.

    print(value+value)  ////Sample String merged into Two
}

sampleString = nil // value is nil and the

if let value = sampleString{

    print(value + value)  ///Will Not execute and safe for nil checking
}

//   print(sampleString! + sampleString!)  //this line Will crash as + operator can not add nil

0

จากhttps://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html :

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

หากต้องการทำความเข้าใจให้ลึกซึ้งยิ่งขึ้นโปรดอ่านลิงก์ด้านบน


0

ดี...

? (ไม่บังคับ)ระบุว่าตัวแปรของคุณอาจมีค่าศูนย์ในขณะที่! (unwrapper)บ่งชี้ว่าตัวแปรของคุณต้องมีหน่วยความจำ (หรือค่า) เมื่อใช้ (พยายามรับค่าจากมัน) ที่รันไทม์

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

เพื่อสะท้อนความจริงที่ว่าการโยงที่เป็นตัวเลือกสามารถถูกเรียกใช้บนค่าศูนย์ผลลัพธ์ของการเรียกการเชื่อมโยงทางเลือกนั้นจะเป็นค่าที่เป็นทางเลือกเสมอถึงแม้ว่าคุณสมบัติวิธีการหรือตัวห้อยที่คุณกำลังสืบค้นจะส่งคืนค่าที่ไม่ใช่ทางเลือก คุณสามารถใช้ค่าส่งคืนทางเลือกนี้เพื่อตรวจสอบว่าการโทรแบบลูกโซ่ทางเลือกสำเร็จหรือไม่ (ตัวเลือกที่ส่งคืนมีค่า) หรือไม่ประสบความสำเร็จเนื่องจากค่าศูนย์ในห่วงโซ่ (ค่าตัวเลือกที่ส่งคืนเป็นศูนย์)

โดยเฉพาะอย่างยิ่งผลลัพธ์ของการเรียกการเชื่อมโยงตัวเลือกนั้นเป็นชนิดเดียวกันกับค่าส่งคืนที่คาดไว้ แต่ถูกรวมเป็นตัวเลือก คุณสมบัติที่ตามปกติจะส่งกลับค่าIntเมื่อเข้าถึงผ่านการผูกมัดเสริม

var defaultNil : Int?  // declared variable with default nil value
println(defaultNil) >> nil  

var canBeNil : Int? = 4
println(canBeNil) >> optional(4)

canBeNil = nil
println(canBeNil) >> nil

println(canBeNil!) >> // Here nil optional variable is being unwrapped using ! mark (symbol), that will show runtime error. Because a nil optional is being tried to get value using unwrapper

var canNotBeNil : Int! = 4
print(canNotBeNil) >> 4

var cantBeNil : Int = 4
cantBeNil = nil // can't do this as it's not optional and show a compile time error

นี่คือรายละเอียดการสอนพื้นฐานโดย Apple Developer Committee: Chaining เพิ่มเติม


0

ตัวเลือกใน Swift เป็นประเภทที่สามารถเก็บค่าได้หรือไม่มีค่าก็ได้ ตัวเลือกจะถูกเขียนโดยการผนวก? ทุกประเภท:

var name: String?

คุณสามารถอ้างอิงลิงค์นี้เพื่อรับความรู้ในเชิงลึก: https://medium.com/@agoiabeladeyemi/optionals-in-swift-2b141f12f870


-1

นี่คือการประกาศทางเลือกที่เทียบเท่าใน Swift:

var middleName: String?

การประกาศนี้สร้างตัวแปรชื่อ middleName ของชนิดสตริง เครื่องหมายคำถาม (?) หลังจากชนิดตัวแปร String ระบุว่าตัวแปร middleName สามารถมีค่าที่สามารถเป็นสตริงหรือไม่มีก็ได้ ใครก็ตามที่ดูรหัสนี้รู้ทันทีว่าชื่อกลางสามารถเป็นศูนย์ได้ มันเป็นเอกสารด้วยตัวเอง!

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

var middleName: String? = nil

สำหรับรายละเอียดเพิ่มเติมสำหรับลิงค์อ่านเพิ่มเติมด้านล่าง

http://www.iphonelife.com/blog/31369/swift-101-working-swifts-new-optional-values


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