สร้างรหัสข้อผิดพลาดของคุณเองใน swift 3


91

สิ่งที่ฉันพยายามบรรลุคือดำเนินการURLSessionตามคำขออย่างรวดเร็ว 3 ฉันกำลังดำเนินการดำเนินการนี้ในฟังก์ชันแยกต่างหาก (เพื่อไม่ให้เขียนโค้ดแยกกันสำหรับ GET และ POST) และส่งคืนURLSessionDataTaskและจัดการความสำเร็จและความล้มเหลวในการปิด เรียงแบบนี้ -

let task = URLSession.shared.dataTask(with: request) { (data, uRLResponse, responseError) in

     DispatchQueue.main.async {

          var httpResponse = uRLResponse as! HTTPURLResponse

          if responseError != nil && httpResponse.statusCode == 200{

               successHandler(data!)

          }else{

               if(responseError == nil){
                     //Trying to achieve something like below 2 lines
                     //Following line throws an error soo its not possible
                     //var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

                     //failureHandler(errorTemp)

               }else{

                     failureHandler(responseError!)
               }
          }
     }
}

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


ลองใช้NSErrorแทนErrorในการประกาศ ( var errorTemp = NSError(...))
Luca D'Alberti

นั่นช่วยแก้ปัญหาได้ แต่ฉันคิดว่า swift 3 ไม่ต้องการใช้ NS ต่อไป?
Rikh

ในการพัฒนา iOS สำหรับการพัฒนา Swift อย่างแท้จริงคุณควรสร้างอินสแตนซ์ข้อผิดพลาดของคุณเองโดยให้สอดคล้องกับErrorโปรโตคอล
Luca D'Alberti

@ LucaD'Alberti วิธีแก้ปัญหาของคุณแก้ปัญหาได้อย่าลังเลที่จะเพิ่มเป็นคำตอบเพื่อที่ฉันจะยอมรับ
Rikh

คำตอบ:


74

คุณสามารถสร้างโปรโตคอลที่สอดคล้องกับLocalizedErrorโปรโตคอลSwift โดยใช้ค่าเหล่านี้:

protocol OurErrorProtocol: LocalizedError {

    var title: String? { get }
    var code: Int { get }
}

สิ่งนี้ทำให้เราสามารถสร้างข้อผิดพลาดที่เป็นรูปธรรมดังนี้:

struct CustomError: OurErrorProtocol {

    var title: String?
    var code: Int
    var errorDescription: String? { return _description }
    var failureReason: String? { return _description }

    private var _description: String

    init(title: String?, description: String, code: Int) {
        self.title = title ?? "Error"
        self._description = description
        self.code = code
    }
}

3
a) ไม่จำเป็นต้องสร้าง OurErrorProtocol เพียงแค่ให้ CustomError ใช้ Error โดยตรง b) ไม่ได้ผล (อย่างน้อยใน Swift 3: localizedDescription จะไม่ถูกเรียกและคุณจะได้รับ "The operation can't be complete.") คุณต้องใช้ LocalizedError แทน ดูคำตอบของฉัน
prewett

@prewett ฉันเพิ่งสังเกตเห็น แต่คุณพูดถูก! การใช้ฟิลด์ errorDescription ใน LocalizedError จะเป็นการตั้งค่าข้อความแทนที่จะใช้วิธีการของฉันตามที่อธิบายไว้ข้างต้น ฉันยังคงเก็บ wrapper "OurErrorProtocol" ไว้เนื่องจากฉันต้องการฟิลด์ localizedTitle ด้วย ขอบคุณที่ชี้ให้ดู!
Harry Bloom

107

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

ในการพัฒนา iOS NSErrorคลาสยังคงพร้อมใช้งานและเป็นไปตามErrorโปรโตคอล

ดังนั้นหากจุดประสงค์ของคุณเพียงเพื่อเผยแพร่รหัสข้อผิดพลาดนี้คุณสามารถแทนที่ได้อย่างง่ายดาย

var errorTemp = Error(domain:"", code:httpResponse.statusCode, userInfo:nil)

ด้วย

var errorTemp = NSError(domain:"", code:httpResponse.statusCode, userInfo:nil)

หรือตรวจสอบคำตอบของSandeep Bhandariเกี่ยวกับวิธีการสร้างประเภทข้อผิดพลาดที่กำหนดเอง


15
ฉันเพิ่งได้รับข้อผิดพลาด: Error cannot be created because it has no accessible initializers.
Supertecnoboff

@AbhishekThapliyal คุณช่วยอธิบายความคิดเห็นของคุณเพิ่มเติมได้ไหม ฉันไม่เข้าใจว่าคุณหมายถึงอะไร
Luca D'Alberti

2
@ LucaD'Alberti ใน Swift 4 แสดงข้อผิดพลาดไม่สามารถสร้างได้เนื่องจากไม่มีตัวเริ่มต้นที่สามารถเข้าถึงได้ในขณะที่สร้าง Error Object
Maheep

1
@Maheep สิ่งที่ฉันแนะนำในคำตอบของฉันคือไม่ใช้Errorแต่NSError. แน่นอนโดยใช้Errorพ่นข้อผิดพลาด
Luca D'Alberti

ข้อผิดพลาดคือโปรโตคอล ไม่สามารถสร้างอินสแตนซ์ได้โดยตรง
slobodans

52

คุณสามารถสร้าง enums เพื่อจัดการกับข้อผิดพลาด :)

enum RikhError: Error {
    case unknownError
    case connectionError
    case invalidCredentials
    case invalidRequest
    case notFound
    case invalidResponse
    case serverError
    case serverUnavailable
    case timeOut
    case unsuppotedURL
 }

จากนั้นสร้างวิธีการภายใน enum เพื่อรับรหัสตอบกลับ http และส่งคืนข้อผิดพลาดที่เกี่ยวข้องในทางกลับกัน :)

static func checkErrorCode(_ errorCode: Int) -> RikhError {
        switch errorCode {
        case 400:
            return .invalidRequest
        case 401:
            return .invalidCredentials
        case 404:
            return .notFound
        //bla bla bla
        default:
            return .unknownError
        }
    }

สุดท้ายอัปเดตบล็อกความล้มเหลวของคุณเพื่อยอมรับพารามิเตอร์เดียวของประเภท RikhError :)

ฉันมีบทช่วยสอนโดยละเอียดเกี่ยวกับวิธีการปรับโครงสร้างแบบจำลองเครือข่าย Objective - C แบบดั้งเดิมที่ใช้ Object Oriented เป็นโมเดล Protocol Oriented ที่ทันสมัยโดยใช้ Swift3 ที่นี่https://learnwithmehere.blogspot.inลองดู :)

หวังว่าจะช่วยได้ :)


อ่า แต่นี่จะไม่ต้องให้ฉันจัดการทุกกรณีด้วยตัวเองเหรอ? นั่นคือพิมพ์รหัสข้อผิดพลาด?
Rikh

ใช่คุณต้อง: D แต่ในขณะเดียวกันคุณสามารถดำเนินการต่างๆที่เฉพาะเจาะจงสำหรับแต่ละสถานะข้อผิดพลาด :) ตอนนี้คุณสามารถควบคุมโมเดลข้อผิดพลาดได้ดีหากในกรณีที่คุณไม่ต้องการทำคุณสามารถใช้ case 400 ... 404 {... } จัดการเฉพาะกรณีทั่วไป :)
Sandeep Bhandari

อ๊ะ! ขอบคุณ
Rikh

สมมติว่ารหัส http หลายตัวไม่จำเป็นต้องชี้ไปที่กรณีเดียวกันคุณควรจะทำได้เพียงแค่ทำ enum RikhError: Int, Error {case invalidRequest = 400} จากนั้นสร้าง RikhError (rawValue: httpCode)
Brian F Leighty

