How to filter combineLatest to be trigger only when One item changes? - swift

I wrote small demo code as following.
I made two PublishSubject of different types.
as I change any, page triggers
I need to get page trigger only when one changes, which of observable_page.
class ViewController: UIViewController {
func loadData(page: Int, keyword: String) -> Observable<[Int]> {
let _result = Observable.of([1,2,3,4])
return _result
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let observable_keyword = PublishSubject<String>()
let observable_page = PublishSubject<Int>()
let trigger_tap = PublishSubject<Void>()
let tapObservable = trigger_tap.debug("trigger_tap", trimOutput: true)
let stringObservable = observable_keyword.debug("stringObservable", trimOutput: true)
let pageObservable = observable_page.debug("pageObservable", trimOutput: true)
let request_call_trigger = Observable.combineLatest(tapObservable, pageObservable)
.debug("request_call_trigger", trimOutput: true)
let page = request_call_trigger
.withLatestFrom(stringObservable) { ($0, $1) }
.flatMap { ((_, _, page), keyword) in
Observable.combineLatest(Observable.just(page), self.loadData(page: page, keyword: keyword)) { (pageNumber: $0, movies: $1) }
.materialize()
.filter { $0.isCompleted == false }
}
.share()
observable_keyword.onNext("breaking bad")
observable_page.onNext(1)
trigger_tap.onNext(())
observable_keyword.onNext("orange is new black")
observable_keyword.onNext("orange")
}
let bag = DisposeBag()
}
I read some option, felt filter or ignore may work here, but as I need their value in next, so confused, how to apply it properly.

If a single trigger is what you're looking for, I think the operator you seek is withLatestFrom :
observable2
.withLatestFrom(observable1)
Means: Only when observable2 changes - get its latest emitted value together with the latest emitted value of observable1.
edit: If you want both values, you might need to provide a resultsSelector:
observable2
.withLatestFrom(observable1) { ($0, $1) }

