การจัดการข้อผิดพลาดในภาษา Swift


190

ฉันไม่ได้อ่าน Swift มากเกินไป แต่สิ่งหนึ่งที่ฉันสังเกตเห็นคือไม่มีข้อยกเว้น ดังนั้นพวกเขาจะจัดการข้อผิดพลาดใน Swift อย่างไร มีใครพบสิ่งที่เกี่ยวข้องกับการจัดการข้อผิดพลาดหรือไม่?


1
ฉันพบข้อความแสดงข้อผิดพลาดเหมือนกับ Obj-C: o
Arbitur

13
@ Arbitur วิธี segfault เก่าดีหรือไม่
peko

สร้าง NSTimer ในสวิฟท์และเมื่อผมสะกดฟังก์ชั่นมันกระแทกและให้ฉันข้อผิดพลาดบอกว่ามันไม่สามารถหาวิธีการที่ :)
Arbitur

3
คุณสามารถเพิ่มการสนับสนุน try-catch สำหรับ Swift โดยทำตามคำแนะนำในบทความนี้: medium.com/@_willfalcon/adding-try-catch-to-swift-71ab27bcb5b8
William Falcon

@peko คุณจัดการ segfault ใน Swift อย่างไร ฉันไม่คิดว่ามันเป็นไปได้ ณ ตอนนี้ซึ่งน่าเศร้าที่ทำให้เกิดข้อผิดพลาดบางอย่างที่ไม่สามารถกู้คืนได้
Orlin Georgiev

คำตอบ:


148

สวิฟท์ 2 และ 3

ทุกอย่างเปลี่ยนไปเล็กน้อยใน Swift 2 เนื่องจากมีกลไกการจัดการข้อผิดพลาดใหม่ซึ่งค่อนข้างคล้ายกับข้อยกเว้น แต่มีรายละเอียดแตกต่างกัน

1. การระบุความเป็นไปได้ของข้อผิดพลาด

หากฟังก์ชัน / เมธอดต้องการระบุว่าอาจเกิดข้อผิดพลาดควรมีthrowsคำหลักเช่นนี้

func summonDefaultDragon() throws -> Dragon

หมายเหตุ: ไม่มีข้อกำหนดสำหรับประเภทของข้อผิดพลาดที่จริง ๆ แล้วฟังก์ชันสามารถโยนได้ การประกาศนี้ระบุเพียงว่าฟังก์ชั่นสามารถส่งตัวอย่างของการใช้งาน ErrorType หรือการโยนไม่ได้เลย

2. เรียกใช้ฟังก์ชันที่อาจทำให้เกิดข้อผิดพลาด

ในการเรียกใช้ฟังก์ชันคุณต้องใช้คำหลักลองเช่นนี้

try summonDefaultDragon()

โดยปกติบรรทัดนี้ควรเป็นบล็อก do-catch ปัจจุบันเช่นนี้

do {
    let dragon = try summonDefaultDragon() 
} catch DragonError.dragonIsMissing {
    // Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
    // Other specific-case error-handlng
} catch {
    // Catch all error-handling
}

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

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

func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

หรือคุณสามารถเรียกใช้ฟังก์ชันการขว้างปาโดยใช้try?:

let dragonOrNil = try? summonDefaultDragon()

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

ซึ่งหมายความว่าคุณสามารถรวมtry?กับข้อความที่เป็นประโยชน์เช่น:

if let dragon = try? summonDefaultDragon()

หรือ

guard let dragon = try? summonDefaultDragon() else { ... }

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

let dragon = try! summonDefaultDragon()

หากฟังก์ชั่นเกิดข้อผิดพลาดจริง ๆ คุณจะได้รับข้อผิดพลาดรันไทม์ในแอปพลิเคชันของคุณและแอปพลิเคชันจะยุติ

3. ทิ้งข้อผิดพลาด

เพื่อที่จะโยนข้อผิดพลาดคุณใช้การโยนคำหลักเช่นนี้

throw DragonError.dragonIsMissing

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

enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