51

คุณควรใช้วัตถุ NSError

let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invalid access token"])

จากนั้นส่ง NSError ไปยังวัตถุ Error


30

รายละเอียด

  • Xcode เวอร์ชัน 10.2.1 (10E1001)
  • สวิฟต์ 5

การแก้ไขข้อผิดพลาดในการจัดระเบียบในแอป

import Foundation

enum AppError {
    case network(type: Enums.NetworkError)
    case file(type: Enums.FileError)
    case custom(errorDescription: String?)

    class Enums { }
}

extension AppError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .network(let type): return type.localizedDescription
            case .file(let type): return type.localizedDescription
            case .custom(let errorDescription): return errorDescription
        }
    }
}

// MARK: - Network Errors

extension AppError.Enums {
    enum NetworkError {
        case parsing
        case notFound
        case custom(errorCode: Int?, errorDescription: String?)
    }
}

extension AppError.Enums.NetworkError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .parsing: return "Parsing error"
            case .notFound: return "URL Not Found"
            case .custom(_, let errorDescription): return errorDescription
        }
    }

    var errorCode: Int? {
        switch self {
            case .parsing: return nil
            case .notFound: return 404
            case .custom(let errorCode, _): return errorCode
        }
    }
}

// MARK: - FIle Errors

extension AppError.Enums {
    enum FileError {
        case read(path: String)
        case write(path: String, value: Any)
        case custom(errorDescription: String?)
    }
}

extension AppError.Enums.FileError: LocalizedError {
    var errorDescription: String? {
        switch self {
            case .read(let path): return "Could not read file from \"\(path)\""
            case .write(let path, let value): return "Could not write value \"\(value)\" file from \"\(path)\""
            case .custom(let errorDescription): return errorDescription
        }
    }
}

การใช้งาน

//let err: Error = NSError(domain:"", code: 401, userInfo: [NSLocalizedDescriptionKey: "Invaild UserName or Password"])
let err: Error = AppError.network(type: .custom(errorCode: 400, errorDescription: "Bad request"))

switch err {
    case is AppError:
        switch err as! AppError {
        case .network(let type): print("Network ERROR: code \(type.errorCode), description: \(type.localizedDescription)")
        case .file(let type):
            switch type {
                case .read: print("FILE Reading ERROR")
                case .write: print("FILE Writing ERROR")
                case .custom: print("FILE ERROR")
            }
        case .custom: print("Custom ERROR")
    }
    default: print(err)
}

16

ใช้ LocalizedError:

struct StringError : LocalizedError
{
    var errorDescription: String? { return mMsg }
    var failureReason: String? { return mMsg }
    var recoverySuggestion: String? { return "" }
    var helpAnchor: String? { return "" }

    private var mMsg : String

    init(_ description: String)
    {
        mMsg = description
    }
}

โปรดทราบว่าการใช้ Error ตามที่อธิบายไว้ในคำตอบข้อใดข้อหนึ่งจะล้มเหลว (อย่างน้อยใน Swift 3) และการเรียก localizedDescription จะส่งผลให้สตริง "The operation could not be complete. (.StringError error 1. ) "


ควรเป็น mMsg = msg
Brett

1
อ๊ะถูกต้อง ฉันเปลี่ยน "msg" เป็น "description" ซึ่งหวังว่าจะชัดเจนกว่าต้นฉบับของฉันเล็กน้อย
prewett

4
คุณสามารถลดขนาดเป็นstruct StringError : LocalizedError { public let errorDescription: String? }และใช้เป็นStringError(errorDescription: "some message")
Koen

7
 let error = NSError(domain:"", code:401, userInfo:[ NSLocalizedDescriptionKey: "Invaild UserName or Password"]) as Error
            self.showLoginError(error)

สร้างวัตถุ NSError และพิมพ์เป็นข้อผิดพลาดแสดงได้ทุกที่