You can use withLatestFrom which as per documentation
Merges two observable sequences into one observable sequence by
combining each element from self with the latest element from the
second source, if any
in your code request_call_trigger is of type Observable<(Void, Int>) which you combine with the latest value from stringObservable using withLatestFrom. This produces another tuple of type Observable<(((Void, Int), String)>
you can flatMap on the tuple created by using withLatestFrom and pass the page and keyword values to loadData method by using tuple value for page which is $0.1 so no need to use combineLatest inside flatMap.
let tapObservable = trigger_tap.debug("trigger_tap", trimOutput: true)
let stringObservable = observable_keyword.debug("stringObservable", trimOutput: true)
let pageObservable = observable_page.debug("pageObservable", trimOutput: true)
let request_call_trigger: Observable<(Void, Int)> = Observable.combineLatest(tapObservable, pageObservable)
.debug("request_call_trigger", trimOutput: true)
_ = request_call_trigger
.withLatestFrom(stringObservable) { ($0, $1) }
.flatMap { [weak self] (request_page_tuple: (Void, Int), keyword: String) -> Observable<Event<[Int]>> in
guard let strongSelf = self else {
return Observable.empty()
}
return strongSelf
.loadData(page: request_page_tuple.1, keyword: keyword)
.materialize()
.filter { !$0.isCompleted }
}
.share()

Related

How to Unit Test asynchronous functions that uses Promise Kit

Might I be so inclined to ask for a hand and or different perspectives on how to Unit Test a function on my Viewcontroller that calls an HTTP request to a Back End server using promise kit which returns JSON that is then decoded into the data types needed and then mapped.
This is one of the promise kit functions (called in viewWillAppear) to get stock values etc...
func getVantage(stockId: String) {
firstly {
self.view.showLoading()
}.then { _ in
APIService.Chart.getVantage(stockId: stockId)
}.compactMap {
return $0.dataModel()
}.done { [weak self] data in
guard let self = self else { return }
self.stockValue = Float(data.price ?? "") ?? 0.00
self.valueIncrease = Float(data.delta ?? "") ?? 0.00
self.percentageIncrease = Float(data.deltaPercentage ?? "") ?? 0.00
let roundedPercentageIncrease = String(format: "%.2f", self.percentageIncrease)
self.stockValueLabel.text = "\(self.stockValue)"
self.stockValueIncreaseLabel.text = "+\(self.valueIncrease)"
self.valueIncreasePercentLabel.text = "(+\(roundedPercentageIncrease)%)"
}.ensure {
self.view.hideLoading()
}.catch { [weak self] error in
guard let self = self else { return }
self.handleError(error: error)
}
}
I've thought of using expectations to wait until the promise kit function is called in the unit test like so :
func testChartsMain_When_ShouldReturnTrue() {
//Arange
let sut = ChartsMainViewController()
let exp = expectation(description: "")
let testValue = sut.stockValue
//Act
-> Note : this code down here doesn't work
-> normally a completion block then kicks in and asserts a value then checks if it fulfills the expectation, i'm not mistaken xD
-> But this doesn't work using promisekit
//Assert
sut.getVantage(stockId: "kj3i19") {
XCTAssert((testValue as Any) is Float && !(testValue == 0.0))
exp.fulfill()
}
self.wait(for: [exp], timeout: 5)
}
but the problem is promisekit is done in its own custom chain blocks with .done being the block that returns a value from the request, thus i can't form the completion block on the unit test like in conventional Http requests like :
sut.executeAsynchronousOperation(completion: { (error, data) in
XCTAssertTrue(error == nil)
XCTAssertTrue(data != nil)
testExpectation.fulfill()
})
You seem to have an awful amount of business logic in your view controller, and this is something that makes it harder (not impossible, but harder) to properly test your code.
Recommending to extract all networking and data processing code into the (View)Model of that controller, and expose it via a simple interface. This way your controller becomes as dummy as possible, and doesn't need much unit testing, and you'll be focusing the unit tests on the (view)model.
But that's another, long, story, and I deviate from the topic of this question.
The first thing that prevents you from properly unit testing your function is the APIService.Chart.getVantage(stockId: stockId), since you don't have control over the behaviour of that call. So the first thing that you need to do is to inject that api service, either in the form of a protocol, or in the form of a closure.
Here's the closure approach exemplified:
class MyController {
let getVantageService: (String) -> Promise<MyData>
func getVantage(stockId: String) {
firstly {
self.view.showLoading()
}.then { _ in
getVantageService(stockId)
}.compactMap {
return $0.dataModel()
}.done { [weak self] data in
// same processing code, removed here for clarity
}.ensure {
self.view.hideLoading()
}.catch { [weak self] error in
guard let self = self else { return }
self.handleError(error: error)
}
}
}
Secondly, since the async call is not exposed outside of the function, it's harder to set a test expectation so the unit tests can assert the data once it knows. The only indicator of this function's async calls still running is the fact that the view shows the loading state, so you might be able to make use of that:
let loadingPredicate = NSPredicate(block: { _, _ controller.view.isLoading })
let vantageExpectation = XCTNSPredicateExpectation(predicate: loadingPredicate, object: nil)
With the above setup in place, you can use expectations to assert the behaviour you expect from getVantage:
func test_getVantage() {
let controller = MyController(getVantageService: { _ in .value(mockedValue) })
let loadingPredicate = NSPredicate(block: { _, _ !controller.view.isLoading })
let loadingExpectation = XCTNSPredicateExpectation(predicate: loadingPredicate, object: nil)
controller.getVantage(stockId: "abc")
wait(for: [loadingExpectation], timeout: 1.0)
// assert the data you want to check
}
It's messy, and it's fragile, compare this to extracting the data and networking code to a (view)model:
struct VantageDetails {
let stockValue: Float
let valueIncrease: Float
let percentageIncrease: Float
let roundedPercentageIncrease: String
}
class MyModel {
let getVantageService: (String) -> Promise<VantageDetails>
func getVantage(stockId: String) {
firstly {
getVantageService(stockId)
}.compactMap {
return $0.dataModel()
}.map { [weak self] data in
guard let self = self else { return }
return VantageDetails(
stockValue: Float(data.price ?? "") ?? 0.00,
valueIncrease: Float(data.delta ?? "") ?? 0.00,
percentageIncrease: Float(data.deltaPercentage ?? "") ?? 0.00,
roundedPercentageIncrease: String(format: "%.2f", self.percentageIncrease))
}
}
}
func test_getVantage() {
let model = MyModel(getVantageService: { _ in .value(mockedValue) })
let vantageExpectation = expectation(name: "getVantage")
model.getVantage(stockId: "abc").done { vantageData in
// assert on the data
// fulfill the expectation
vantageExpectation.fulfill()
}
wait(for: [loadingExpectation], timeout: 1.0)
}

Chaining array of AnyPublishers

I'm wondering if there is a way to chain array of publishers similar to how we chain publishers with regular flatMap
Let's say I have three publishers: publisher1, publisher2, publisher3 all of them have the same Output, Failure types. For example, each of them is AnyPublisher<String, Never> and emits a single String value. The only role of each publisher is to fetch its own value and emits previous value joined with its own.
I'm looking for same effect as from the following pseudo code:
let pipe = publisher1(value: "")
.flatMap { publisher2(value: $0) }
.flatMap { publisher3(value: $0) }
Execution flow:
publisher1 (fetches "A") -> publisher2 (fetches "B") -> publisher3 (fetches "C") -> "ABC"
I would like to reproduce the same flow for the array with unknown count of publishers n ([AnyPublisher<String, Never>])
1 -> 2 -> 3 -> ... -> n
I'll appreciate any tips, thanks! :)
First of all, let's clarify your question. Based on your description of wanting to create a chain of flatMap-ed publishers, what you must have is an array of closures - not publishers - each returning an AnyPublisher<String, Never> publisher given a String parameter.
let pubs: [(String) -> AnyPublisher<String, Never>] = [
publisher1, publisher2, publisher3
]
To chain them, you could use a reduce method of the array by starting with a Just publisher to emit the initial parameter:
let pipe = pubs.reduce(Just("").eraseToAnyPublisher()) { acc, next in
acc.flatMap { next($0) }.eraseToAnyPublisher()
}
If I understand you correctly you should be able to use append on your publishers
let pub1: AnyPublisher<String, Never> = ["A1", "B1", "C1"].publisher.eraseToAnyPublisher()
let pub2: AnyPublisher<String, Never> = ["A2", "B2", "C2"].publisher.eraseToAnyPublisher()
let pub3: AnyPublisher<String, Never> = ["A3", "B3", "C3"].publisher.eraseToAnyPublisher()
_ = pub1.append(pub2.append(pub3))
.sink(receiveValue: { value in
print(value)
})
Another approach is to zip the publishers together, and them combine the latest values:
let publisher1 = ["A"].publisher
let publisher2 = ["B"].publisher
let publisher3 = ["C"].publisher
_ = publisher1.zip(publisher2, publisher3)
.map { $0+$1+$2 }
.sink(receiveValue: { print("Combined: \($0)") })
/// prints ABC
Or, if you have a variable size number of publishers, you can use MergeMany and reduce:
// same result: ABC
_ = Publishers.MergeMany([publisher1, publisher2, publisher3])
.reduce("") { $0 + $1 }
.sink(receiveValue: { print("Combined: \($0)") })
You can go even further, and write your own publisher, if you think you'll be needing this functionality in multiple places:
extension Publishers {
/// works also with arrays, or any other range replaceable collection
struct ConcatenateOutputs<Upstream: Publisher> : Publisher where Upstream.Output: RangeReplaceableCollection {
typealias Output = Upstream.Output
typealias Failure = Upstream.Failure
private let reducer: AnyPublisher<Upstream.Output, Failure>
init(_ publishers: Upstream...) {
self.init(publishers)
}
init<S: Swift.Sequence>(_ publishers: S) where S.Element == Upstream {
reducer = MergeMany(publishers)
.reduce(Output.init()) { $0 + $1 }
.eraseToAnyPublisher()
}
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
reducer.receive(subscriber: subscriber)
}
}
}
extension Sequence where Element: Publisher, Element.Output: RangeReplaceableCollection {
var concatenateOutputs: Publishers.ConcatenateOutputs<Element> { .init(self) }
}
// same output
_ = Publishers.ConcatenateOutputs([publisher1, publisher2, publisher3])
.sink(receiveValue: { print("Combined: \($0)") })
// the variadic initializer works the same
_ = Publishers.ConcatenateOutputs(publisher1, publisher2, publisher3)
.sink(receiveValue: { print("Combined: \($0)") })
// the less verbose construct
_ = [publisher1, publisher2, publisher3].concatenateOutputs
.sink(receiveValue: { print("Combined: \($0)") })

Loop over Publisher Combine framework

I have the following function to perform an URL request:
final class ServiceManagerImpl: ServiceManager, ObservableObject {
private let session = URLSession.shared
func performRequest<T>(_ request: T) -> AnyPublisher<String?, APIError> where T : Request {
session.dataTaskPublisher(for: self.urlRequest(request))
.tryMap { data, response in
try self.validateResponse(response)
return String(data: data, encoding: .utf8)
}
.mapError { error in
return self.transformError(error)
}
.eraseToAnyPublisher()
}
}
Having these 2 following functions, I can now call the desired requests from corresponded ViewModel:
final class AuditServiceImpl: AuditService {
private let serviceManager: ServiceManager = ServiceManagerImpl()
func emptyAction() -> AnyPublisher<String?, APIError> {
let request = AuditRequest(act: "", nonce: String.randomNumberGenerator)
return serviceManager.performRequest(request)
}
func burbleAction(offset: Int) -> AnyPublisher<String?, APIError> {
let request = AuditRequest(act: "burble", nonce: String.randomNumberGenerator, offset: offset)
return serviceManager.performRequest(request)
}
}
final class AuditViewModel: ObservableObject {
#Published var auditLog: String = ""
private let auditService: AuditService = AuditServiceImpl()
init() {
let timer = Timer(timeInterval: 5, repeats: true) { _ in
self.getBurbles()
}
RunLoop.main.add(timer, forMode: .common)
}
func getBurbles() {
auditService.emptyAction()
.flatMap { [unowned self] offset -> AnyPublisher<String?, APIError> in
let currentOffset = Int(offset?.unwrapped ?? "") ?? 0
return self.auditService.burbleAction(offset: currentOffset)
}
.receive(on: RunLoop.main)
.sink(receiveCompletion: { [unowned self] completion in
print(completion)
}, receiveValue: { [weak self] burbles in
self?.auditLog = burbles!
})
.store(in: &cancellableSet)
}
}
Everything is fine when I use self.getBurbles() for the first time. However, for the next calls, print(completion) shows finished, and the code doesn't perform self?.auditLog = burbles!
I don't know how can I loop over the getBurbles() function and get the response at different intervals.
Edit
The whole process in a nutshell:
I call getBurbles() from class initializer
getBurbles() calls 2 nested functions: emptyAction() and burbleAction(offset: Int)
Those 2 functions generate different requests and call performRequest<T>(_ request: T)
Finally, I set the response into auditLog variable and show it on the SwiftUI layer
There are at least 2 issues here.
First when a Publisher errors it will never produce elements again. That's a problem here because you want to recycle the Publisher here and call it many times, even if the inner Publisher fails. You need to handle the error inside the flatMap and make sure it doesn't propagate to the enclosing Publisher. (ie you can return a Result or some other enum or tuple that indicates you should display an error state).
Second, flatMap is almost certainly not what you want here since it will merge all of the api calls and return them in arbitrary order. If you want to cancel any existing requests and only show the latest results then you should use .map followed by switchToLatest.

