Swift: ตัวเลือกการทดสอบสำหรับศูนย์


138

ฉันใช้ Xcode 6 Beta 4 ฉันมีสถานการณ์แปลก ๆ นี้ซึ่งฉันไม่สามารถหาวิธีทดสอบตัวเลือกได้อย่างเหมาะสม

หากฉันมี xyz ที่เป็นทางเลือกคือวิธีทดสอบที่ถูกต้อง:

if (xyz) // Do something

หรือ

if (xyz != nil) // Do something

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

ตัวอย่างเฉพาะของฉันคือการใช้ตัวแยกวิเคราะห์ GData XML เชื่อมต่ออย่างรวดเร็ว:

let xml = GDataXMLDocument(
    XMLString: responseBody,
    options: 0,
    error: &xmlError);

if (xmlError != nil)

ที่นี่ถ้าฉันเพิ่งทำ:

if xmlError

มันจะกลับมาเป็นจริงเสมอ อย่างไรก็ตามถ้าฉันทำ:

if (xmlError != nil)

จากนั้นก็ใช้งานได้ (ตามวิธีการทำงานใน Objective-C)

มีบางอย่างกับ GData XML และวิธีที่ใช้กับตัวเลือกที่ฉันขาดหายไปหรือไม่?


4
ขอดูตัวอย่างทั้งหมดสำหรับกรณีที่ไม่คาดคิดและกรณีข้อผิดพลาดของคุณได้ไหม (และฉันรู้ว่ามันยาก แต่พยายามที่จะเริ่มสูญเสียวงเล็บรอบเงื่อนไข!)
Matt Gibson

1
สิ่งนี้มีการเปลี่ยนแปลงใน Xcode 6 beta 5
newacct


ฉันเพิ่งอัปเดตคำถามกับกรณีที่ไม่คาดคิด
tng

ฉันยังไม่ได้อัปเดตเป็นเบต้า 5 ฉันจะทำในไม่ช้า น่าดู ?? ใน Swift และพฤติกรรมเสริมที่สอดคล้องกันมากขึ้น
tng

คำตอบ:


180

ใน Xcode Beta 5 พวกเขาไม่อนุญาตให้คุณทำ:

var xyz : NSString?

if xyz {
  // Do something using `xyz`.
}

สิ่งนี้ก่อให้เกิดข้อผิดพลาด:

ไม่เป็นไปตามโปรโตคอล 'BooleanType.Protocol'

คุณต้องใช้หนึ่งในรูปแบบเหล่านี้:

if xyz != nil {
   // Do something using `xyz`.
}

if let xy = xyz {
   // Do something using `xy`.
}

5
ใครสามารถอธิบายรายละเอียดว่าทำไม xyz == nil จึงไม่ทำงาน
คริสตอฟ

ตัวอย่างที่สองควรอ่าน: // ทำบางอย่างโดยใช้ xy (ซึ่งเป็นข้อแตกต่างหลักในกรณีการใช้งานระหว่างสองตัวแปร)
Alper

@ คริสตอฟ: ข้อความแสดงข้อผิดพลาดคือค่าคงที่ 'xyz' ที่ใช้ก่อนที่จะถูกเปลี่ยนค่า ฉันคิดว่าคุณต้องต่อท้าย '= nil' ที่ท้ายบรรทัดของการประกาศ xyz
user3207158

สำหรับผู้ที่เพิ่งเริ่มต้นอย่างรวดเร็วและกำลังสงสัย: คุณยังต้องแกะ xyz ในบล็อก if แม้ว่าจะปลอดภัยแม้ว่าคุณจะบังคับแกะ
Omar

@ โอมาร์นั่นคือความสวยงามของรูปแบบที่สองคุณใช้ xy ในบล็อกโดยไม่ต้องแกะมัน
Erik Doernenburg

25

หากต้องการเพิ่มคำตอบอื่น ๆ แทนที่จะกำหนดให้กับตัวแปรที่มีชื่อแตกต่างกันภายในifเงื่อนไข:

var a: Int? = 5

if let b = a {
   // do something
}

คุณสามารถใช้ชื่อตัวแปรเดียวกันซ้ำได้เช่นนี้:

var a: Int? = 5

if let a = a {
    // do something
}

วิธีนี้อาจช่วยให้คุณหลีกเลี่ยงการใช้ชื่อตัวแปรโฆษณาจนหมด ...

สิ่งนี้ใช้ประโยชน์จากการสร้างเงาตัวแปรที่รองรับใน Swift


18

หนึ่งในวิธีที่ตรงที่สุดในการใช้ตัวเลือกมีดังต่อไปนี้:

สมมติว่าxyzเป็นประเภททางเลือกเช่นInt?ตัวอย่าง

if let possXYZ = xyz {
    // do something with possXYZ (the unwrapped value of xyz)
} else {
    // do something now that we know xyz is .None
}

ด้วยวิธีนี้คุณสามารถทดสอบว่าxyzมีค่าหรือไม่และถ้าเป็นเช่นนั้นให้ใช้ค่านั้นทันที

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


1
ฉันคิดว่าวิธีนี้ไม่ดีเพราะฉันสร้างตัวแปรใหม่ (ค่าคงที่) โดยไม่จำเป็น ans ต้องสร้างชื่อใหม่ซึ่งเวลาส่วนใหญ่จะแย่กว่าก่อนหน้านี้ ฉันใช้เวลามากขึ้นในการเขียนและสำหรับผู้อ่าน
Lombas

3
เป็นวิธีมาตรฐานและวิธีที่แนะนำในการแกะตัวแปรเสริม "ถ้าปล่อย" มีพลังอย่างจริงจัง
gnasher729

@ gnasher729 แต่ถ้าคุณต้องการใช้ส่วนอื่นเท่านั้น
Brad Thomas

16

สวิฟต์ 3.0, 4.0

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

1. ถ้าปล่อยให้

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

นอกเหนือจากนั้นถ้าxไม่ใช่ศูนย์การปิด if จะถูกดำเนินการและx_valจะสามารถใช้ได้ภายใน มิฉะนั้นการปิดอื่นจะถูกเรียก

if let x_val = x, x_val > 5 {
    //x_val available on this scope
} else {

}

2. ยามให้

guard letสามารถทำสิ่งที่คล้ายกันได้ จุดประสงค์หลักคือทำให้สมเหตุสมผลมากขึ้น เหมือนกับการพูดว่าMake sure the variable is not nil, or stop the function . guard letยังสามารถตรวจสอบเงื่อนไขเพิ่มเติมได้อีกif letด้วย

ความแตกต่างคือค่าที่ยังไม่ได้ห่อจะมีอยู่ในขอบเขตเดียวกันดังguard letที่แสดงในความคิดเห็นด้านล่าง นอกจากนี้ยังนำไปสู่จุดนั้นในที่อื่นปิดโปรแกรมที่มีเพื่อออกจากขอบเขตปัจจุบันโดยreturn, breakฯลฯ

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope

11

จากคู่มือการเขียนโปรแกรมที่รวดเร็ว

หากคำสั่งและบังคับให้ยกเลิกการห่อ

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

ดังนั้นวิธีที่ดีที่สุดคือ

// swift > 3
if xyz != nil {}

และถ้าคุณกำลังใช้xyzคำสั่ง in if คุณสามารถแกะxyzคำสั่ง if ในตัวแปรค่าคงที่ได้ดังนั้นคุณไม่จำเป็นต้องแกะทุกที่ในคำสั่ง if ที่xyzจะใช้

if let yourConstant = xyz{
      //use youtConstant you do not need to unwrap `xyz`
}

อนุสัญญานี้เสนอแนะโดยappleและจะตามด้วย devlopers


2
สวิฟท์ใน 3 if xyz != nil {...}ที่คุณต้องทำ
psmythirl

ในตัวเลือก Swift 3 ไม่สามารถใช้เป็นบูลีนได้ ประการแรกเนื่องจากเป็น C-ism ที่ไม่ควรมีในภาษามาก่อนและประการที่สองเนื่องจากทำให้เกิดความสับสนกับ Bool ที่เป็นทางเลือก
gnasher729

9

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

กล่าวอีกนัยหนึ่งตอนนี้ตัวเลือกของคุณรวมถึงการตรวจสอบอย่างชัดเจนสำหรับnil:

if xyz != nil {
    // Do something with xyz
}

การผูกเพิ่มเติม:

if let xyz = xyz {
    // Do something with xyz
    // (Note that we can reuse the same variable name)
}

และguardงบ:

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    // e.g. by calling return, break, continue, or throw
    return
}

// Do something with xyz, which is now guaranteed to be non-nil

สังเกตว่าการเชื่อมโยงทางเลือกธรรมดาสามารถนำไปสู่การเยื้องมากขึ้นได้อย่างไรเมื่อมีค่าทางเลือกมากกว่าหนึ่งค่า:

if let abc = abc {
    if let xyz = xyz {
        // Do something with abc and xyz
    }        
}

คุณสามารถหลีกเลี่ยงการซ้อนกันได้ด้วยguardคำสั่ง:

guard let abc = abc else {
    // Handle failure and then exit this code block
    return
}

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    return
}

// Do something with abc and xyz

1
ดังที่กล่าวไว้ข้างต้นคุณสามารถทำได้เช่นกันเพื่อกำจัด statmenets เสริมที่ซ้อนกัน ถ้าให้ abc = abc? .xyz? .something {}
Suhaib

