Swift 2: การโทรสามารถโยนได้ แต่ไม่มีการทำเครื่องหมายด้วย 'ลอง' และข้อผิดพลาดจะไม่ถูกจัดการ


161

หลังจากฉันติดตั้ง Xcode 7 เบต้าและแปลงรหัส swift เป็น Swift 2 ฉันมีปัญหากับรหัสที่ฉันไม่สามารถเข้าใจได้ ฉันรู้ว่า Swift 2 เป็นของใหม่ดังนั้นฉันจึงค้นหาและคิดออกเนื่องจากไม่มีอะไรเกี่ยวกับมันฉันควรเขียนคำถาม

นี่คือข้อผิดพลาด:

การโยนสามารถโยนได้ แต่ไม่มีการทำเครื่องหมายด้วย 'ลอง' และข้อผิดพลาดจะไม่ถูกจัดการ

รหัส:

func deleteAccountDetail(){
        let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
        let request = NSFetchRequest()
        request.entity = entityDescription

        //The Line Below is where i expect the error
        let fetchedEntities = self.Context!.executeFetchRequest(request) as! [AccountDetail]

        for entity in fetchedEntities {
        self.Context!.deleteObject(entity)
        }

        do {
            try self.Context!.save()
        } catch _ {
        }

    }

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

คำตอบ:


168

คุณต้องจับข้อผิดพลาดเช่นเดียวกับที่คุณกำลังทำอยู่กับการsave()โทรของคุณและเนื่องจากคุณจัดการกับข้อผิดพลาดหลายอย่างที่นี่คุณสามารถtryโทรหลายครั้งตามลำดับในบล็อก do-catch เดียวเช่น:

func deleteAccountDetail() {
    let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
    let request = NSFetchRequest()
    request.entity = entityDescription

    do {
        let fetchedEntities = try self.Context!.executeFetchRequest(request) as! [AccountDetail]

        for entity in fetchedEntities {
            self.Context!.deleteObject(entity)
        }

        try self.Context!.save()
    } catch {
        print(error)
    }
}

หรือตามที่ @ bames53 ชี้ให้เห็นในความคิดเห็นด้านล่างก็มักจะดีกว่าที่จะไม่จับข้อผิดพลาดที่ถูกโยน คุณสามารถทำเครื่องหมายวิธีthrowsนั้นtryเพื่อเรียกวิธีการนั้น ตัวอย่างเช่น:

func deleteAccountDetail() throws {
    let entityDescription = NSEntityDescription.entityForName("AccountDetail", inManagedObjectContext: Context!)
    let request = NSFetchRequest()

    request.entity = entityDescription

    let fetchedEntities = try Context.executeFetchRequest(request) as! [AccountDetail]

    for entity in fetchedEntities {
        self.Context!.deleteObject(entity)
    }

    try self.Context!.save()
}

นี่ช่วยฉันคิดออกขอบคุณ
Farhad

5
จริงๆแล้วมันไม่จำเป็นที่จะต้องมีการยกเว้นที่นี่ มันเป็นไปได้ที่จะเพียงแค่เพิ่มคำหลักเพื่อเรียกใช้ฟังก์ชันและการประกาศฟังก์ชั่นนี้เป็นtry หรือถ้าคุณได้รับประกันว่าการทำงานจะไม่โยนสำหรับการป้อนข้อมูลให้คุณสามารถใช้func deleteAccountDetail() throw try!
bames53

4
ฉันนำสิ่งนี้มาใช้กับ nitpick ไม่ได้ แต่เพราะจริงๆแล้วมันค่อนข้างสำคัญที่จะต้องจัดการข้อผิดพลาดตามสถานที่ส่วนใหญ่ที่มีข้อยกเว้นเกิดขึ้นไม่ได้รับการยกเว้น มีสถานที่สามประเภทที่เหมาะสมสำหรับการยกเว้น ในที่อื่น ๆ โค้ดไม่ควรจัดการกับข้อยกเว้นอย่างชัดเจนและควรพึ่งพาการdeinit()โทรโดยปริยายเพื่อทำการล้างข้อมูล (เช่น RAII) หรือใช้เป็นครั้งคราวdeferเพื่อทำความสะอาดเฉพาะกิจ เพื่อดูข้อยกเว้นเพิ่มเติม (ที่พูดถึง C ++ แต่มีหลักการพื้นฐานที่ใช้กับข้อยกเว้นของ Swift เช่นกัน)
bames53

แต่คุณจะเรียกใช้ฟังก์ชันอย่างไร ถ้าฉันไปด้วย @ bames53 วิธี?
Farhad

