可以返回或错误的Swift方法的最佳实践

时间:2016-09-09 15:41:24

标签: swift

我正在练习Swift并且有一个场景(和方法),其结果可能是成功的,也可能是失败的。

这是一个安全服务类。我有一种方法可以使用电子邮件地址和密码进行身份验证,并且如果凭据正确则返回User实例,或者抛出某种形式的false值。

我有点困惑,因为我对Swift方法的理解是你需要指定一个返回类型,所以我有:

class SecurityService {
    static func loginWith(email: String, password: String) -> User {
        // Body
    }
}

我在Go和Node.js方法中看到了返回“double”值的方法,其中第一个表示任何错误,第二个是“成功”响应。我也知道Swift没有错误或异常之类的东西(但是在我学习Swift的早期版本时可能已经改变了。)

在这种情况下,适当的做法是什么?

4 个答案:

答案 0 :(得分:10)

如果您想处理在登录过程中可能发生的错误,而不是使用Swift错误处理的强大功能:

struct User {
}

enum SecurityError: Error {
    case emptyEmail
    case emptyPassword
}

class SecurityService {
    static func loginWith(email: String, password: String) throws -> User {
        if email.isEmpty {
            throw SecurityError.emptyEmail
        }
        if password.isEmpty {
            throw SecurityError.emptyPassword
        }
        return User()
    }    
}

do {
    let user = try SecurityService.loginWith1(email: "", password: "")
} catch SecurityError.emptyEmail {
    // email is empty
} catch SecurityError.emptyPassword {
    // password is empty
} catch {
    print("\(error)")
}

或转换为可选:

guard let user = try? SecurityService.loginWith(email: "", password: "") else {
    // error during login, handle and return
    return
}

// successful login, do something with `user`

如果您只想获得Usernil

class SecurityService {    
    static func loginWith(email: String, password: String) -> User? {
        if !email.isEmpty && !password.isEmpty {
            return User()
        } else {
            return nil
        }
    }
}

if let user = SecurityService.loginWith(email: "", password: "") {
    // do something with user
} else {
    // error
}

// or

guard let user = SecurityService.loginWith(email: "", password: "") else {
    // error
    return
}

// do something with user

答案 1 :(得分:2)

除了throw错误的标准方法之外,您还可以使用enum作为返回类型的关联类型

struct User {}

enum LoginResult {
  case Success(User)
  case Failure(String)
}

class SecurityService {
  static func loginWith(email: String, password: String) -> LoginResult {
    if email.isEmpty { return .Failure("Email is empty") }
    if password.isEmpty { return .Failure("Password is empty") }
    return .Success(User())
  }
}

并称之为:

let result = SecurityService.loginWith("Foo", password: "Bar")
switch result {
  case .Success(let user) : 
     print(user) 
     // do something with the user
  case .Failure(let errormessage) : 
     print(errormessage) 
    // handle the error
}

答案 2 :(得分:1)

我认为调用 loginWith 的结果可以从网络请求中获得,这是我在您提供的场景中可以执行的代码:

助手班:

struct User {
  var name: String
  var email: String
}

class HTTP {
  static func request(URL: String, method: String, params: [String: AnyObject], callback: (error: NSError?, result: [String:AnyObject]?) -> Void) -> Void {
    // network request
  }
}
class SecurityService {
  static func loginWith(email: String, password: String, callback: (error: NSError?, user: User?) -> Void) -> Void {

    let URL = ".."
    let params = [
      "email": email,
      "password": password
    ]

    HTTP.request(URL, method: "POST", params: params) { (error, result) in
      if let error = error {
        callback(error: error, user: nil)
      } else {
        guard let JSON = result else {
          let someDomain = "some_domain"
          let someCode = 100
          let someInfo = [NSLocalizedDescriptionKey: "No results were sent by the server."]

          let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
          callback(error: error, user: nil)
          return
        }

        guard let name = JSON["name"] as? String, email = JSON["email"] as? String else {
          let someDomain = "some_domain"
          let someCode = 100
          let someInfo = [NSLocalizedDescriptionKey: "No user properties were sent by the server."]

          let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
          callback(error: error, user: nil)
          return
        }

        let user = User(name: name, email: email)
        callback(error: nil, user: user)
      }
    }
  }
}

使用 SecurityService 类:

SecurityService.loginWith("someone@email.com", password: "123456") { (error, user) in
  if let error = error {
    print(error)
  } else {

    guard let user = user else {
      print("no user found.")
      return
    }

    print(user)
  }
}

答案 3 :(得分:0)

返回带有关联值的结果枚举,抛出异常,并使用带有可选错误和可选用户的回调,尽管有效假设登录失败错误 。然而,并非总是如此。

  • 返回包含成功和失败案例的枚举分别包含结果和错误几乎与返回可选User?相同。更像是编写自定义可选枚举,最终都会使调用者变得混乱。此外,它仅在登录进程同步时才有效。
  • 通过callBack返回结果,看起来更好,因为它允许操作异步。但是在呼叫者面前仍然存在错误处理。
  • 抛出通常比返回错误更受欢迎,只要调用者的范围是处理错误的正确位置,或者至少调用者可以访问可以处理此错误的对象/方法。

这是另一种选择:

func login(with login: Login, failure: ((LoginError) -> ())?, success: (User) -> ()?) {
    if successful {
        success?(user)
    } else {
        failure?(customError)
    }
}

// Rename with exactly how this handles the error if you'd have more handlers, 
// Document the existence of this handler, so caller can pass it along if they wish to.
func handleLoginError(_ error: LoginError) {
    // Error handling
}

现在来电者可以;只是决定忽略错误或传递处理函数/闭包。

login(with: Login("email", "password"), failure: nil) { user in 
    // Ignores the error 
}

login(with: Login("email", "password"), failure: handleLoginError) { user in 
    // Lets the error be handled by the "default" handler.
} 

PS,为相关领域创建数据结构是一个好主意; Login电子邮件和密码,而不是单独设置属性。

struct Login {
    typealias Email = String 
    typealias Password = String 

    let email: Email
    let password: Password
}