1
@Suhaib Ah จริง: คุณยังสามารถใช้ Chaining เสริม ( developer.apple.com/library/prerelease/content/documentation/… ) เพื่อเข้าถึงคุณสมบัติที่ซ้อนกันได้อย่างรวดเร็ว ฉันไม่ได้พูดถึงที่นี่เพราะฉันคิดว่ามันอาจจะมีสัมผัสจากคำถามเดิมมากเกินไป
Chris Frederick

ตอบโจทย์มาก! แต่ Swift OMG ... ยังอีกยาวไกลจากการเป็นมิตรกับโปรแกรมเมอร์!
turingtested

@turingtested ขอบคุณ! อันที่จริงฉันคิดว่านี่เป็นโปรแกรมเมอร์ที่เป็นมิตรในแง่ที่รับประกันว่าคุณจะไม่พยายามใช้nilค่าโดยไม่ได้ตั้งใจแต่ฉันยอมรับว่าเส้นโค้งการเรียนรู้ค่อนข้างสูงชัน :)
Chris Frederick

4

ส่วนขยายโปรโตคอล Swift 5

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

import Foundation

public extension Optional {

    var isNil: Bool {

        guard case Optional.none = self else {
            return false
        }

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

การใช้งาน

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}

ฉันได้รับการโหวตลงคะแนนเล็กน้อยเกี่ยวกับคำตอบนี้และต้องการทราบสาเหตุ อย่างน้อยแสดงความคิดเห็นหากคุณกำลังจะลงคะแนน
Brody Robertson

1
น่าจะเป็นswiftแอนะล็อกpythonistasที่พยายามรักษาภาษาให้บริสุทธิ์ในรูปแบบไพโธนิก ใช้เวลาของฉัน? ฉันเพิ่งยกโค้ดของคุณไปไว้ในยูทิลมิกซ์อิน
javadba

2

ตอนนี้คุณสามารถทำสิ่งต่อไปนี้ได้อย่างรวดเร็วซึ่งจะช่วยให้คุณได้รับวัตถุประสงค์ -c เล็กน้อย if nil else

if textfieldDate.text?.isEmpty ?? true {

}

2

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

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}

xyz == nilนิพจน์ไม่ทำงาน แต่ใช้x != nilงานได้ ไม่รู้ทำไม.
Andrew

2

อีกวิธีหนึ่งนอกเหนือจากการใช้ifหรือguardคำสั่งในการทำการผูกทางเลือกคือการขยายOptionalด้วย:

extension Optional {

    func ifValue(_ valueHandler: (Wrapped) -> Void) {
        switch self {
        case .some(let wrapped): valueHandler(wrapped)
        default: break
        }
    }

}

ifValueได้รับการปิดและเรียกมันด้วยค่าเป็นอาร์กิวเมนต์เมื่อทางเลือกไม่ใช่ศูนย์ ใช้วิธีนี้:

var helloString: String? = "Hello, World!"

helloString.ifValue {
    print($0) // prints "Hello, World!"
}

helloString = nil

helloString.ifValue {
    print($0) // This code never runs
}

คุณควรใช้ifหรือguardอย่างไรก็ตามวิธีการเหล่านี้เป็นวิธีการธรรมดาที่สุด (ที่คุ้นเคย) ที่โปรแกรมเมอร์ Swift ใช้


2

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

if let _ = xyz {
    // something that should only happen if xyz is not nil
}

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


1

นอกจากนี้คุณสามารถใช้ Nil-Coalescing Operator

nil-coalescing operator(a ?? b ) unwraps ตัวเลือกaถ้ามันมีaค่าหรือผลตอบแทนที่aคุ้มค่าเริ่มต้นbถ้ามีa nilนิพจน์ a เป็นประเภททางเลือกเสมอ การแสดงออกต้องตรงกับชนิดที่เก็บไว้ภายในba

let value = optionalValue ?? defaultValue

หากoptionalValueเป็นnilเช่นนั้นระบบจะกำหนดค่าให้โดยอัตโนมัติdefaultValue


ฉันชอบมันเพราะมันมีตัวเลือกที่ง่ายในการระบุค่าเริ่มต้น
thowa

0
var xyz : NSDictionary?

// case 1:
xyz = ["1":"one"]
// case 2: (empty dictionary)
xyz = NSDictionary() 
// case 3: do nothing

if xyz { NSLog("xyz is not nil.") }
else   { NSLog("xyz is nil.")     }

การทดสอบนี้ได้ผลตามที่คาดไว้ในทุกกรณี BTW, ()คุณไม่จำเป็นต้องวงเล็บ


2
กรณี OP if (xyz != nil)มีความกังวลเกี่ยวกับการเป็น:
zaph

0

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

if xyz != nil && xyz! == "some non-nil value" {

}

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

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