ความแตกต่างที่สำคัญระหว่างกลไกข้อผิดพลาด Swift 2 & 3 ใหม่และข้อยกเว้นสไตล์ Java / C # / C ++ มีดังนี้:

  • ไวยากรณ์แตกต่างกันเล็กน้อย: do-catch+ try+ defervs ดั้งเดิมtry-catch-finallyไวยากรณ์
  • การจัดการข้อยกเว้นมักเกิดเวลาดำเนินการสูงกว่าในเส้นทางยกเว้นมากกว่าในเส้นทางสำเร็จ นี่ไม่ใช่กรณีที่มีข้อผิดพลาด Swift 2.0 ซึ่งเส้นทางความสำเร็จและเส้นทางข้อผิดพลาดมีค่าใช้จ่ายประมาณเดียวกัน
  • รหัสการโยนข้อผิดพลาดทั้งหมดจะต้องประกาศในขณะที่ข้อยกเว้นอาจถูกโยนจากที่ใดก็ได้ ข้อผิดพลาดทั้งหมดคือ "ข้อยกเว้นที่ตรวจสอบแล้ว" ในระบบเรียกชื่อ Java อย่างไรก็ตามตรงกันข้ามกับ Java คุณไม่ได้ระบุข้อผิดพลาดที่อาจเกิดขึ้น
  • ข้อยกเว้นที่รวดเร็วไม่สามารถใช้งานได้กับข้อยกเว้นของ ObjC do-catchบล็อกของคุณจะไม่จับ NSException ใด ๆ และในทางกลับกันคุณต้องใช้ ObjC
  • ข้อยกเว้นอย่างรวดเร็วเข้ากันได้กับNSErrorระเบียบวิธีการCocoa ของการส่งคืนอย่างใดอย่างหนึ่งfalse(สำหรับBoolฟังก์ชั่นการกลับมา) หรือnil(สำหรับAnyObjectฟังก์ชั่นการกลับมา) และผ่านNSErrorPointerกับรายละเอียดข้อผิดพลาด

ในฐานะที่เป็นน้ำตาล syntatic พิเศษเพื่อลดความผิดพลาดในการจัดการมีสองแนวคิดเพิ่มเติม

  • การกระทำที่รอการตัดบัญชี (ใช้ deferคำหลัก) ซึ่งช่วยให้คุณได้รับผลเช่นเดียวกับในที่สุดบล็อกใน Java / C # / ฯลฯ
  • คำสั่งยาม (ใช้guardคำหลัก) ซึ่งช่วยให้คุณเขียนน้อยกว่าถ้า / อื่น ๆ รหัสกว่าในการตรวจสอบข้อผิดพลาดปกติ / รหัสการส่งสัญญาณ

สวิฟท์ 1

ข้อผิดพลาดรันไทม์:

ตามที่ Leandros แนะนำสำหรับการจัดการข้อผิดพลาดรันไทม์ (เช่นปัญหาการเชื่อมต่อเครือข่าย, การแยกวิเคราะห์ข้อมูล, การเปิดไฟล์, ฯลฯ ) คุณควรใช้NSErrorอย่างที่เคยทำใน ObjC, เพราะ Foundation, AppKit, UIKit, ฯลฯ รายงานข้อผิดพลาดด้วยวิธีนี้ ดังนั้นมันจึงเป็นโครงงานมากกว่าเรื่องภาษา

อีกรูปแบบที่ใช้บ่อยคือความสำเร็จของตัวแยก / บล็อกความล้มเหลวเช่นใน AFNetworking:

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
    success: { (NSURLSessionDataTask) -> Void in
        println("Success")
    },
    failure:{ (NSURLSessionDataTask, NSError) -> Void in
        println("Failure")
    })

ยังคงบล็อกความล้มเหลวที่ได้รับบ่อยNSErrorเช่นอธิบายข้อผิดพลาด

ข้อผิดพลาดของโปรแกรมเมอร์:

สำหรับข้อผิดพลาดของโปรแกรมเมอร์ (เช่นไม่สามารถเข้าถึงองค์ประกอบอาร์เรย์ได้อาร์กิวเมนต์ที่ไม่ถูกต้องถูกส่งผ่านไปยังการเรียกใช้ฟังก์ชัน ฯลฯ ) คุณใช้ข้อยกเว้นใน ObjC ดูเหมือนว่าภาษา Swift จะไม่สนับสนุนภาษาใด ๆ สำหรับข้อยกเว้น (เช่นthrow, catchคำหลัก ฯลฯ ) อย่างไรก็ตามตามเอกสารแนะนำว่ามันกำลังทำงานอยู่ในรันไทม์เดียวกับ ObjC และดังนั้นคุณยังสามารถโยนNSExceptionsดังนี้:

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

คุณไม่สามารถจับพวกมันด้วย Swift ล้วนๆได้แม้ว่าคุณจะสามารถจับข้อยกเว้นในรหัส ObjC

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


20
"ปัญหาการเชื่อมต่อเครือข่าย" และ "การเปิดไฟล์" โดยใช้ Cocoa API (NSFileHandle) สามารถส่งข้อยกเว้นที่จำเป็นต้องได้รับการตรวจจับ โดยไม่มีข้อยกเว้นใน Swift คุณต้องใช้ส่วนนี้ของโปรแกรมของคุณใน Objective-C หรือทำงานทั้งหมดของคุณโดยใช้ BSD C APIs (ทั้งสองอย่างนี้ทำงานได้ไม่ดี) ดูเอกสารประกอบสำหรับ NSFileHandle.writeData สำหรับข้อมูลเพิ่มเติม ... developer.apple.com/library/ios/documentation/Cocoa/Reference/ ...... :
Matt Gallagher

5
ไม่มีข้อยกเว้นการจัดการหมายถึงการสร้างวัตถุสองขั้นตอนที่มีปัญหาโดยธรรมชาติทั้งหมด ดูstroustrup.com/except.pdf
Phil

2
fatalError(...)เป็นเช่นเดียวกับดี
holex

8
เท่าที่ฉันชอบสวิฟท์ผมคิดว่านี่เป็นทางเลือกที่ภัยพิบัติและต้องมีรสชาติของบางส่วนของผลกระทบที่พวกเขากำลังเล่นกับไฟด้วยการละเลยนี้ ...
ร็อบ

2
ใช่ตอนนี้มีการใช้ข้อยกเว้นที่ตรวจสอบอย่าง จำกัด เพราะพบว่าการบังคับให้โปรแกรมเมอร์จับข้อยกเว้นพวกเขามีความหวังเพียงเล็กน้อยในการกู้คืนจากรหัสมลพิษในหลักการความรับผิดชอบเดียว คลาสระดับโดเมนไม่ต้องการจัดการกับข้อยกเว้นเลเยอร์โครงสร้างพื้นฐาน ดังนั้นตอนนี้ข้อยกเว้นที่ไม่ผ่านการตรวจสอบมีแนวโน้มที่จะได้รับความนิยมและผู้สนใจสามารถจับพวกเขาได้หากเหมาะสม . Checked = กู้คืนได้แน่นอน ไม่ได้ตรวจสอบ = ไม่ใช่ / อาจกู้คืนได้
Jasper Blues

69

อัปเดต 9 มิถุนายน 2558 - สำคัญมาก

สวิฟท์ 2.0 มาพร้อมกับtry, throwและcatchคำหลักและที่น่าตื่นเต้นที่สุดคือ:

Swift แปลวิธีการ Objective-C โดยอัตโนมัติซึ่งสร้างข้อผิดพลาดเป็นวิธีการที่ทำให้เกิดข้อผิดพลาดตามฟังก์ชันการจัดการข้อผิดพลาดดั้งเดิมของ Swift

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

ข้อความที่ตัดตอนมาจาก: Apple Inc. “ การใช้ Swift กับ Cocoa และ Objective-C (Swift 2 Prerelease)” iBooks

ตัวอย่าง: (จากหนังสือ)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

ค่าที่เทียบเท่าใน swift จะเป็น:

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

โยนข้อผิดพลาด:

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

จะถูกเผยแพร่ไปยังผู้โทรโดยอัตโนมัติ:

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

จากหนังสือ Apple ภาษา Swift Programming Language ดูเหมือนว่าควรจัดการข้อผิดพลาดโดยใช้ enum

