devxoul / Then

✨ Super sweet syntactic sugar for Swift initializers
MIT License
4.18k stars 290 forks source link

Add ability to call block on Optional #20

Closed DenTelezhkin closed 8 years ago

DenTelezhkin commented 8 years ago

Hey!

I really like then approach and wanted to extend it for optionals. Feature implemented in this PR is different from current Then, however similar in ideology.

It allows calling any custom block of code on optional, if optional contains .Some. Most common case for this would be [weak self] in various kinds of completion blocks, however there are a lot more cases where you can use this approach.

For example, let's say we need to use weak self, however pass self somewhere else as unwrapped optional, like this:

self.doSomethingWithCompletion { [weak self] result in
    self?.parseResult(result, receivedFrom: self!)
    self?.doSomething()
}

This would require multiple optional unwrappings and possibly even force unwraps. Another way to do this is to unwrap with guard:

self.doSomethingWithCompletion { [weak self] result in
    guard let strongSelf = self else { return }
    strongSelf.parseResult(result, receivedFrom: strongSelf)
    strongSelf.doSomething()
}

Unwraps are gone, however we introduced new variable for self, which is not really convenient. Meet Then from this PR:

self.doSomethingWithCompletion { [weak self] result in
    self.then {
        $0.parseResult(result, receivedFrom: $0)
        $0.doSomething()
    }
}
devxoul commented 8 years ago

Why not use flatMap?

self.doSomethingWithCompletion { [weak self] result in
    self.flatMap {
        $0.parseResult(result, receivedFrom: $0)
        $0.doSomething()
    }
}

Or you can unwrap self by using backtick(`):

self.doSomethingWithCompletion { [weak self] result in
    guard let `self` = self else { return }
    self.parseResult(result, receivedFrom: self!)
    self.doSomething()
}
DenTelezhkin commented 8 years ago

You can't use flatMap for two reasons:

  1. It warns about result not being used
  2. It confuses Swift compiler about return value of a closure, in my project at least it shows compiler error:
 Cannot invoke 'flatMap' with an argument list of type '(@noescape (...) throws -> _?)'

If i try to workaround flatMap, it still does not work:

_ = self.flatMap {
    $0.doSomething()
}
Generic parameter 'U' could not be inferred

You can try this yourself.

As for guarding self, as i said, [weak self] is not the only application, here's for example another common one:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        (segue.destinationViewController as? ChooseContenderViewController).then {
            $0.delegate = self
// Some more code on ChooseContenderViewController referenced as $0
        }
    }

Here i am completely skipping creating new variable via if let, and casting controller in type-safe way.

devxoul commented 8 years ago

I'm not sure. Why should we use then() instead of optional binding?

These are the more simple, reasonable, beautiful and extensible:

self.doSomethingWithCompletion { [weak self] result in
    guard let `self` = self else { return }
    self.parseResult(result, receivedFrom: self!)
    self.doSomething()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    switch segue.destinationViewController {
    case let viewController as ChooseContenderViewController:
        // do something with ChooseContenderViewController

    case let viewController as OtherViewController:
        // do something with OtherViewController

    default:
        break
    }
}

I think Then is not a good solution for optional binding but a world most elegant solution for initializing properties with instance initialization code.

DenTelezhkin commented 8 years ago

Ok, no problem, it's up to you to decide framework's future.

I am closing this PR then, thanks for discussion.

devxoul commented 8 years ago

Thanks for your time :smile: