"มูลค่าที่ไม่ได้ห่อ" ใน Swift คืออะไร


155

ฉันกำลังเรียนรู้ Swift สำหรับ iOS 8 / OSX 10.10 โดยทำตามบทช่วยสอนนี้และคำว่า " มูลค่าที่ยังไม่ได้ใช้ " ถูกนำมาใช้หลายครั้งเช่นเดียวกับในย่อหน้านี้ (ภายใต้วัตถุและคลาส ):

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

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare?.sideLength

ฉันไม่เข้าใจและค้นหาบนเว็บโดยไม่มีโชค

สิ่งนี้หมายความว่าอย่างไร


แก้ไข

จากคำตอบของ Cezary มีความแตกต่างเล็กน้อยระหว่างเอาท์พุทของรหัสต้นฉบับและทางออกสุดท้าย (ทดสอบบนสนามเด็กเล่น):

รหัสเดิม

รหัสเดิม

ทางออกของ Cezary

ทางออกของ Cezary

คุณสมบัติ superclass 'จะแสดงในผลลัพธ์ในกรณีที่สองในขณะที่มีวัตถุว่างเปล่าในกรณีแรก

ผลลัพธ์ไม่ควรเหมือนกันในทั้งสองกรณี?

คำถาม & คำตอบที่เกี่ยวข้อง: ค่าเสริมใน Swift คืออะไร


1
คำตอบของ Cezary คือจุดที่ นอกจากนี้ยังมีหนังสือแนะนำยอดเยี่ยมสำหรับ Swift ฟรี
Daniel Storm

@DanielStormApps iBook นี้เป็นรุ่นพกพาของการกวดวิชาเชื่อมโยงในคำถามของฉัน :)
Bigood

คำตอบ:


289

ก่อนอื่นคุณต้องเข้าใจว่าประเภทตัวเลือกคืออะไร ประเภทที่เป็นทางเลือกโดยทั่วไปหมายความว่าตัวแปรนั้นสามารถเป็น nilได้

ตัวอย่าง:

var canBeNil : Int? = 4
canBeNil = nil

เครื่องหมายคำถามแสดงถึงความจริงที่canBeNilเป็นnilไปได้

สิ่งนี้จะไม่ทำงาน:

var cantBeNil : Int = 4
cantBeNil = nil // can't do this

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

var canBeNil : Int? = 4
println(canBeNil!)

รหัสของคุณควรมีลักษณะเช่นนี้:

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare!.sideLength

Sidenote:

นอกจากนี้คุณยังสามารถประกาศตัวเลือกให้แกะโดยอัตโนมัติด้วยการใช้เครื่องหมายอัศเจรีย์แทนเครื่องหมายคำถาม

ตัวอย่าง:

var canBeNil : Int! = 4
print(canBeNil) // no unwrapping needed

วิธีอื่นในการแก้ไขรหัสของคุณคือ:

let optionalSquare: Square! = Square(sideLength: 2.5, name: "optional square") 
let sideLength = optionalSquare.sideLength

แก้ไข:

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

การเปรียบเทียบสนามเด็กเล่นอย่างรวดเร็ว:

สนามเด็กเล่น

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

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

if let sideLength = optionalSquare?.sideLength {
    println("sideLength is not nil")
} else {
    println("sidelength is nil")
}

1
ขอบคุณสำหรับคำอธิบายที่ชัดเจน! รหัสที่รวมอยู่ในคำถามของฉันนั้นยกมาจากบทช่วยสอนของ Apple (ลิงก์ในคำถาม) และฉันเดาว่ามันควรจะทำงานได้ดีเท่าที่จะเป็นไปได้ แม้ว่าทางออกสุดท้ายของคุณและต้นฉบับจะให้ผลลัพธ์ที่แตกต่างกัน (ดูการแก้ไขของฉัน) ความคิดใด ๆ
Bigood

2
เย็น. คำอธิบายที่ดี ฉันยังคงสับสนอยู่ เหตุใดฉันจึงต้องการใช้ค่าเพิ่มเติม มันจะมีประโยชน์เมื่อไหร่? เหตุใด Apple จึงสร้างลักษณะการทำงานและไวยากรณ์นี้
ทอดด์เลห์แมน

1
มันมีความเกี่ยวข้องโดยเฉพาะอย่างยิ่งเมื่อมันมาถึงคุณสมบัติของชั้นเรียน Swift ต้องการวิธีการที่ไม่จำเป็นทั้งหมดเพื่อเริ่มต้นในinit()ฟังก์ชันของคลาส ฉันขอแนะนำให้อ่านบท "พื้นฐาน" (ครอบคลุมตัวเลือก), "การเริ่มต้น" และ "ตัวเลือกผูกมัด" ใน Swift book ที่วางจำหน่ายโดย Apple
Cezary Wojcik

2
@ToddLehman Apple สร้างสิ่งนี้เพื่อช่วยให้คุณเขียนรหัสที่ปลอดภัยยิ่งขึ้น เช่นลองนึกภาพข้อยกเว้นตัวชี้โมฆะทั้งหมดใน Java หยุดทำงานโปรแกรมเนื่องจากบางคนลืมตรวจสอบโมฆะ หรือพฤติกรรมแปลก ๆ ใน Objective-C เพราะคุณลืมตรวจสอบไม่มี ด้วยประเภทที่เป็นตัวเลือกคุณจะไม่ลืมที่จะจัดการกับศูนย์ คอมไพเลอร์บังคับให้คุณจัดการกับมัน
Erik Engheim

5
@ AdamSmith - มาจากพื้นหลังของ Java ฉันสามารถเห็นการใช้ตัวเลือกได้อย่างแน่นอน ... แต่เมื่อเร็ว ๆ นี้ (เมื่อเร็ว ๆ นี้) จากพื้นหลัง Objective-C ฉันยังคงมีปัญหาในการเห็นการใช้งานเนื่องจากวัตถุประสงค์ - C อย่างชัดเจนช่วยให้คุณสามารถส่งข้อความไปยังวัตถุศูนย์ อืมมม ฉันชอบที่จะเห็นคนเขียนตัวอย่างของสิ่งเหล่านี้แสดงว่าพวกเขาช่วยทำให้รหัสสั้นลงและปลอดภัยกว่ารหัสเทียบเท่าที่ไม่ได้ใช้ ฉันไม่ได้บอกว่ามันไม่มีประโยชน์ ฉันแค่บอกว่าฉันยังไม่ได้ "เข้าใจ"
ทอดด์เลห์แมน

17

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

ดังนั้นให้ฉันช่วยเพื่อนนักพัฒนา "การคิดอย่างถูกต้อง" (มองเห็นด้วยตาเปล่า) โดยให้มุมมองที่แตกต่างไปจากคำตอบที่ถูกต้อง นี่คือการเปรียบเทียบที่ดีที่ช่วยฉันได้มาก

การห่อของขวัญวันเกิด

คิดว่าตัวเลือกเป็นเหมือนของขวัญวันเกิดที่ห่อแข็งแข็งสี

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

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

อะไรอยู่ในกล่อง?! การเปรียบเทียบ

แม้หลังจากที่คุณแกะตัวแปรคุณยังคงเป็นเหมือนแบรดพิตต์ในฉากสุดท้ายในเซเว่น ( คำเตือน : สปอยเลอร์และภาษาเหม็นที่มีเรทติ้งและความรุนแรง) เพราะแม้หลังจากที่คุณถอดของขวัญออกมาคุณก็ยังอยู่ในสถานการณ์ต่อไปนี้: ตอนนี้คุณมีnilหรือกล่องที่มีบางสิ่ง (แต่คุณไม่รู้อะไร)

คุณอาจจะทราบชนิดของบางสิ่งบางอย่าง ตัวอย่างเช่นหากคุณประกาศตัวแปรว่าเป็นประเภท[Int:Any?]แล้วคุณจะรู้ว่าคุณมีพจนานุกรม (อาจว่างเปล่า) พร้อมตัวห้อยจำนวนเต็มที่ให้เนื้อหาที่ถูกห่อเป็นประเภทเก่า ๆ

นั่นเป็นเหตุผลที่การจัดการกับประเภทคอลเลกชัน (พจนานุกรมและอะเรย์) ในสวิฟท์สามารถมีขนดก

กรณีในจุด:

typealias presentType = [Int:Any?]

func wrap(i:Int, gift:Any?) -> presentType? {
    if(i != 0) {
        let box : presentType = [i:wrap(i-1,gift:gift)]
        return box
    }
    else {
        let box = [i:gift]
        return box
    }
}

func getGift() -> String? {
    return "foobar"
}

let f00 = wrap(10,gift:getGift())

//Now we have to unwrap f00, unwrap its entry, then force cast it into the type we hope it is, and then repeat this in nested fashion until we get to the final value.

var b4r = (((((((((((f00![10]! as! [Int:Any?])[9]! as! [Int:Any?])[8]! as! [Int:Any?])[7]! as! [Int:Any?])[6]! as! [Int:Any?])[5]! as! [Int:Any?])[4]! as! [Int:Any?])[3]! as! [Int:Any?])[2]! as! [Int:Any?])[1]! as! [Int:Any?])[0])

