Swift:通过能够捕获 defer 语句中的返回值来使调试更容易

Saj*_*jon 5 ios swift guard-statement swift3

所以我喜欢声明变量来保存返回值,然后在下一行返回所述变量,从而轻松调试我的代码,我可以在返回行设置一个断点并查看它返回什么值。我在任何地方都使用它,它使我的所有代码更容易调试。

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let cellCount = models.count
    return cellCount
}
Run Code Online (Sandbox Code Playgroud)

但是,在这种情况下,您必须满足多种选择和不同的条件,才能使您的方法有意义。Guard 声明非常适合确保满足某些条件,同时不会引入厄运金字塔

但早期返回的问题是,您从方法中获得至少两个退出点(因为在此上下文中guard需要 a ),这使得调试变得更加困难。return

// Instantiate using dependency injection
private let reachability: ReachabilityProtocol
private let apiClient: APIClientProtocol

    // Returns true if could start login request, else false 
    func loginUser(username: String, password: String) -> Bool {
        defer {
             // Not possible, would be nice! Return value would be an implicitly declared variable
             // exaclty like the variables 'newValue' and 'oldValue' in property observers!
            print("return value: \(returnValue)")
        }
        guard reachability.isOnline && !username.isEmpty && !password.isEmpty { return false }
        apiClient.loginUser(username, password: password)
        return true
    }
Run Code Online (Sandbox Code Playgroud)

如果 Swift 3.X 能让defer 语句能够捕获返回值,那就太棒了,不是吗?

这将使调试变得更加容易,同时仍然利用guard早期返回。我对编写编译器等一无所知,但感觉这在未来的 Swift 版本中实现起来并不难

您能想出一种不同的方法来实现单点读取具有多个退出点的方法的返回值吗?(无需等待我建议的改进defer?)

编辑
我上面的登录示例并不是一个完美的示例,抱歉,为什么我要编写这样的代码?哈哈!但还有很多其他类似的场景,也许像这样,使用do-try-catch也会使代码难以调试:

// We don't know the return value of this function! Makes it hard to debug!
func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
    defer {
         // Not possible, would be nice! Return value would be an implicitly declared variable
         // exaclty like the variables 'newValue' and 'oldValue' in property observers!
        print("return value: \(returnValue)")
    }

    guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
    guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
    // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
    guard firstName != "DEBUG" else { return User.debugUser }
    let fetchRequest = NSFetchRequest(entityName: Users.entityName)
    let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
    fetchRequest.predicate = predicate
    do {
        let user = try context.executeFetchRequest(fetchRequest)
        return user
    } catch let error as NSError {
        print("Error fetching user: \(error)")
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

vac*_*ama 1

我喜欢你对 Swift 的改进建议,以defer捕获返回值。

这是可行的,但它并不完美,因为它需要一些额外的工作(并且代码混乱),但您可以通过在函数顶部声明returnValuewith来手动完成此操作,为其提供与函数相同的类型let返回。然后,将您的全部替换return <something>returnValue = <something>; return returnValue.

通过声明returnValuewith ,如果您在离开函数之前let忘记赋值,Swift 会通知您。returnValue因此,如果您向函数添加新的return内容,则在您分配returnValue. 您将看到错误:error:constant 'returnValue'used before beinginitialized

func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
    let returnValue: User?
    defer {
        print("return value: \(returnValue)")
    }

    guard !firstName.isEmpty else { print("firstName can't be empty"); returnValue = nil; return returnValue }
    guard !lastName.isEmpty else { print("lastName can't be empty"); returnValue = nil; return returnValue }
    // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
    guard firstName != "DEBUG" else { returnValue = User.debugUser; return returnValue }
    let fetchRequest = NSFetchRequest(entityName: Users.entityName)
    let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
    fetchRequest.predicate = predicate
    do {
        let user = try context.executeFetchRequest(fetchRequest)
        returnValue = user; return returnValue
    } catch let error as NSError {
        print("Error fetching user: \(error)")
    }
    returnValue = nil; return returnValue
}
Run Code Online (Sandbox Code Playgroud)

或者(只是在这里集思广益......),将具有多个退出点的函数放入内部函数中,然后调用它:

func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {

    func innerFunc(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {

        guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
        guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
        // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
        guard firstName != "DEBUG" else { return User.debugUser }
        let fetchRequest = NSFetchRequest(entityName: Users.entityName)
        let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
        fetchRequest.predicate = predicate
        do {
            let user = try context.executeFetchRequest(fetchRequest)
            return user.first as? User
        } catch let error as NSError {
            print("Error fetching user: \(error)")
        }
        return nil
    }

    let returnValue = innerFunc(firstName, andLastName: lastName, fromContext: context)
    print("return value: \(returnValue)")
    return returnValue
}
Run Code Online (Sandbox Code Playgroud)