private func showLoginError(_ error: Error?) {
    if let errorObj = error {
        UIAlertController.alert("Login Error", message: errorObj.localizedDescription).action("OK").presentOn(self)
    }
}

6

ฉันยังคงคิดว่าคำตอบของแฮร์รี่นั้นง่ายและสมบูรณ์ที่สุด แต่ถ้าคุณต้องการสิ่งที่ง่ายกว่านั้นให้ใช้:

struct AppError {
    let message: String

    init(message: String) {
        self.message = message
    }
}

extension AppError: LocalizedError {
    var errorDescription: String? { return message }
//    var failureReason: String? { get }
//    var recoverySuggestion: String? { get }
//    var helpAnchor: String? { get }
}

และใช้หรือทดสอบดังนี้:

printError(error: AppError(message: "My App Error!!!"))

func print(error: Error) {
    print("We have an ERROR: ", error.localizedDescription)
}

3
protocol CustomError : Error {

    var localizedTitle: String
    var localizedDescription: String

}

enum RequestError : Int, CustomError {

    case badRequest         = 400
    case loginFailed        = 401
    case userDisabled       = 403
    case notFound           = 404
    case methodNotAllowed   = 405
    case serverError        = 500
    case noConnection       = -1009
    case timeOutError       = -1001

}

func anything(errorCode: Int) -> CustomError? {

      return RequestError(rawValue: errorCode)
}

1

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

รหัสตอบกลับ http เป็นรหัสข้อผิดพลาดมาตรฐานเกี่ยวกับการตอบสนอง http ที่กำหนดสถานการณ์ทั่วไปเมื่อได้รับการตอบกลับและแตกต่างกันไปตั้งแต่ 1xx ถึง 5xx (เช่น 200 OK, 408 Request หมดเวลา, 504 Gateway timeout เป็นต้น - http://www.restapitutorial.com/ httpstatuscodes.html )

รหัสข้อผิดพลาดในอ็อบเจ็กต์ NSError ให้การระบุที่เฉพาะเจาะจงมากสำหรับชนิดของข้อผิดพลาดที่อ็อบเจ็กต์อธิบายสำหรับโดเมนเฉพาะของแอปพลิเคชัน / ผลิตภัณฑ์ / ซอฟต์แวร์ ตัวอย่างเช่นแอปพลิเคชันของคุณอาจใช้ 1,000 รายการสำหรับ "ขออภัยคุณไม่สามารถอัปเดตบันทึกนี้ได้มากกว่าหนึ่งครั้งในหนึ่งวัน" หรือพูดว่า 1001 สำหรับ "คุณต้องมีบทบาทผู้จัดการเพื่อเข้าถึงทรัพยากรนี้" ... ซึ่งเป็นข้อมูลเฉพาะสำหรับโดเมน / แอปพลิเคชันของคุณ ตรรกะ.

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

ดังนั้นอาจมีสองเทคนิคในการจัดการโค้ดให้ดีขึ้น:

1. การโทรกลับจะดำเนินการตรวจสอบทั้งหมด

completionHandler(data, httpResponse, responseError) 

2. วิธีการของคุณตัดสินความสำเร็จและสถานการณ์ผิดพลาดจากนั้นเรียกใช้การโทรกลับที่เกี่ยวข้อง

if nil == responseError { 
   successCallback(data)
} else {
   failureCallback(data, responseError) // failure can have data also for standard REST request/response APIs
}

มีความสุขในการเขียนโค้ด :)


โดยพื้นฐานแล้วสิ่งที่คุณพยายามจะบอกคือส่งผ่านพารามิเตอร์ "data" ในกรณีที่มีสตริงเฉพาะที่จะแสดงในกรณีของรหัสข้อผิดพลาดเฉพาะที่ส่งคืนจากเซิร์ฟเวอร์? (ขออภัยฉันช้าไปหน่อยในบางครั้ง!)
Rikh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.