//Now we have to DOUBLE UNWRAP the final value (because, remember, getGift returns an optional) AND force cast it to the type we hope it is

let asdf : String = b4r!! as! String

print(asdf)

nice 😊😊😊😊😊😊😊😊😊
SamSol

รักการเปรียบเทียบ! ดังนั้นมีวิธีที่ดีกว่าที่จะแกะกล่องหรือไม่ ในตัวอย่างโค้ดของคุณ
David T.

แมวของSchrödingerอยู่ในกล่อง
Jacksonkr

ขอบคุณที่ทำให้ฉันสับสน .... โปรแกรมเมอร์แบบไหนที่มอบของขวัญวันเกิดที่ว่างเปล่าอยู่ดี? kinda mean
delta2flat

@ Jacksonkr หรือไม่
amsmath

13

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

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


หมายเหตุ: ตัวแปรหรือประเภทIntคือไม่ได้Int?เช่นเดียวกับ พวกเขาเป็นสองประเภทที่แตกต่างกันที่ไม่สามารถดำเนินการกับแต่ละอื่น ๆ


ใช้ตัวเลือก

var myString: String?

myString = "foobar"

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

print(myString)

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

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


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

หมายเหตุ: คุณสามารถให้ชื่อคงที่แบบไม่เปลี่ยนแปลงตามเงื่อนไขเหมือนกับตัวแปรทางเลือกที่คุณไม่ได้เปิดใช้

if let myString = myString {
    print(myString)
    // will print "foobar"
}

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

if let myString = myString {
    print(myString)
    // will print "foobar"
}
else {
    print("No value")
}

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

var myString: String?

myString = "foobar"

print(myString!)

ข้างต้นนี้เป็นรหัส Swift ที่ถูกต้องทั้งหมด มันพิมพ์ค่าของmyStringที่ถูกตั้งค่าเป็น "foobar" ผู้ใช้จะเห็นfoobarพิมพ์ในคอนโซลและที่เกี่ยวกับมัน แต่สมมติว่าไม่มีการตั้งค่า:

var myString: String?

print(myString!) 

ตอนนี้เรามีสถานการณ์ที่แตกต่างกันในมือของเรา แตกต่างจาก Objective-C เมื่อใดก็ตามที่มีความพยายามบังคับให้แกะตัวเลือกเพิ่มเติมและยังไม่ได้ตั้งค่าตัวเลือกและnilเมื่อคุณพยายามแกะตัวเลือกเพื่อดูว่ามีอะไรอยู่ในแอปพลิเคชันของคุณ


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

var something: Any?

บางแห่งในรหัสของเราตัวแปรsomethingจะได้รับการตั้งค่าบางอย่าง บางทีเรากำลังใช้ยาชื่อสามัญหรืออาจมีเหตุผลอื่นที่เกิดขึ้นซึ่งจะทำให้สิ่งนี้เปลี่ยนแปลง ดังนั้นภายหลังในรหัสของเราที่เราต้องการใช้somethingแต่ก็ยังสามารถรักษาได้แตกต่างกันถ้ามันเป็นประเภทที่แตกต่างกัน ในกรณีนี้คุณจะต้องใช้asคำหลักเพื่อกำหนดสิ่งนี้:

หมายเหตุ: asผู้ประกอบการเป็นวิธีที่คุณพิมพ์ cast ใน Swift

// Conditionally
if let thing = something as? Int {
    print(thing) // 0
}

// Optionally
let thing = something as? Int
print(thing) // Optional(0)

// Forcibly
let thing = something as! Int
print(thing) // 0, if no exception is raised

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

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

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

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

ตอนนี้มันเป็น Optional(0)

และถ้าคุณเคยพยายามทำการบ้านของคุณเขียนสิ่งที่ชอบ

1 + Optional(1) = 2

ครูคณิตศาสตร์ของคุณน่าจะให้ "F" แก่คุณ เช่นเดียวกับ Swift ยกเว้นสวิฟท์จะไม่รวบรวมเลยแทนที่จะให้เกรด เพราะในตอนท้ายของวันนี้อาจจะเป็นตัวเลือกในความเป็นจริงจะเป็นศูนย์

ปลอดภัยไว้ก่อน


คำอธิบายที่ดีของประเภทตัวเลือก ช่วยสิ่งที่ชัดเจนขึ้นสำหรับฉัน
Tobe_Sta

0

'?' หมายถึงนิพจน์ผูกมัดตัวเลือก

'!' หมายถึงแรงตามตัวอักษร
ป้อนคำอธิบายรูปภาพที่นี่

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