ทำไมต้องสร้าง“ ตัวเลือก Unwrapped นัย” โดยนัยเนื่องจากนั่นหมายความว่าคุณรู้ว่ามีค่า


493

ทำไมคุณต้องสร้าง "ตัวเลือก Unwrapped ทางเลือก" โดยปริยายเมื่อสร้างตัวแปรหรือค่าคงที่ปกติ? หากคุณรู้ว่าสามารถถอดออกได้สำเร็จทำไมสร้างตัวเลือกตั้งแต่แรก ตัวอย่างเช่นทำไมจึงเป็นเช่นนี้:

let someString: String! = "this is the string"

จะเป็นประโยชน์มากกว่า:

let someString: String = "this is the string"

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

คำตอบ:


127

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

(แก้ไข) เพื่อให้ชัดเจนแม้ว่า: ตัวเลือกปกติจะดีกว่าเกือบตลอดเวลา


459

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

เมื่อใดที่จะใช้ตัวเลือก Unwrapped โดยนัย

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

1. ค่าคงที่ที่ไม่สามารถกำหนดได้ในระหว่างการเริ่มต้น

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

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

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

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

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

2. เมื่อแอพของคุณไม่สามารถกู้คืนจากการเปลี่ยนแปลงได้ nil

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

เมื่อไม่ต้องการใช้ตัวเลือก Unwrapped โดยนัย

1. ตัวแปรสมาชิกคำนวณอย่าง Lazily

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

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

ตอนนี้ตัวแปรสมาชิกcontentsจะไม่เริ่มต้นจนกว่าจะมีการเข้าถึงครั้งแรก สิ่งนี้จะช่วยให้ชั้นเรียนมีโอกาสเข้าสู่สถานะที่ถูกต้องก่อนที่จะคำนวณค่าเริ่มต้น

หมายเหตุ:สิ่งนี้อาจขัดแย้งกับ # 1 จากด้านบน อย่างไรก็ตามมีความแตกต่างที่สำคัญที่จะทำ buttonOriginalWidthดังกล่าวข้างต้นจะต้องตั้งค่าในช่วง viewDidLoad เพื่อป้องกันไม่ให้คนเปลี่ยนปุ่มกว้างก่อนที่ทรัพย์สินที่มีการเข้าถึง

2. ทุกที่อื่น

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


4
คำตอบนี้ควรได้รับการปรับปรุงสำหรับเบต้า 5. if someOptionalคุณสามารถใช้อีกต่อไป
ซานตาคลอส

2
@SantaClaus hasValueมีการกำหนดสิทธิ์ในตัวเลือก ฉันชอบความหมายของกับบรรดาhasValue != nilฉันรู้สึกว่ามันเข้าใจได้ง่ายกว่าสำหรับโปรแกรมเมอร์ใหม่ที่ไม่ได้ใช้nilภาษาอื่น มากขึ้นกว่าตรรกะhasValue nil
drewag

2
ดูเหมือนว่าhasValueจะถูกดึงออกมาจากเบต้า 6 แอชนำมันกลับมา ... github.com/AshFurrow/hasValue
Chris Wagner

1
@newacct เกี่ยวกับชนิดส่งคืนของตัวเริ่มต้น Objc มันเป็นของที่ไม่ระบุนัยโดยนัย Unwrapped ตัวเลือก พฤติกรรมที่คุณอธิบายไว้สำหรับการใช้ "ไม่ใช่ตัวเลือก" เป็นสิ่งที่ตัวเลือก Unwrapped ทางอ้อมจะทำ (ไม่ล้มเหลวจนกว่าจะเข้าถึง) เกี่ยวกับการปล่อยให้โปรแกรมล้มเหลวก่อนหน้านี้โดยการบังคับใช้การถอนกำลังออกไปฉันยอมรับว่าต้องการใช้ตัวเลือกที่ไม่ใช่ตัวเลือก แต่ไม่สามารถทำได้เสมอไป
drewag