นี่คือตัวอย่างจากหนังสือ

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

จาก: Apple Inc. “ ภาษาการเขียนโปรแกรม Swift” iBooks https://itun.es/br/jEUH0.l

ปรับปรุง

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

นี่คือยืดจากหนังสือ:

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

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

ข้อความที่ตัดตอนมาจาก: Apple Inc. “ การใช้ Swift กับ Cocoa และ Objective-C” iBooks https://itun.es/br/1u3-0.l


และหนังสือยังสนับสนุนให้คุณใช้รูปแบบข้อผิดพลาดโกโก้จาก Objective-C (NSError Object)

การรายงานข้อผิดพลาดใน Swift เป็นไปตามรูปแบบเดียวกับที่ทำใน Objective-C พร้อมสิทธิประโยชน์เพิ่มเติมในการนำเสนอค่าตอบแทนที่เป็นทางเลือก ในกรณีที่ง่ายที่สุดคุณคืนค่า Bool จากฟังก์ชันเพื่อระบุว่าสำเร็จหรือไม่ เมื่อคุณต้องการรายงานสาเหตุของข้อผิดพลาดคุณสามารถเพิ่มฟังก์ชัน NSError out พารามิเตอร์ประเภท NSErrorPointer ประเภทนี้เทียบเท่ากับ NSError ** ของ Objective-C โดยมีความปลอดภัยของหน่วยความจำเพิ่มเติมและการพิมพ์เสริม คุณสามารถใช้คำนำหน้า & โอเปอเรเตอร์เพื่อส่งผ่านการอ้างอิงไปยังประเภท NSError ที่เป็นตัวเลือกเป็นวัตถุ NSErrorPointer ดังที่แสดงในรายการรหัสด้านล่าง

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

ข้อความที่ตัดตอนมาจาก: Apple Inc. “ การใช้ Swift กับ Cocoa และ Objective-C” iBooks https://itun.es/br/1u3-0.l


สำหรับคำสั่งสุดท้ายมันควรจะเป็น: do {ลอง myString.writeToFile (เส้นทาง, อะตอม: จริง, การเข้ารหัส: NSUTF8StringEncoding)} จับ catch ให้เกิดข้อผิดพลาดเป็น NSError {print (error)}
Jacky

1
@ แจ็คกี้ใช่นั่นเป็นจริงสำหรับ swift 2.0 แม้ว่าคำตอบนี้ถูกเขียนขึ้นก่อนการเปิดตัว swift 2.0 ฉันได้อัปเดตคำตอบเพื่อแสดงข้อผิดพลาดวิธีการจัดการแบบใหม่ใน swift 2.0 ฉันคิดว่าจะปล่อยให้วิธีนี้เป็นข้อมูลอ้างอิง แต่ฉันจะพิจารณาอัปเดตคำตอบทั้งหมดเพื่อใช้อย่างรวดเร็ว 2.0 เท่านั้น
Guilherme Torres Castro

12

ไม่มีข้อยกเว้นใน Swift ซึ่งคล้ายกับวิธีการของ C-Objective

ในการพัฒนาคุณสามารถใช้assertเพื่อตรวจจับข้อผิดพลาดใด ๆ ที่อาจปรากฏขึ้นและต้องแก้ไขก่อนที่จะผลิต

NSErrorวิธีแบบดั้งเดิมไม่มีการเปลี่ยนแปลงคุณส่งNSErrorPointerซึ่งจะได้รับการเติม

ตัวอย่างย่อ:

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}

6
สิ่งนี้เพิ่มขึ้นสองคำถาม: จะเกิดอะไรขึ้นเมื่อรหัส ObjC ที่เราเรียกจาก Swift ส่งข้อยกเว้นออกมาจริงหรือไม่และ NSError เป็นวัตถุข้อผิดพลาดสากลของเราเหมือนใน ObjC หรือไม่
MDJ

1
มันเป็นเพียงความจริงของชีวิตกับ Swift ที่ initializers ทำไม่ได้หรือไม่ล้มเหลว?
Phil

11
การจัดการข้อยกเว้นดูค่อนข้างสกปรก
Tash Pemhiwa