Transforming [String : String] to [String : URL] and flattening out nil values

Say I have a dictionary of type [String : String] which I want to transform to type [String : URL]. I can use map or flatMap to transform the dictionary, but due to the failable URL(string:) initializer, my values are optional:
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com"]
let result = source.flatMap { ($0, URL(string: $1)) }
This returns a value of type [(String, URL?)] and not [String : URL]. Is there a one-liner to transform this dictionary with a single method? My first thought was something like:
source.filter { $1 != nil }.flatMap { ($0, URL(string: $1)!) }
But I don't need to check if the value is nil (values will never return nil on a dictionary concrete values), I need to check if the return value of URL(string:) is nil.
I could use filter to remove the nil values, but this doesn't change the return type:
source.flatMap { ($0, URL(string: $1)) }.filter { $1 != nil }
You need to make sure you're returning tuples with only non-optional values, and since optional values themselves support flatMap you can use that to make the tuple optional as opposed to the individual value inside of it:
let source = [
"google": "http://google.com",
"twitter": "http://twitter.com",
"bad": "",
]
var dict = [String: URL]()
source.flatMap { k, v in URL(string: v).flatMap { (k, $0) } }.forEach { dict[$0.0] = $0.1 }
But since we've already expanded out the dictionary creation (I don't think there's a built-in way to create a dict from an array), you might as well do this:
var dict = [String: URL]()
source.forEach { if let u = URL(string: $1) { dict[$0] = u } }
Here are a few solutions:
//: Playground - noun: a place where people can play
import Foundation
let source = ["google": "http://google.com", "twitter": "http://twitter.com", "bad": ""]
//: The first solution takes advantage of the fact that flatMap, map and filter can all be implemented in terms of reduce.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
let result = source.reduce([String: URL]()) { result, item in
guard let url = URL(string: item.value) else { return result }
return result.updatedValue(url, forKey: item.key)
}
print(result)
//: This soultion uses a custom Dictionary initializer that consums the Key/Value tuple.
extension Dictionary {
// construct a dictionary from an array of key/value pairs.
init(items: [(key: Key, value: Value)]) {
self.init()
for item in items {
self[item.key] = item.value
}
}
}
let items = source
.map { ($0, URL(string: $1)) } // convert the values into URL?s
.filter { $1 != nil } // filter out the ones that didn't convert
.map { ($0, $1!) } // force unwrap the ones that did.
let result2 = Dictionary(items: items)
print(result2)
//: This solution also uses the above initializer. Since unwrapping optional values is likely a common thing to do, this solution provides a method that takes care of the unwrapping.
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value: OptionalType {
// Flatten [Key: Optional<Type>] to [Key: Type]
func flattenValues() -> Dictionary<Key, Value.Wrapped> {
let items = self.filter { $1.asOptional != nil }.map { ($0, $1.asOptional!) }
return Dictionary<Key, Value.Wrapped>(items: items)
}
}
let result3 = Dictionary(items: source.map { ($0, URL(string: $1)) }).flattenValues()
print(result3)
Daniel T's last solution is quite nice if you want to write it in a more functional style. I'd do it a bit differently with the primary difference being a method to turn a tuple of optionals into an optional tuple. I find that to be a generally useful transform, especially combined with flatMap.
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "fail" : ""]
// Dictionary from array of (key, value) tuples. This really ought to be built it
extension Dictionary {
public init(_ array: [Element]) {
self.init()
array.forEach { self[$0.key] = $0.value }
}
}
//Turn a tuple of optionals into an optional tuple. Note will coerce non-optionals so works on (A, B?) or (A?, B) Usefull to have variants for 2,3,4 tuples.
func raiseOptionality<A,B>(_ tuple:(A?, B?)) -> (A, B)? {
guard let a = tuple.0, let b = tuple.1 else { return nil }
return (a,b)
}
let result = Dictionary(source.flatMap { raiseOptionality(($0, URL(string: $1))) } )
Easy as pie if you just want a good, known URL in place of the bad ones.
Use
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "bad": ""]
let defaultURL = URL(string: "http://www.google.com")! // or whatever you want for your default URL
let result = source.flatMap { ($0, URL(string: $1) ?? defaultURL) }