1
@ ยืนยันหมายเลข ไม่ว่าจะเกิดอะไรขึ้นก็จะปรากฏใน Objective-C เป็นตัวชี้ (ถ้าเป็นตัวเลือก, ไม่ได้เปิดใช้งานโดยปริยายหรือไม่มีตัวเลือก)
drewag

56

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

ตัวอย่างเช่น:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Bอินสแตนซ์ใด ๆควรมีการmyBuddyAอ้างอิงที่ถูกต้องอยู่เสมอดังนั้นเราไม่ต้องการให้ผู้ใช้ถือว่าเป็นตัวเลือก แต่เราต้องการให้เป็นทางเลือกเพื่อให้เราสามารถสร้าง a Bก่อนที่เราจะต้องAอ้างถึง

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


7
ฉันคิดว่าหนึ่งในเหตุผลที่พวกเขาสร้างคุณลักษณะภาษานี้คือ@IBOutlet
Jiaaro

11
+1 สำหรับคำเตือน "HOWEVER" อาจไม่เป็นความจริงตลอดเวลา แต่เป็นสิ่งที่ควรระวัง
JMD

4
คุณยังคงมีรอบการอ้างอิงที่รัดกุมระหว่าง A และ B ตัวเลือกที่ยังไม่ได้เปิดโดยปริยายอย่าสร้างการอ้างอิงที่อ่อนแอ คุณยังคงต้องประกาศ myByddyA หรือ myBuddyB ว่าอ่อนแอ (อาจเป็น myBuddyA)
drewag

6
เพื่อให้ชัดเจนยิ่งขึ้นว่าทำไมคำตอบนี้ผิดและทำให้เข้าใจผิดอันตราย: ตัวเลือก Unwrapped นัยโดยนัยไม่มีอะไรเกี่ยวข้องกับการจัดการหน่วยความจำและไม่ป้องกันรอบการรักษา อย่างไรก็ตาม Optionals Unwrapped โดยนัยยังคงมีประโยชน์ในกรณีที่อธิบายไว้สำหรับการตั้งค่าการอ้างอิงสองทาง ดังนั้นเพียงแค่เพิ่มการweakประกาศและลบ "โดยไม่ต้องสร้างวงจรการเก็บรักษาที่แข็งแกร่ง"
drewag

1
@drewag: ถูกต้อง - ฉันได้แก้ไขคำตอบเพื่อลบรอบการเก็บรักษา ฉันตั้งใจจะทำให้การอ้างอิงกลับอ่อนแอ แต่ฉันคิดว่ามันทำให้จิตใจของฉันลื่น
n8gray

37

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

หนังสือ Swift ในบทThe Basicsส่วนทางเลือก Unwrapped Option โดยปริยายบอกว่า:

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

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

ส่วนบนจากหนังสือ Swift อ้างอิงไปยังบทการนับการอ้างอิงอัตโนมัติ :

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

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

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

ที่ครอบคลุม“ เมื่อต้องการใช้ตัวเลือกที่ไม่ได้เปิดใช้งานโดยนัยในรหัสของคุณ?” คำถาม. ในฐานะนักพัฒนาแอปพลิเคชันส่วนใหญ่คุณจะพบพวกเขาในลายเซ็นต์วิธีการของไลบรารีที่เขียนใน Objective-C ซึ่งไม่มีความสามารถในการแสดงประเภทตัวเลือก

จากการใช้ Swift กับ Cocoa และ Objective-C หัวข้อการทำงานกับไม่มี :

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

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

... และอื่น ๆ ที่นี่อยู่ มังกร


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

@Hairgami_Master ฉันได้เพิ่มคำตอบของตัวเองพร้อมด้วยรายการและตัวอย่างที่เป็นรูปธรรม
drewag

18

ตัวอย่างง่ายๆแบบหนึ่งบรรทัด (หรือหลายบรรทัด) ไม่ครอบคลุมถึงพฤติกรรมของตัวเลือกที่ดีมาก - ใช่ถ้าคุณประกาศตัวแปรและระบุค่าทันทีไม่มีตัวเลือกใด ๆ

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

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