27
ใช่ใครต้องการข้อยกเว้นเมื่อคุณสามารถผิดพลาดได้บ้าง หรือใส่ NSError ** เป็นอาร์กิวเมนต์ในทุกฟังก์ชั่นที่คุณประกาศ? เพื่อให้ทุกf();g();กลายเป็นf(&err);if(err) return;g(&err);if(err) return;เดือนแรกแล้วมันก็กลายเป็นf(nil);g(nil);hopeToGetHereAlive();
hariseldon78

2
คำตอบนี้ล้าสมัยแล้ว (ปัจจุบัน Swift รองรับข้อยกเว้น) และไม่ถูกต้อง (Objective-C รองรับข้อยกเว้น
Rog

11

คำแนะนำ 'Swift Way' คือ:

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

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

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

นอกจากนี้ยังง่ายต่อการเพิ่มสถานที่ลองอีกครั้ง:

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

รายชื่อของ TryBool คือ:

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

คุณสามารถเขียนคลาสที่คล้ายกันเพื่อทดสอบค่าที่ส่งคืนซึ่งเป็นทางเลือกแทนค่า Bool:

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

รุ่น TryOptional บังคับใช้ชนิดส่งคืนที่ไม่ใช่ตัวเลือกซึ่งทำให้การเขียนโปรแกรมที่ตามมาง่ายขึ้นเช่น 'Swift Way:

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

ใช้ TryOptional:

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

หมายเหตุการแกะอัตโนมัติ


7

แก้ไข:ถึงแม้ว่าคำตอบนี้จะได้ผล แต่ก็มีอะไรมากกว่าคำแปลของ Objective-C ใน Swift มันล้าสมัยจากการเปลี่ยนแปลงใน Swift 2.0 คำตอบของ Guilherme Torres Castro ด้านบนเป็นคำแนะนำที่ดีมากเกี่ยวกับวิธีจัดการข้อผิดพลาดใน Swift VOS

มันคิดออกเล็กน้อย แต่ฉันคิดว่าฉัน sussed มัน ดูเหมือนว่าน่าเกลียด ไม่มีอะไรยิ่งไปกว่าสกินผอมกว่ารุ่น Objective-C

การเรียกฟังก์ชันด้วยพารามิเตอร์ NSError ...

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

กำลังเขียนฟังก์ชันที่รับพารามิเตอร์ข้อผิดพลาด ...

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}


5

เสื้อคลุมพื้นฐานรอบ C วัตถุประสงค์ที่ให้คุณลองจับคุณสมบัติ https://github.com/williamFalcon/SwiftTryCatch

ใช้เช่น:

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})

ความคิดที่ดี. แต่ผู้ที่ตัดสินใจใช้สิ่งนี้จะต้องระลึกไว้เสมอว่าวัตถุที่จัดสรรในบล็อกการลองไม่ได้ถูกจัดสรรคืนเมื่อมีการโยนข้อยกเว้น สิ่งนี้อาจทำให้เกิดปัญหาของวัตถุซอมบี้และการใช้งานของ RAII ทุกครั้งจะถูกบุกรุก (ปลดล็อกอัตโนมัติ, อัตโนมัติ - sql-commit, อัตโนมัติ - ย้อนกลับ ... -) c ++ อาจช่วยเราด้วยรูปแบบ "runAtExit" บางรูปแบบใช่หรือไม่
hariseldon78

ปรับปรุง: ฉันเพิ่งพบว่ามีการตั้งค่าสถานะในเสียงดังกราวเพื่อเปิดใช้งานการเปิดตัวของวัตถุที่ยกเว้นการขว้างปา: -fobjc-arc-ข้อยกเว้น ฉันต้องลองถ้ามันยังใช้งานได้กับเวอร์ชันที่ถูกห่อไว้ (ฉันคิดว่ามันควรจะเป็น)
hariseldon78