How do I tell which guard statement failed?

If I’ve got a bunch of chained guard let statements, how can I diagnose which condition failed, short of breaking apart my guard let into multiple statements?
Given this example:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
return nil
}
How can I tell which of the 4 let statements was the one that failed and invoked the else block?
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
guard let keypath = dictionary["field"] as? String
else
{
print("Keypath failed to load.")
self.init()
return nil
}
guard let rule = dictionary["rule"] as? String else
{
print("Rule failed to load.")
self.init()
return nil
}
guard let comparator = FormFieldDisplayRuleComparator(rawValue: rule) else
{
print("Comparator failed to load for rawValue: \(rule)")
self.init()
return nil
}
guard let value = dictionary["value"] else
{
print("Value failed to load.")
self.init()
return nil
}
If I wanted to keep them all in one guard statement, I can think of another option. Checking for nils inside the guard statement might work:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
if let keypath = keypath {} else {
print("Keypath failed to load.")
}
// ... Repeat for each let...
return nil
}
I don't even know if that will compile, but then I might as well have used a bunch of if let statements or guards to begin with.
What's the idiomatic Swift way?
Erica Sadun just wrote a good blog post on this exact topic.
Her solution was to hi-jack the where clause and use it to keep track of which guard statements pass. Each successful guard condition using the diagnose method will print the file name and the line number to the console. The guard condition following the last diagnose print statement is the one that failed. The solution looked like this:
func diagnose(file: String = #file, line: Int = #line) -> Bool {
print("Testing \(file):\(line)")
return true
}
// ...
let dictionary: [String : AnyObject] = [
"one" : "one"
"two" : "two"
"three" : 3
]
guard
// This line will print the file and line number
let one = dictionary["one"] as? String where diagnose(),
// This line will print the file and line number
let two = dictionary["two"] as? String where diagnose(),
// This line will NOT be printed. So it is the one that failed.
let three = dictionary["three"] as? String where diagnose()
else {
// ...
}
Erica's write-up on this topic can be found here
Normally, a guard statement doesn't let you distinguish which of its conditions wasn't satisfied. Its purpose is that when the program executes past the guard statement, you know all the variables are non-nil. But it doesn't provide any values inside the guard/else body (you just know that the conditions weren't all satisfied).
That said, if all you want to do is print something when one of the steps returns nil, you could make use of the coalescing operator ?? to perform an extra action.
Make a generic function that prints a message and returns nil:
/// Prints a message and returns `nil`. Use this with `??`, e.g.:
///
/// guard let x = optionalValue ?? printAndFail("missing x") else {
/// // ...
/// }
func printAndFail<T>(message: String) -> T? {
print(message)
return nil
}
Then use this function as a "fallback" for each case. Since the ?? operator employs short-circuit evaluation, the right-hand side won't be executed unless the left-hand side has already returned nil.
guard
let keypath = dictionary["field"] as? String ?? printAndFail("missing keypath"),
let rule = dictionary["rule"] as? String ?? printAndFail("missing rule"),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule) ?? printAndFail("missing comparator"),
let value = dictionary["value"] ?? printAndFail("missing value")
else
{
// ...
return
}
Very good question
I wish I had a good answer for that but I have not.
Let's begin
However let's take a look at the problem together. This is a simplified version of your function
func foo(dictionary:[String:AnyObject]) -> AnyObject? {
guard let
a = dictionary["a"] as? String,
b = dictionary[a] as? String,
c = dictionary[b] else {
return nil // I want to know more ☹️ !!
}
return c
}
Inside the else we don't know what did go wrong
First of all inside the else block we do NOT have access to the constants defined in the guard statement. This because the compiler doesn't know which one of the clauses did fail. So it does assume the worst case scenario where the first clause did fail.
Conclusion: we cannot write a "simple" check inside the else statement to understand what did not work.
Writing a complex check inside the else
Of course we could replicate inside the else the logic we put insito the guard statement to find out the clause which did fail but this boilerplate code is very ugly and not easy to maintain.
Beyond nil: throwing errors
So yes, we need to split the guard statement. However if we want a more detailed information about what did go wrong our foo function should no longer return a nil value to signal an error, it should throw an error instead.
So
enum AppError: ErrorType {
case MissingValueForKey(String)
}
func foo(dictionary:[String:AnyObject]) throws -> AnyObject {
guard let a = dictionary["a"] as? String else { throw AppError.MissingValueForKey("a") }
guard let b = dictionary[a] as? String else { throw AppError.MissingValueForKey(a) }
guard let c = dictionary[b] else { throw AppError.MissingValueForKey(b) }
return c
}
I am curious about what the community thinks about this.
One possible (non-idiomatic) workaround: make use of the where clause to track the success of each subsequent optional binding in the guard block
I see nothing wrong with splitting up your guard statements in separate guard blocks, in case you're interested in which guard statement that fails.
Out of a technical perspective, however, one alternative to separate guard blocks is to make use of a where clause (to each optional binding) to increment a counter each time an optional binding is successful. In case a binding fails, the value of the counter can be used to track for which binding this was. E.g.:
func foo(a: Int?, _ b: Int?) {
var i: Int = 1
guard let a = a where (i+=1) is (),
let b = b where (i+=1) is () else {
print("Failed at condition #\(i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
Above we make use of the fact that the result of an assignment is the empty tuple (), whereas the side effect is the assignment to the lhs of the expression.
If you'd like to avoid introducing the mutable counter i prior the scope of guard clause, you could place the counter and the incrementing of it as a static class member, e.g.
class Foo {
static var i: Int = 1
static func reset() -> Bool { i = 1; return true }
static func success() -> Bool { i += 1; return true }
}
func foo(a: Int?, _ b: Int?) {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
print("Failed at condition #\(Foo.i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
Possibly a more natural approach is to propagate the value of the counter by letting the function throw an error:
class Foo { /* as above */ }
enum Bar: ErrorType {
case Baz(Int)
}
func foo(a: Int?, _ b: Int?) throws {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
throw Bar.Baz(Foo.i)
}
// ...
}
do {
try foo(nil,1) // Baz error: failed at condition #1
// try foo(1,nil) // Baz error: failed at condition #2
} catch Bar.Baz(let num) {
print("Baz error: failed at condition #\(num)")
}
I should probably point out, however, that the above is probably closer to be categorized as a "hacky" construct, rather than an idiomatic one.
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
In my personal opinion, the Swift way shouldn't require you to check whether the values are nil or not.
However, you could extend Optional to suit your needs:
extension Optional
{
public func testingForNil<T>(#noescape f: (Void -> T)) -> Optional
{
if self == nil
{
f()
}
return self
}
}
Allowing for:
guard let keypath = (dictionary["field"] as? String).testingForNil({ /* or else */ }),
let rule = (dictionary["rule"] as? String).testingForNil({ /* or else */ }),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule).testingForNil({ /* or else */ }),
let value = dictionary["value"].testingForNil({ /* or else */ })
else
{
return nil
}
My two cents:
Since Swift doesn't let me add the where in the guard let, I came up with this solution instead:
func validate<T>(_ input: T?, file: String = #file, line: Int = #line) -> T? {
guard let input = input else {
print("Nil argument at \(file), line: \(line)")
return nil
}
return input
}
class Model {
let id: Int
let name: String
init?(id: Int?, name: String?) {
guard let id = validate(id),
let name = validate(name) else {
return nil
}
self.id = id
self.name = name
}
}
let t = Model(id: 0, name: "ok") // Not nil
let t2 = Model(id: 0, name: nil) // Nil
let t3 = Model(id: nil, name: "ok") // Nil
I think other answers here are better, but another approach is to define functions like this:
func checkAll<T1, T2, T3>(clauses: (T1?, T2?, T3?)) -> (T1, T2, T3)? {
guard let one = clauses.0 else {
print("1st clause is nil")
return nil
}
guard let two = clauses.1 else {
print("2nd clause is nil")
return nil
}
guard let three = clauses.2 else {
print("3rd clause is nil")
return nil
}
return (one, two, three)
}
And then use it like this
let a: Int? = 0
let b: Int? = nil
let c: Int? = 3
guard let (d, e, f) = checkAll((a, b, c)) else {
fatalError()
}
print("a: \(d)")
print("b: \(e)")
print("c: \(f)")
You could extend it to print the file & line number of the guard statement like other answers.
On the plus side, there isn't too much clutter at the call site, and you only get output for the failing cases. But since it uses tuples and you can't write a function that operates on arbitrary tuples, you would have to define a similar method for one parameter, two parameters etc up to some arity. It also breaks the visual relation between the clause and the variable it's being bound to, especially if the unwrapped clauses are long.
This code can be used for all guard and if logic tests like optional, bool and case tests. It prints a line of a logic test which failed.
class GuardLogger {
var lastGoodLine: Int
var lineWithError: Int { lastGoodLine + 1 }
var file: String
var function: String
init(file: String = #file, function: String = #function, line: Int = #line) {
self.lastGoodLine = line
self.file = file
self.function = function
}
func log(line: Int = #line) -> Bool {
lastGoodLine = line
return true
}
func print() {
Swift.print([file, function, String(lineWithError)].joined(separator: " "))
}
}
let testBoolTrue = true
let testBoolFalse = false
let guardLogger = GuardLogger()
guard
testBoolTrue, guardLogger.log(),
let testOptionalBoolTrue = Optional(testBoolTrue), guardLogger.log(),
let selfIsViewController = self as? UIViewController, guardLogger.log(),
testBoolTrue == false, guardLogger.log() // this fails
else {
print(guardLogger.lastGoodLine)
fatalError()
}