เรารู้ว่าprintSizeจะถูกเรียกใช้หลังจากที่โหลดมุมมอง - มันเป็นวิธีการกระทำที่เชื่อมโยงกับตัวควบคุมภายในมุมมองนั้นและเราแน่ใจว่าจะไม่เรียกมันเป็นอย่างอื่น ดังนั้นเราจึงสามารถช่วยตัวเองได้บางส่วนที่ไม่จำเป็นต้องตรวจสอบ / !ผลผูกพันกับ Swift ไม่รู้จักการรับประกันนั้น (อย่างน้อยก็จนกว่า Apple จะแก้ปัญหาการหยุดชะงัก) ดังนั้นคุณจึงบอกคอมไพเลอร์ว่ามันมีอยู่

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


ทำไมไม่เริ่มต้นขนาดหน้าจอเป็น CGSize (สูง: 0, ความกว้าง: 0) และบันทึกปัญหาในการตะโกนตัวแปรทุกครั้งที่คุณเข้าถึง
Martin Gordon

ขนาดอาจไม่ได้เป็นตัวอย่างที่ดีที่สุดเนื่องจากCGSizeZeroสามารถเป็นค่า Sentinel ที่ดีในการใช้งานจริง แต่ถ้าคุณมีขนาดที่โหลดจากปลายปากกาซึ่งอาจเป็นศูนย์จริง ๆ จากนั้นการใช้CGSizeZeroSentinel ไม่ช่วยให้คุณแยกแยะความแตกต่างระหว่างค่าที่ไม่มีการตั้งค่าและตั้งค่าเป็นศูนย์ นอกจากนี้สิ่งนี้ใช้ได้ดีกับประเภทอื่น ๆ ที่โหลดจากปลายปากกา (หรือที่อื่น ๆ หลังจากนั้นinit): สตริง, การอ้างอิงถึงการแสดงย่อย ฯลฯ
rickster

2
ส่วนหนึ่งของจุดตัวเลือกในภาษาที่ใช้งานได้คือไม่มีค่า Sentinel คุณมีค่าหรือคุณไม่มี คุณไม่ควรมีกรณีที่คุณมีค่าที่ระบุถึงค่าที่ขาดหายไป
Wayne Tanner

4
ฉันคิดว่าคุณเข้าใจผิดคำถามของ OP OP ไม่ได้ถามเกี่ยวกับกรณีทั่วไปสำหรับตัวเลือกค่อนข้างเฉพาะเกี่ยวกับความต้องการ / การใช้ตัวเลือกที่ยังไม่ได้เปิดใช้โดยนัย (เช่นไม่ใช่let foo? = 42ค่อนข้างlet foo! = 42) นี่ไม่ใช่ที่อยู่ที่ (นี่อาจเป็นคำตอบที่เกี่ยวข้องกับตัวเลือกให้คำนึงถึงคุณ แต่ไม่เกี่ยวกับตัวเลือกที่ยังไม่ได้เปิดซึ่งเป็นสัตว์ที่เกี่ยวข้อง / แตกต่างกัน)
JMD

15

Apple ให้ตัวอย่างที่ยอดเยี่ยมในภาษาการเขียนโปรแกรม Swift -> การนับการอ้างอิงอัตโนมัติ -> การแก้ไขรอบการอ้างอิงที่แข็งแกร่งระหว่างอินสแตนซ์ของคลาส -> การอ้างอิงที่ไม่เป็นเจ้าของและไม่มีการระบุคุณสมบัติทางเลือกโดยปริยาย

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

สำหรับ initializer ที่ถูกเรียกจากภายในสำหรับการเริ่มต้นCity Countryแต่การเริ่มต้นสำหรับการCountryไม่สามารถผ่านselfไปCityinitializer จนกว่าใหม่Countryเช่นจะเริ่มต้นได้อย่างเต็มที่ตามที่อธิบายไว้ในสองเฟสเริ่มต้น

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


กวดวิชาที่กล่าวถึงในคำตอบนี้เป็นที่นี่
Franklin Yu

3

เหตุผลของการบอกทางเลือกโดยปริยายนั้นง่ายกว่าที่จะอธิบายโดยดูที่เหตุผลในการบังคับแกะ

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

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

ซึ่งไม่ดีเท่า

println("\(value!)")

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


1
@newacct: non-nil ในการไหลที่เป็นไปได้ทั้งหมดผ่านรหัสของคุณไม่เหมือนกับ non-nil จากการเริ่มต้น parent (คลาส / struct) ผ่านการจัดสรรคืน Interface Builder เป็นตัวอย่างแบบคลาสสิก (แต่มีรูปแบบการกำหนดค่าเริ่มต้นล่าช้าอื่น ๆ อีกมากมาย): หากคลาสนั้นเคยใช้จากปลายปากกาเท่านั้นตัวแปรชุดทางออกจะไม่ถูกตั้งค่าinit(ซึ่งคุณอาจไม่ได้ใช้) อีกครั้งรับประกันที่จะตั้งหลัง/awakeFromNib viewDidLoad
rickster

3

หากคุณทราบว่าผลตอบแทนที่คุ้มค่าจากตัวเลือกแทนnil, โดยปริยายแกะ optionalsใช้โดยตรงจับค่าเหล่านั้นจาก optionals และ optionals ไม่ใช่ไม่สามารถ

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

ดังนั้นนี่คือความแตกต่างระหว่างการใช้

let someString : String! และ let someString : String


1
นี่ไม่ได้ตอบคำถามของ OP OP รู้ว่าตัวเลือก Unwrapped ทางอ้อมคืออะไร
Franklin Yu

0

ฉันคิดว่าOptionalเป็นชื่อที่ไม่ดีสำหรับโครงสร้างนี้ซึ่งสร้างความสับสนให้กับผู้เริ่มต้นจำนวนมาก

ภาษาอื่น (เช่น Kotlin และ C #) ใช้คำศัพท์Nullableและทำให้เข้าใจได้ง่ายขึ้น

Nullableหมายความว่าคุณสามารถกำหนดค่า Null ให้กับตัวแปรประเภทนี้ ดังนั้นถ้าเป็นNullable<SomeClassType>เช่นนั้นคุณสามารถกำหนดค่า Null ให้ถ้าเป็นเพียงSomeClassTypeคุณไม่สามารถทำได้ นั่นเป็นเพียงวิธีการทำงานของสวิฟท์

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

Btw ฉันขอแนะนำให้คุณดูว่าวิธีนี้ใช้ได้กับภาษาอื่นเช่น Kotlin และ C #

นี่คือลิงค์อธิบายคุณลักษณะนี้ใน Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

ภาษาอื่น ๆ เช่น Java และ Scala มีOptionals แต่ทำงานแตกต่างจากOptionals ใน Swift เนื่องจากประเภทของ Java และ Scala นั้นเป็นโมฆะทั้งหมดโดยค่าเริ่มต้น

โดยรวมแล้วฉันคิดว่าคุณสมบัตินี้ควรมีชื่อNullableใน Swift ไม่ใช่Optional...


0

Implicitly Unwrapped Optionalเป็นน้ำตาลทรายOptionalที่ไม่ได้บังคับให้โปรแกรมเมอร์ทำการแกะตัวแปร มันสามารถใช้สำหรับตัวแปรที่ไม่สามารถเริ่มต้นในช่วงtwo-phase initialization processและหมายถึงไม่มีศูนย์ ตัวแปรนี้ทำตัวเป็นnon-nilแต่จริงๆแล้วเป็นตัวแปรเสริม ตัวอย่างที่ดีคือ - ช่องของตัวสร้างส่วนต่อประสาน

Optional มักจะเป็นที่นิยม

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.