Tôi chưa đọc quá nhiều về Swift nhưng có một điều tôi nhận thấy là không có ngoại lệ. Vậy làm thế nào để họ xử lý lỗi trong Swift? Có ai tìm thấy bất cứ điều gì liên quan đến xử lý lỗi?
Tôi chưa đọc quá nhiều về Swift nhưng có một điều tôi nhận thấy là không có ngoại lệ. Vậy làm thế nào để họ xử lý lỗi trong Swift? Có ai tìm thấy bất cứ điều gì liên quan đến xử lý lỗi?
Câu trả lời:
Mọi thứ đã thay đổi một chút trong Swift 2, vì có một cơ chế xử lý lỗi mới, có phần giống với ngoại lệ hơn nhưng khác về chi tiết.
Nếu hàm / phương thức muốn chỉ ra rằng nó có thể gây ra lỗi, thì nó nên chứa throws
từ khóa như thế này
func summonDefaultDragon() throws -> Dragon
Lưu ý: không có thông số kỹ thuật cho loại lỗi mà hàm thực sự có thể ném. Tuyên bố này chỉ đơn giản nói rằng hàm có thể ném một thể hiện của bất kỳ loại ErrorType nào hoặc hoàn toàn không ném.
Để gọi hàm, bạn cần sử dụng từ khóa thử, như thế này
try summonDefaultDragon()
dòng này thường có mặt khối bắt-bắt như thế này
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
}
Lưu ý: mệnh đề bắt sử dụng tất cả các tính năng mạnh mẽ của khớp mẫu Swift để bạn rất linh hoạt ở đây.
Bạn có thể quyết định tuyên truyền lỗi, nếu bạn đang gọi một hàm ném từ một hàm được đánh dấu bằng throws
từ khóa:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
Ngoài ra, bạn có thể gọi chức năng ném bằng cách sử dụng try?
:
let dragonOrNil = try? summonDefaultDragon()
Bằng cách này, bạn có thể nhận được giá trị trả về hoặc không, nếu có lỗi xảy ra. Sử dụng theo cách này bạn không nhận được đối tượng lỗi.
Điều đó có nghĩa là bạn cũng có thể kết hợp try?
với các tuyên bố hữu ích như:
if let dragon = try? summonDefaultDragon()
hoặc là
guard let dragon = try? summonDefaultDragon() else { ... }
Cuối cùng, bạn có thể quyết định rằng bạn biết rằng lỗi sẽ không thực sự xảy ra (ví dụ: vì bạn đã kiểm tra là điều kiện tiên quyết) và sử dụng try!
từ khóa:
let dragon = try! summonDefaultDragon()
Nếu chức năng thực sự gây ra lỗi, thì bạn sẽ gặp lỗi thời gian chạy trong ứng dụng của mình và ứng dụng sẽ chấm dứt.
Để ném lỗi, bạn sử dụng từ khóa throw như thế này
throw DragonError.dragonIsMissing
Bạn có thể ném bất cứ thứ gì phù hợp với ErrorType
giao thức. Đối với những người mới bắt đầu NSError
tuân thủ giao thức này, nhưng bạn có thể muốn sử dụng enum dựa trên enum ErrorType
cho phép bạn nhóm nhiều lỗi liên quan, có khả năng với các phần dữ liệu bổ sung, như thế này
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Sự khác biệt chính giữa cơ chế lỗi Swift 2 & 3 mới và các ngoại lệ kiểu Java / C # / C ++ như sau:
do-catch
+ try
+ defer
so với truyền thốngtry-catch-finally
cú pháp .do-catch
Khối của bạn sẽ không bắt được bất kỳ NSException nào và ngược lại, bạn phải sử dụng ObjC.NSError
các quy ước của phương thức Ca cao về việc trả về false
(cho các Bool
hàm trả về) hoặc nil
(cho các AnyObject
hàm trả về) và chuyển NSErrorPointer
với các chi tiết lỗi.Là một đường tổng hợp thêm để giảm bớt xử lý lỗi, có hai khái niệm nữa
defer
từ khóa) cho phép bạn đạt được hiệu quả tương tự như các khối cuối cùng trong Java / C # / etcguard
từ khóa) cho phép bạn viết ít hơn mã if / khác so với mã kiểm tra / báo hiệu lỗi thông thường.Lỗi thời gian chạy:
Như Leandros đề xuất để xử lý các lỗi thời gian chạy (như các sự cố kết nối mạng, phân tích dữ liệu, mở tệp, v.v.), bạn nên sử dụng NSError
như bạn đã làm trong ObjC, vì Foundation, AppKit, UIKit, v.v ... báo cáo lỗi của họ theo cách này. Vì vậy, đó là điều khuôn khổ hơn điều ngôn ngữ.
Một mô hình thường xuyên khác đang được sử dụng là các khối thành công / thất bại như trong 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")
})
Vẫn là khối lỗi thường xuyên nhận được NSError
, mô tả lỗi.
Lỗi lập trình viên:
Đối với các lỗi lập trình viên (như truy cập ngoài giới hạn của phần tử mảng, các đối số không hợp lệ được truyền cho một lệnh gọi hàm, v.v.) bạn đã sử dụng các ngoại lệ trong ObjC. Swift ngôn ngữ dường như không có bất kỳ hỗ trợ ngôn ngữ cho trường hợp ngoại lệ (như throw
, catch
, vv từ khóa). Tuy nhiên, như tài liệu cho thấy nó đang chạy trên cùng thời gian chạy với ObjC, và do đó bạn vẫn có thể ném NSExceptions
như thế này:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
Bạn không thể bắt chúng trong Swift thuần túy, mặc dù bạn có thể chọn bắt ngoại lệ trong mã ObjC.
Các câu hỏi là liệu bạn có nên đưa ra ngoại lệ cho lỗi lập trình viên hay sử dụng các xác nhận như Apple gợi ý trong hướng dẫn ngôn ngữ.
fatalError(...)
giống là tốt.
Cập nhật ngày 9 tháng 6 năm 2015 - Rất quan trọng
Swift 2.0 đi kèm với try
, throw
và catch
từ khóa và thú vị nhất là:
Swift tự động dịch các phương thức Objective-C tạo ra lỗi thành các phương thức đưa ra lỗi theo chức năng xử lý lỗi gốc của Swift.
Lưu ý: Các phương thức sử dụng lỗi, chẳng hạn như các phương thức ủy nhiệm hoặc phương thức nhận xử lý hoàn thành với đối số NSError, không trở thành phương thức ném khi được nhập bởi Swift.
Trích từ: Apple Inc., Sử dụng Swift với Ca cao và Objective-C (Phát hành trước Swift 2). Sách điện tử.
Ví dụ: (từ cuốn sách)
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);
}
Tương đương trong swift sẽ là:
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)")
}
Ném một lỗi:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
Sẽ được tự động truyền tới người gọi:
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
Từ sách của Apple, Ngôn ngữ lập trình Swift có vẻ như lỗi nên được xử lý bằng enum.
Dưới đây là một ví dụ từ cuốn sách.
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)"
}
Từ: Apple Inc., Ngôn ngữ lập trình Swift. Sách điện tử. https://itun.es/br/jEUH0.l
Cập nhật
Từ sách tin tức của Apple, "Sử dụng Swift với Ca cao và Mục tiêu-C". Ngoại lệ thời gian chạy không xảy ra bằng cách sử dụng ngôn ngữ nhanh, vì vậy đó là lý do tại sao bạn không thử bắt. Thay vào đó bạn sử dụng Chaining tùy chọn .
Đây là một đoạn trong cuốn sách:
Ví dụ, trong danh sách mã bên dưới, các dòng đầu tiên và thứ hai không được thực thi vì thuộc tính độ dài và phương thức characterAtIndex: không tồn tại trên một đối tượng NSDate. Hằng số myLpm được suy ra là một Int tùy chọn và được đặt thành không. Bạn cũng có thể sử dụng câu lệnh if let let để hủy kết quả một cách có điều kiện kết quả của một phương thức mà đối tượng có thể không đáp ứng, như được hiển thị trên dòng ba
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
Trích từ: Apple Inc., sử dụng Swift với ca cao và khách quan-C. Sách điện tử. https://itun.es/br/1u3-0.l
Và các cuốn sách cũng khuyến khích bạn sử dụng mẫu lỗi ca cao từ Objective-C (NSError Object)
Báo cáo lỗi trong Swift tuân theo cùng một mẫu trong Objective-C, với lợi ích bổ sung là cung cấp các giá trị trả về tùy chọn. Trong trường hợp đơn giản nhất, bạn trả về giá trị Bool từ hàm để cho biết liệu nó có thành công hay không. Khi bạn cần báo cáo lý do lỗi, bạn có thể thêm vào hàm một tham số NSError ngoài loại NSErrorPulum. Loại này gần tương đương với NSError của Objective-C, với sự an toàn bộ nhớ bổ sung và gõ tùy chọn. Bạn có thể sử dụng tiền tố & toán tử để chuyển tham chiếu đến loại NSError tùy chọn dưới dạng đối tượng NSErrorPulum, như được hiển thị trong danh sách mã bên dưới.
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)")
}
}
Trích từ: Apple Inc., sử dụng Swift với ca cao và khách quan-C. Sách điện tử. https://itun.es/br/1u3-0.l
Không có trường hợp ngoại lệ nào trong Swift, tương tự như cách tiếp cận của Objective-C.
Trong quá trình phát triển, bạn có thể sử dụng assert
để bắt bất kỳ lỗi nào có thể xuất hiện và cần được sửa trước khi đi vào sản xuất.
Cách NSError
tiếp cận cổ điển không bị thay đổi, bạn gửi một cái NSErrorPointer
, được phổ biến.
Ví dụ ngắn gọn:
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)")
}
f();g();
trở thành f(&err);if(err) return;g(&err);if(err) return;
trong tháng đầu tiên, sau đó nó sẽ trở thànhf(nil);g(nil);hopeToGetHereAlive();
'Swift Way' được đề xuất là:
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
}
Tuy nhiên, tôi thích thử / bắt hơn vì tôi thấy dễ theo dõi hơn vì nó chuyển việc xử lý lỗi sang một khối riêng ở cuối, sự sắp xếp này đôi khi được gọi là "Đường dẫn vàng". May mắn bạn có thể làm điều này với đóng cửa:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
Ngoài ra, thật dễ dàng để thêm một cơ sở thử lại:
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
}
Danh sách cho TryBool là:
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)
}
}
Bạn có thể viết một lớp tương tự để kiểm tra giá trị trả về Tùy chọn thay vì giá trị 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)
}
}
Phiên bản TryOptional thực thi loại trả về không tùy chọn giúp cho việc lập trình tiếp theo dễ dàng hơn, ví dụ: '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
Sử dụng 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
}
Lưu ý tự động mở khóa.
Chỉnh sửa: Mặc dù câu trả lời này hoạt động, nhưng nó ít hơn so với Objective-C được phiên âm thành Swift. Nó đã bị lỗi thời bởi những thay đổi trong Swift 2.0. Câu trả lời của Guilherme Torres Castro ở trên là phần giới thiệu rất hay về cách xử lý lỗi ưa thích trong Swift. VOS
Phải mất một chút để tìm ra nó nhưng tôi nghĩ rằng tôi đã nói nó. Có vẻ như xấu xí mặc dù. Không có gì hơn một lớp da mỏng so với phiên bản Objective-C.
Gọi một hàm với tham số 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
}`
Viết hàm có tham số lỗi ...
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...
}
Trình bao bọc cơ bản xung quanh mục tiêu C cung cấp cho bạn tính năng thử bắt. https://github.com/williamFalcon/SwiftTryCatch
Sử dụng như:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
Đây là một câu trả lời cập nhật cho swift 2.0. Tôi đang mong đợi mô hình xử lý lỗi phong phú như trong java. Cuối cùng, họ đã thông báo tin tốt. đây
Mô hình xử lý lỗi: Mô hình xử lý lỗi mới trong Swift 2.0 sẽ ngay lập tức cảm thấy tự nhiên, với các từ khóa thử, ném và bắt quen thuộc . Trên hết, nó được thiết kế để hoạt động hoàn hảo với SDK Apple và NSError. Trên thực tế, NSError tuân thủ Loại Lỗi của Swift. Bạn chắc chắn sẽ muốn xem phiên WWDC trên What's New Swift để nghe thêm về nó.
ví dụ :
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Như Guilherme Torres Castro cho biết, trong Swift 2.0, try
, catch
, do
có thể được sử dụng trong chương trình này.
Ví dụ: Trong phương thức tìm nạp dữ liệu CoreData, thay vì đặt &error
làm tham số vào managedContext.executeFetchRequest(fetchRequest, error: &error)
, bây giờ chúng ta chỉ cần sử dụng managedContext.executeFetchRequest(fetchRequest)
và sau đó xử lý lỗi với try
, catch
( Liên kết tài liệu của Apple )
do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
Nếu bạn đã tải xuống xcode7 Beta. Hãy thử tìm kiếm lỗi ném trong Tài liệu và Tham chiếu API và chọn kết quả hiển thị đầu tiên, nó đưa ra ý tưởng cơ bản về những gì có thể được thực hiện cho cú pháp mới này. Tuy nhiên, tài liệu đầy đủ chưa được đăng cho nhiều API.
Các kỹ thuật xử lý lỗi lạ mắt hơn có thể được tìm thấy trong
Có gì mới trong Swift (Phiên bản 2015 106 28m30s)
Xử lý lỗi là một tính năng mới của Swift 2.0. Nó sử dụng try
, throw
và catch
từ khóa.
Xem thông báo Apple Swift 2.0 trên blog chính thức của Apple Swift
Lib đẹp và đơn giản để xử lý ngoại lệ: TryCatchFinally-Swift
Giống như một số khác, nó bao quanh các tính năng ngoại lệ C khách quan.
Sử dụng nó như thế này:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
Bắt đầu với Swift 2, như những người khác đã đề cập, việc xử lý lỗi được thực hiện tốt nhất thông qua việc sử dụng các enum do / try / Catch và ErrorType. Điều này hoạt động khá tốt cho các phương thức đồng bộ, nhưng cần một chút thông minh để xử lý lỗi không đồng bộ.
Bài viết này có một cách tiếp cận tuyệt vời cho vấn đề này:
https://jeremywsherman.com/blog/2015/06/17/USE-swift-throws-with-completion-callbacks/
Để tóm tắt:
// 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()
}
sau đó, cuộc gọi đến phương thức trên sẽ như sau:
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
}
})
Điều này có vẻ sạch hơn một chút so với việc có một cuộc gọi lại errorHandler riêng được chuyển đến chức năng không đồng bộ, đó là cách điều này sẽ được xử lý trước Swift 2.
Những gì tôi đã thấy là do bản chất của thiết bị mà bạn không muốn ném một loạt các thông báo xử lý lỗi khó hiểu vào người dùng. Đó là lý do tại sao hầu hết các hàm trả về các giá trị tùy chọn sau đó bạn chỉ cần mã để bỏ qua tùy chọn. Nếu một chức năng trở lại không có nghĩa là nó thất bại, bạn có thể bật một tin nhắn hoặc bất cứ điều gì.