1
@NickMoore สิ่งที่นักพัฒนา Swift เลือกที่จะเรียกพวกเขาไม่ได้สร้างความแตกต่างในสิ่งที่พวกเขาเป็นจริง ระบบการจัดการข้อผิดพลาดใหม่ของ Swift เป็นการใช้งานข้อยกเว้นเนื่องจากคำดังกล่าวมักใช้กันทั่วไปในส่วนที่เหลือของอุตสาหกรรม
bames53

41

เมื่อโทรฟังก์ชั่นที่มีการประกาศที่มีthrowsในสวิฟท์, คุณต้องใส่คำอธิบายประกอบเว็บไซต์ฟังก์ชั่นการโทรด้วยหรือtry try!ตัวอย่างเช่นกำหนดฟังก์ชั่นการขว้างปา:

func willOnlyThrowIfTrue(value: Bool) throws {
  if value { throw someError }
}

ฟังก์ชั่นนี้สามารถเรียกว่าชอบ:

func foo(value: Bool) throws {
  try willOnlyThrowIfTrue(value)
}

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

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

func bar() {
  try! willOnlyThrowIfTrue(false)
}

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

try!มีผลบังคับใช้ขณะใช้งานจริง: หากคุณใช้งานtry!และฟังก์ชั่นจะจบลงด้วยการโยนการดำเนินการของโปรแกรมของคุณจะถูกยกเลิกด้วยข้อผิดพลาดรันไทม์

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

func baz(value: Bool) throws {

  var filePath = NSBundle.mainBundle().pathForResource("theFile", ofType:"txt")
  var data = NSData(contentsOfFile:filePath)

  try willOnlyThrowIfTrue(value)

  // data and filePath automatically cleaned up, even when an exception occurs.
}

หากมีเหตุผลอะไรก็ตามที่คุณมีรหัสทำความสะอาดที่ต้องทำงาน แต่ไม่ได้อยู่ในฟังก์ชั่นที่คุณสามารถใช้deinit()defer

func qux(value: Bool) throws {
  defer {
    print("this code runs when the function exits, even when it exits by an exception")
  }

  try willOnlyThrowIfTrue(value)
}

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

อย่างไรก็ตามรหัสระดับที่สูงขึ้นควรรู้ว่าต้องทำอะไรในกรณีที่มีข้อผิดพลาด ดังนั้นข้อยกเว้นอนุญาตให้เกิดข้อผิดพลาดเฉพาะในการทำให้ฟองสบู่เกิดขึ้นจากจุดเริ่มต้นของสิ่งที่สามารถจัดการได้

การจัดการข้อยกเว้นทำได้ผ่านcatchข้อความ

func quux(value: Bool) {
  do {
    try willOnlyThrowIfTrue(value)
  } catch {
    // handle error
  }
}

คุณสามารถมีหลายคำสั่งที่จับได้แต่ละข้อยกเว้นที่แตกต่างกัน

  do {
    try someFunctionThatThowsDifferentExceptions()
  } catch MyErrorType.errorA {
    // handle errorA
  } catch MyErrorType.errorB {
    // handle errorB
  } catch {
    // handle other errors
  }

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการปฏิบัติที่ดีที่สุดมีข้อยกเว้นให้ดูhttp://exceptionsafecode.com/ มันมีจุดประสงค์เฉพาะที่ C ++ แต่หลังจากตรวจสอบโมเดลการยกเว้นของ Swift ฉันเชื่อว่าพื้นฐานนั้นใช้ได้กับ Swift เช่นกัน

สำหรับรายละเอียดเกี่ยวกับไวยากรณ์และการจัดการข้อผิดพลาดรุ่น Swift, ดูหนังสือวิฟท์ Programming Language (สวิฟท์ 2 ก่อนวางจำหน่าย)


โดยทั่วไปจับตัวเองสามารถจัดการข้อผิดพลาดหรือไม่ หรือฟังก์ชั่นอินพุต
Farhad

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

ฉันมีข้อผิดพลาดอย่างหนึ่งที่ฉันเงียบไม่เข้าใจคุณช่วยฉันได้ไหม Invalid conversion from throwing function of type '() throws -> _' to non-throwing function type '(NSData?, NSURLResponse?, NSError?) -> Void'
Farhad

@BrianS ดูเหมือนว่าคุณกำลังใช้งานฟังก์ชั่นที่มีลายเซ็นไม่ถูกต้องที่ไหนสักแห่ง มีบางสิ่งที่คาดว่าจะได้รับฟังก์ชั่นที่รับNSData?, NSURLResponse?, NSError?เป็นอาร์กิวเมนต์ แต่คุณให้ฟังก์ชันที่ไม่มีอาร์กิวเมนต์ใด ๆ
bames53

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