My app has a status area at the top that shows progress information (similar to Xcode and iTunes). I want to update it by injecting side effects into an event stream, using a closure that converts the stream's value into the ProgressUpdate value. I'm using an extension on SignalProducer so any signal producer in my app can update the app's status area (there is a lot more involved to allow for multiple signals at once, but that doesn't affect this problem).
I'm basing it on SignalProducer's on(starting:, started:, ...). It requires the latest swift 3.1 beta to allow the constraint on the error type, but this is straight from a playground.
import ReactiveSwift
struct Rc2Error: Error {
}
struct ProgressUpdate {
let message: String
let value: Double = -1
}
class MacAppStatus {
fileprivate func process(event: (Event<ProgressUpdate, Rc2Error>) -> Void)
{
//update UI based on the event
}
}
extension SignalProducer where Error == Rc2Error {
func updateProgress<Value>(status: MacAppStatus, converter: #escaping (Value) -> ProgressUpdate) -> SignalProducer<Value, Error>
{
return SignalProducer<Value, Error> { observer, compositeDisposable in
self.startWithSignal { signal, disposable in
compositeDisposable += disposable
compositeDisposable += signal
.on(event: { (orignal) in
switch original {
case .completed:
status.process(Event<ProgressUpdate, Rc2Error>.completed)
case .interrupted:
status.process(Event<ProgressUpdate, Rc2Error>.interrupted)
case .failed(let err):
status.process(Event<ProgressUpdate, Rc2Error>.failed(err))
case .value(let val):
status.process(Event<ProgressUpdate, Rc2Error>.value(converter(val)))
}
})
.observe(observer)
}
}
}
}
```
The last line of .observe(observer) produces an error:
error: cannot convert value of type 'Observer<Value, Rc2Error>' to expected argument type 'Observer<_, Rc2Error>'
Any ideas why this conversion fails? Suggestions on a different way to accomplish this?
It looks like it was just bad error reporting from the compiler. The actual problem was that process() should take an Event, not a closure that takes an event. It also needed an empty external parameter name.
Changing the signature to
fileprivate func process(_ event: Event<ProgressUpdate, Rc2Error>)
and fixing the original typo Mike Taverne pointed out fixed it.
Related
I have setup a Kotlin Multiplatform project and attached a SQLDelight database to it. Its all setup and running correctly as i have tested it on the android side using the following:
commonMain:
val backgroundColorFlow: Flow<Color> =
dbQuery.getColorWithId(BGColor.id)
.asFlow()
.mapToOneNotNull()
which triggers fine in the Android projects MainActivity.kt using:
database.backgroundColorFlow.onEach { setBackgroundColor(it.hex) }.launchIn(lifecycleScope)
but when trying to access the same call in the iOS projects app delegate i get the following options and im unsure how to use them or convert them into my BGColor object:
database.backgroundColorFlow.collect(collector: T##Kotlinx_coroutines_coreFlowCollector, completionHandler: (KotlinUnit?, Error?) -> Void)
can anyone help me with how to use this?
So this was resolved by creating a flow helper:
import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
My backgroundColorFlow var is update as follows to utilise this helper:
val backgroundColorFlow: CommonFlow<BGColor> =
dbQuery.getColorWithId(BGColor.id)
.asFlow()
.mapToOneNotNull()
.map { BGColor(it.name) }
.asCommonFlow()
Then my swift works as follows:
database.backgroundColorFlow.watch { color in
guard let colorHex = color?.hex else {
return
}
self.colorBehaviourSubject.onNext(colorHex)
}
and android like so:
database.backgroundColorFlow.watch { setBackgroundColor(it.hex) }
Hope this helps anyone that comes across this. I would like to convert the CommonFlow class into an extension of Flow but don't have the know-how atm so if any could that IMHO would be a much nicer solution
You can do it in swift, with the mentioned collect method
FlowCollector is a protocol which can be implemented to collect the data of the Flow object.
Generic example implementation could look like:
class Collector<T>: FlowCollector {
let callback:(T) -> Void
init(callback: #escaping (T) -> Void) {
self.callback = callback
}
func emit(value: Any?, completionHandler: #escaping (KotlinUnit?, Error?) -> Void) {
// do whatever you what with the emitted value
callback(value as! T)
// after you finished your work you need to call completionHandler to
// tell that you consumed the value and the next value can be consumed,
// otherwise you will not receive the next value
//
// i think first parameter can be always nil or KotlinUnit()
// second parameter is for an error which occurred while consuming the value
// passing an error object will throw a NSGenericException in kotlin code, which can be handled or your app will crash
completionHandler(KotlinUnit(), nil)
}
}
The second part is calling the Flow.collect function
database.backgroundColorFlow.collect(collector: Collector<YourValueType> { yourValue in
// do what ever you want
}) { (unit, error) in
// code which is executed if the Flow object completed
}
probably you also like to write some extension function to increase readability
I have a method that returns a Future:
func getItem(id: String) -> Future<MediaItem, Error> {
return Future { promise in
// alamofire async operation
}
}
I want to use it in another method and covert MediaItem to NSImage, which is a synchronous operation. I was hoping to simply do a map or flatMap on the original Future but it creates a long Publisher that I cannot erased to Future<NSImage, Error>.
func getImage(id: String) -> Future<NSImage, Error> {
return getItem(id).map { mediaItem in
// some sync operation to convert mediaItem to NSImage
return convertToNSImage(mediaItem) // this returns NSImage
}
}
I get the following error:
Cannot convert return expression of type 'Publishers.Map<Future<MediaItem, Error>, NSImage>' to return type 'Future<NSImage, Error>'
I tried using flatMap but with a similar error. I can eraseToAnyPublisher but I think that hides the fact that getImage(id: String returns a Future.
I suppose I can wrap the body of getImage in a future but that doesn't seem as clean as chaining and mapping. Any suggestions would be welcome.
You can't use dribs and drabs and bits and pieces from the Combine framework like that. You have to make a pipeline — a publisher, some operators, and a subscriber (which you store so that the pipeline will have a chance to run).
Publisher
|
V
Operator
|
V
Operator
|
V
Subscriber (and store it)
So, here, getItem is a function that produces your Publisher, a Future. So you can say
getItem (...)
.map {...}
( maybe other operators )
.sink {...} (or .assign(...))
.store (...)
Now the future (and the whole pipeline) will run asynchronously and the result will pop out the end of the pipeline and you can do something with it.
Now, of course you can put the Future and the Map together and then stop, vending them so someone else can attach other operators and a subscriber to them. You have now assembled the start of a pipeline and no more. But then its type is not going to be Future; it will be an AnyPublisher<NSImage,Error>. And there's nothing wrong with that!
You can always wrap one future in another. Rather than mapping it as a Publisher, subscribe to its result in the future you want to return.
func mapping(futureToWrap: Future<MediaItem, Error>) -> Future<NSImage, Error> {
var cancellable: AnyCancellable?
return Future<String, Error> { promise in
// cancellable is captured to assure the completion of the wrapped future
cancellable = futureToWrap
.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
}
} receiveValue: { value in
promise(.success(convertToNSImage(mediaItem)))
}
}
}
This could always be generalized to
extension Publisher {
func asFuture() -> Future<Output, Failure> {
var cancellable: AnyCancellable?
return Future<Output, Failure> { promise in
// cancellable is captured to assure the completion of the wrapped future
cancellable = self.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
}
} receiveValue: { value in
promise(.success(value))
}
}
}
}
Note above that if the publisher in question is a class, it will get retained for the lifespan of the closure in the Future returned. Also, as a future, you will only ever get the first value published, after which the future will complete.
Finally, simply erasing to AnyPublisher is just fine. If you want to assure you only get the first value (similar to getting a future's only value), you could just do the following:
getItem(id)
.map(convertToNSImage)
.eraseToAnyPublisher()
.first()
The resulting type, Publishers.First<AnyPublisher<Output, Failure>> is expressive enough to convey that only a single result will ever be received, similar to a Future. You could even define a typealias to that end (though it's probably overkill at that point):
typealias AnyFirst<Output, Failure> = Publishers.First<AnyPublisher<Output, Failure>>
I'm trying to make my API Service as generic as possible:
API Service Class
class ApiService {
func send<T>(request: RestRequest) -> T {
return request.parse()
}
}
So that the compiler can infer the response type from the request categories .auth and .data:
let apiService = ApiService()
// String
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
// Int
let intResponse = apiService.send(request: .data(.content(id: "123")))
I tried to come up with a solution using generics and a protocol with associated type to handle the parsing in a clean way. However I'm having trouble associating the request cases with the different response types in a way that it's simple and type-safe:
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
Endpoints
enum RestRequest {
case auth(_ request: AuthRequest)
case data(_ request: DataRequest)
// COMPILER ERROR HERE: Generic parameter 'T' is not used in function signature
func parse<T: Parseable>() -> T.ResponseType {
switch self {
case .auth(let request): return (request as T).parse()
case .data(let request): return (request as T).parse()
}
}
enum AuthRequest: Parseable {
case login(email: String, password: String)
case signupWithFacebook(token: String)
typealias ResponseType = String
func parse() -> ResponseType {
return "String!!!"
}
}
enum DataRequest: Parseable {
case content(id: String?)
case package(id: String?)
typealias ResponseType = Int
func parse() -> ResponseType {
return 16
}
}
}
How is T not used in function signature even though I'm using T.ResponseType as function return?
Is there a better still clean way to achieve this?
I'm trying to make my API Service as generic as possible:
First, and most importantly, this should never be a goal. Instead, you should start with use cases, and make sure that your API Service meets them. "As generic as possible" doesn't mean anything, and only will get you into type nightmares as you add "generic features" to things, which is not the same thing as being generally useful to many use cases. What callers require this flexibility? Start with the callers, and the protocols will follow.
func send<T>(request: RestRequest) -> T
Next, this is a very bad signature. You don't want type inference on return types. It's a nightmare to manage. Instead, the standard way to do this in Swift is:
func send<ResultType>(request: RestRequest, returning: ResultType.type) -> ResultType
By passing the expected result type as a parameter, you get rid of the type inference headaches. The headache looks like this:
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")))
How is the compiler to know that stringResponse is supposed to be a String? Nothing here says "String." So instead you have to do this:
let stringResponse: String = ...
And that's very ugly Swift. Instead you probably want (but not really):
let stringResponse = apiService.send(request: .auth(.signupWithFacebook(token: "9999999999999")),
returning: String.self)
"But not really" because there's no way to implement this well. How can send know how to translate "whatever response I get" into "an unknown type that happens to be called String?" What would that do?
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
This PAT (protocol w/ associated type) doesn't really make sense. It says something is parseable if an instance of it can return a ResponseType. But that would be a parser not "something that can be parsed."
For something that can be parsed, you want an init that can take some input and create itself. The best for that is Codable usually, but you could make your own, such as:
protocol Parseable {
init(parsing data: Data) throws
}
But I'd lean towards Codable, or just passing the parsing function (see below).
enum RestRequest {}
This is probably a bad use of enum, especially if what you're looking for is general usability. Every new RestRequest will require updating parse, which is the wrong place for this kind of code. Enums make it easy to add new "things that all instances implement" but hard to add "new kinds of instances." Structs (+ protocols) are the opposite. They make it easy to add new kinds of the protocol, but hard to add new protocol requirements. Requests, especially in a generic system, are the latter kind. You want to add new requests all the time. Enums make that hard.
Is there a better still clean way to achieve this?
It depends on what "this" is. What does your calling code look like? Where does your current system create code duplication that you want to eliminate? What are your use cases? There is no such thing as "as generic as possible." There are just systems that can adapt to use cases along axes they were prepared to handle. Different configuration axes lead to different kinds of polymorphism, and have different trade-offs.
What do you want your calling code to look like?
Just to provide an example of what this might look like, though, it'd be something like this.
final class ApiService {
let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = try? JSONDecoder().decode(Response.self, from: data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
}
This is very generic, and can apply to numerous kinds of use cases (though this particular form is very primitive). You may find it doesn't apply to all your use cases, so you'd begin to expand on it. For example, maybe you don't like using Decodable here. You want a more generic parser. That's fine, make the parser configurable:
func send<Response>(request: URLRequest,
returning: Response.Type,
parsedBy: #escaping (Data) -> Response?,
completion: #escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = parsedBy(data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
Maybe you want both approaches. That's fine, build one on top of the other:
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: #escaping (Response?) -> Void) {
send(request: request,
returning: returning,
parsedBy: { try? JSONDecoder().decode(Response.self, from: $0) },
completion: completion)
}
If you're looking for even more on this topic, you may be interested in "Beyond Crusty" which includes a worked-out example of tying together parsers of the kind you're discussing. It's a bit dated, and Swift protocols are more powerful now, but the basic message is unchanged and the foundation of things like parsedBy in this example.
I have a viewMode that determines if iCloud is enabled or disabled with the result being a prompt to the User to login to iCloud or not.
Is there a way to progamatically login/logout to iCloud from an XCTest to reliable test all paths?
Here is my test
func testShowLoginButtonForiCloud() {
let viewModel = OnboardingViewModel()
let expectation = XCTestExpectation(description: "Wait for CKContainer auth check")
var iCloudEnabled: Bool?
viewModel.shouldShowiCloudLogin { result, error in
iCloudEnabled = result
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
XCTAssertNotNil(iCloudEnabled)
XCTAssertFalse(iCloudEnabled!)
}
Here is my ViewModel
typealias Completion = (Bool, Error?) -> Void
final class OnboardingViewModel {
func shouldShowiCloudLogin(completion: #escaping Completion) {
CKContainer.default().accountStatus { (status, error) in
switch status {
case .available :
completion(true, nil)
default :
completion(false, error)
}
}
}
}
Can we programmatically log in to CloudKit for unit testing? This is inadvisable, because even if we could, the tests would be slow & fragile. Instead, treat CloudKit as an architectural boundary. Unit tests can go right up to this boundary. And we can pretend stuff comes back from the boundary. In this way, we can test all paths.
To program this boundary into your code, use a protocol. This protocol will be a slice containing only those CKContainer methods you want. (This is the Interface Segregation Principle in action.) Since CKContainer already implements this method, we can attach it as an empty extension.
protocol CKContainerProtocol {
func accountStatus(completionHandler: #escaping (CKAccountStatus, Error?) -> Void)
}
extension CKContainer: CKContainerProtocol {}
Then add a property to your view model:
var cloudKitContainer: CKContainerProtocol = CKContainer.default()
The default value means your code will continue to use the real CKContainer unless told otherwise. Change your code to call cloudKitContainer instead of CKContainer.default().
Then in test code, you can provide a different implementation of CKContainerProtocol. This will let you do stubbing and mocking. You can confirm that accountStatus() is called exactly once. And you can exercise its closure with different CKAccountStatus values to confirm how your Completion closure is called.
I am using Firebase to observe event and then setting an image inside completion handler
FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let _ = snapshot.value as? NSNull {
self.img = UIImage(named:"Some-image")!
} else {
self.img = UIImage(named: "some-other-image")!
}
})
However I am getting this error
Closure cannot implicitly capture a mutating self parameter
I am not sure what this error is about and searching for solutions hasn't helped
The short version
The type owning your call to FirebaseRef.observeSingleEvent(of:with:) is most likely a value type (a struct?), in which case a mutating context may not explicitly capture self in an #escaping closure.
The simple solution is to update your owning type to a reference once (class).
The longer version
The observeSingleEvent(of:with:) method of Firebase is declared as follows
func observeSingleEvent(of eventType: FIRDataEventType,
with block: #escaping (FIRDataSnapshot) -> Void)
The block closure is marked with the #escaping parameter attribute, which means it may escape the body of its function, and even the lifetime of self (in your context). Using this knowledge, we construct a more minimal example which we may analyze:
struct Foo {
private func bar(with block: #escaping () -> ()) { block() }
mutating func bax() {
bar { print(self) } // this closure may outlive 'self'
/* error: closure cannot implicitly capture a
mutating self parameter */
}
}
Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:
SE-0035: Limiting inout capture to #noescape contexts
Stating [emphasis mine]:
Capturing an inout parameter, including self in a mutating
method, becomes an error in an escapable closure literal, unless the
capture is made explicit (and thereby immutable).
Now, this is a key point. For a value type (e.g. struct), which I believe is also the case for the type that owns the call to observeSingleEvent(...) in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).
The simplest solution to this issue would be making the type owning the observeSingleEvent(...) a reference type, e.g. a class, rather than a struct:
class Foo {
init() {}
private func bar(with block: #escaping () -> ()) { block() }
func bax() {
bar { print(self) }
}
}
Just beware that this will capture self by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self weakly, e.g.
FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
Sync Solution
If you need to mutate a value type (struct) in a closure, that may only work synchronously, but not for async calls, if you write it like this:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
You cannot otherwise capture a "mutating self" with value types except by providing a mutable (hence var) copy.
Why not Async?
The reason this does not work in async contexts is: you can still mutate result without compiler error, but you cannot assign the mutated result back to self. Still, there'll be no error, but self will never change because the method (peel()) exits before the closure is even dispatched.
To circumvent this, you may try to change your code to change the async call to synchronous execution by waiting for it to finish. While technically possible, this probably defeats the purpose of the async API you're interacting with, and you'd be better off changing your approach.
Changing struct to class is a technically sound option, but doesn't address the real problem. In our example, now being a class Banana, its property can be changed asynchronously who-knows-when. That will cause trouble because it's hard to understand. You're better off writing an API handler outside the model itself and upon finished execution fetch and change the model object. Without more context, it is hard to give a fitting example. (I assume this is model code because self.img is mutated in the OP's code.)
Adding "async anti-corruption" objects may help
I'm thinking about something among the lines of this:
a BananaNetworkRequestHandler executes requests asynchronously and then reports the resulting BananaPeelingResult back to a BananaStore
The BananaStore then takes the appropriate Banana from its inside by looking for peelingResult.bananaID
Having found an object with banana.bananaID == peelingResult.bananaID, it then sets banana.isPeeled = peelingResult.isPeeled,
finally replacing the original object with the mutated instance.
You see, from the quest to find a simple fix it can become quite involved easily, especially if the necessary changes include changing the architecture of the app.
If someone is stumbling upon this page (from search) and you are defining a protocol / protocol extension, then it might help if you declare your protocol as class bound. Like this:
protocol MyProtocol: class {
...
}
You can try this! I hope to help you.
struct Mutating {
var name = "Sen Wang"
mutating func changeName(com : #escaping () -> Void) {
var muating = self {
didSet {
print("didSet")
self = muating
}
}
execute {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
muating.name = "Wang Sen"
com()
})
}
}
func execute(with closure: #escaping () -> ()) { closure() }
}
var m = Mutating()
print(m.name) /// Sen Wang
m.changeName {
print(m.name) /// Wang Sen
}
Another solution is to explicitly capture self (since in my case, I was in a mutating function of a protocol extension so I couldn't easily specify that this was a reference type).
So instead of this:
functionWithClosure(completion: { _ in
self.property = newValue
})
I have this:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
Which seems to have silenced the warning.
Note this does not work for value types so if self is a value type you need to be using a reference type wrapper in order for this solution to work.