หากคุณใช้ตัวเลือกนี้โปรดทราบว่าขนาดของรหัสจะเพิ่มขึ้นเนื่องจากคอมไพเลอร์จำเป็นต้องสร้างรหัส semi-exception-safe นอกจากนี้: การใช้คุณสมบัติคอมไพเลอร์เช่นนี้อาจไม่ใช่ความคิดที่ดีที่สุด ข้อยกเว้นสำหรับข้อผิดพลาดของโปรแกรมเมอร์เท่านั้นดังนั้นการใช้ตัวเลือกคอมไพเลอร์นั้นเพื่อบันทึกหน่วยความจำเล็กน้อยระหว่างการพัฒนาไม่คุ้มค่า หากคุณมีข้อยกเว้นในรหัสการผลิตคุณควรจัดการกับสิ่งที่ทำให้เกิดข้อยกเว้นเหล่านั้นตั้งแต่แรก
Christian Kienle

1
อาจมีสถานการณ์ที่อยู่นอกเหนือการควบคุมของคุณ ตัวอย่างเช่นการแยก json ในรูปแบบที่ไม่ถูกต้อง
William Falcon

3

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

การจัดการรูปแบบข้อผิดพลาด: รูปแบบการจัดการข้อผิดพลาดใหม่ในสวิฟท์ 2.0 ทันทีจะรู้สึกธรรมชาติที่คุ้นเคยลองโยนและจับคำหลัก เหนือสิ่งอื่นใดมันถูกออกแบบมาเพื่อทำงานอย่างสมบูรณ์แบบกับ Apple SDK และ NSError อันที่จริง NSError เป็นไปตาม ErrorType ของ Swift แน่นอนคุณจะต้องการชมเซสชัน WWDC เกี่ยวกับมีอะไรใหม่ใน Swift เพื่อฟังข้อมูลเพิ่มเติม

เช่น :

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}

3

ในฐานะที่เป็น Guilherme Torres คาสโตรกล่าวว่าในสวิฟท์ 2.0 try, catch,doสามารถนำมาใช้ในการเขียนโปรแกรม

ยกตัวอย่างเช่นใน CoreData เรียกวิธีการข้อมูลแทนการใส่&errorเป็นพารามิเตอร์เข้ามาmanagedContext.executeFetchRequest(fetchRequest, error: &error)ตอนนี้เราจะต้องใช้การใช้งานmanagedContext.executeFetchRequest(fetchRequest)แล้วจัดการกับข้อผิดพลาดที่มีtry, catch( แอปเปิ้ลเอกสารเชื่อมโยง )

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

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

เทคนิคการจัดการข้อผิดพลาดแฟนซีเพิ่มเติมสามารถพบได้ใน

มีอะไรใหม่ในสวิฟท์ (2015 เซสชัน 106 28m30s)



1

lib ที่ดีและง่ายต่อการจัดการข้อยกเว้น: TryCatchFinally-Swift

มันคล้ายกับคุณสมบัติข้อยกเว้นของ C วัตถุประสงค์

ใช้แบบนี้:

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}

ฉันได้เพิ่มตัวอย่าง :)
มอ Holmgaard

มันอาจคุ้มค่าที่จะกล่าวถึงความคิดเห็นของผู้เขียน: "คำเตือน: นี่คือแฮ็คเพื่อความสนุกและความชั่วร้ายต่อต้านการทดลองที่จะใช้มัน"
jbat100

1

การเริ่มต้นด้วย Swift 2 อย่างที่คนอื่น ๆ พูดถึงแล้วการจัดการข้อผิดพลาดทำได้ดีที่สุดผ่านการใช้ do / try / catch และ ErrorType enums วิธีนี้ใช้ได้ดีสำหรับวิธีการแบบซิงโครนัส แต่ต้องใช้ความฉลาดเล็กน้อยสำหรับการจัดการข้อผิดพลาดแบบอะซิงโครนัส

บทความนี้มีแนวทางที่ดีในการแก้ไขปัญหานี้:

https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/

เพื่อสรุป:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

จากนั้นการเรียกใช้เมธอดด้านบนจะเป็นดังนี้:

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

ดูเหมือนว่าจะสะอาดกว่าการแยก callHandler errorHandler แยกต่างหากซึ่งเป็นวิธีจัดการสิ่งนี้ก่อน Swift 2


0

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


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