PromiseKit: delegate-system wrappers seem to return immediately when not used at the beginning of the chain - swift

I am fairly new to PromiseKit and I have been trying for a couple of days to figure out a solution for an unexpected behaviour of promise-wrapped delegate systems (UIALertView+PromiseKit, PMKLocationManager etc..).
In my fairly typical scenario of an app's Setup process, I am trying to chain up a series of operations that the user has to go through when the app loads.
For the sake of this example, let's restrict the case to only two steps:
logging the user into a Restful system followed by presenting an alertView and waiting for user's interaction.
Below is my code, where:
LoginToService is a promise-able version of a block-based method obtained via extending MCUser with PromiseKit. This works as expected and returns once the user had logged in, or fails with an error.
In the 'then' clause of the successful login, I present an alertView by returning its promised version via alert.promise().
I would expect that promise to be fulfilled before the successive .then clause (and in the end the 'finally' clause) gets called - the alert's promise should be fulfilled when the the user clicks on button to dismiss it, as per implementation of PromiseKit's delegate system wrappers: this works just fine the observed behaviour when I use alert.promise().then to start the chain of Promises -
// Doesn't work: alert.promise returns immediately
let user = MCUser.sharedInstance()
user.logInToService(.RestServiceOne, delegate: self).then { _ -> AnyPromise in
MCLogInfo("LogInToService: Promise fulfilled")
let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
return alert.promise()
}.then { (obj:AnyObject) -> Void in
print("Clicked")
}.finally {
print("Finally")
}.catch_ { error in
print("Error")
}
What I observe is that the chain continues immediately without waiting for the user to click, with the 'Clicked' and 'Finally' messages being printed to console and the alert on screen waiting for an action. Am I obviously missing something or ar those delegate-system wrappers not meant to be used if not at the beginning of the Promise chain?
Thanks in advance for any hint

It should work as you expect. You may check whether the returned promise is incorrectly completed.
What makes me gripes, though, is that alert.promise() should return a Promise<Int> - but the closure is explicitly typed to return a AnyPromise. So, your code should not compile.
I setup a test project myself, and indeed, the compiler complains. I used PromiseKit v3.x. Your's is likely an older version (finally and catch are deprecated).
After fixing the return type of the closure to Promise<Int>, the code compiles. But the important fact is, that the behaviour is as you described and experienced in your code - and not as it should, IMHO. So, there seems to be a bug.
Edit:
OK, it turned out that there is an issue with "overload resolution" and "type inference". Given your code in your OP, the Swift compiler resolves to an unexpected overload of the then method:
expected:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> Promise<U>) -> Promise<U>
actual:
func then<U>(on q: dispatch_queue_t = dispatch_get_main_queue(), _ body: (T) throws -> U) -> Promise<U>
This is caused by the succeeding finally and catch methods.
In order to solve it here in this scenario, you should either correctly fully specify the type of the closure, or let the compiler figure it out themselves by not specifying the type. I finally got this, which works as expected using PromiseKit v3.x with Swift:
import UIKit
import PromiseKit
// helper
func fooAsync() -> Promise<String> {
return Promise { fulfill, reject in
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_global_queue(0,0)) {
fulfill("OK")
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fooAsync()
.then { str in
print("String: \(str)")
let alert = UIAlertView(title: "Title", message: "Msg", delegate: nil, cancelButtonTitle: "Cancel", otherButtonTitles: "Hello")
let promise: Promise<Int> = alert.promise()
return promise
}.then { (n: Int) -> Void in // notice: closure type specified!
print("Clicked")
}.ensure {
print("Finally")
}.report { error in
print("Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This above code may not solve it in your case, since you are using a different PromiseKit library. I would recommend to use the newest Swift version.
Nonetheless, there seem to be some subtle pitfalls here with PromiseKit. Hope you can now solve your issue.

Related

Extra trailing closure passed in call - Task showing error

I'm pretty new to Swift, and attempting to convert a program in c# to swift, and I'm receiving the following error whenever I try to use a Task.
I tried to make the button's action function async, and ran into more errors, and read that one should use a wrapper function. Thus the following code is my attempt..
#IBAction func noteBrowser_button(_ sender: Any){
Task{
await load_noteBrowserScreen()
}
}
func load_noteBrowserScreen()async throws {
do{
if ( try await Server.serverVersion() >= Server.retrieveCustomerLimit )
{
//screen to change to
let screen = storyboard!.instantiateViewController(identifier: "Note_Browser") as UIViewController?
//show screen
show(screen!, sender: self)
}
else
{
Popup.showMessage(parent: Popup.parent,
title: "Server Update Required",
msg: "Your server needs updated to enable this feature.")
}
}
catch {}
}
So it turns out the answer to the problem above was conflicting class names. There is the built-in Task with swift, and there was a Task.swift class, which was throwing a conflict, and by renaming that class removed the above errors.
Thanks everyone for your time offering suggestions.

RxSwift - How to call method which returns Driver at regular intervals?

I am a RxSwift beginner and making a app with RxSwift + MVVM.
I have a method which calls API and converts to RxCocoa.Driver in ViewModel class like below.
func fetch() -> Driver<HomeViewEntity> {
apiUseCase.fetch(query: HomeViewQuery())
.map { data in
HomeViewEntity(userName: data.name,
emailAddress: data.email
}
.asDriver(onErrorRecover: { [weak self] error in
if let printableError = error as? PrintableError {
self?.errorMessageRelay.accept(AlertPayload(title: printableError.title, message: printableError.message))
}
return Driver.never()
})
}
Now, I'd like to call this fetchListPlace() method at regular intervals a.k.a polling (e.g. each 5 minutes) at ViewController.
How to do that????
I think interval is suit in this case, but I can't get an implementation image....
Here you go:
func example(_ fetcher: Fetcher) -> Driver<HomeViewEntity> {
Driver<Int>.interval(.seconds(5 * 60))
.flatMap { _ in fetcher.fetch() }
}
Also note,
Returning a Driver.never() from your recovery closure is probably a bad idea. Prefer Driver.empty() instead.
I'm not a fan of putting a side effect in the recovery closure in the first place. I think it would be better to have the fetch() return a Driver<Result<HomeViewEntity, Error>> instead and move the side effect to the end of the chain (in a subscribe or a flatMap.)

XCTest for when iCloud is enabled and disabled

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.

RxSwift state changes trigger "Warning: Recursive call or synchronization error!"

I've inherited some Swift 3 code which uses RxSwift to manage a store. The basic layout of the class is:
class UserActivityStore {
fileprivate lazy var internalDataCache: Variable<Set<NSUserActivity>?> = Variable(nil)
func addAction(_ actionToAdd: NSUserActivity) {
var content = internalDataCache.value ?? Set<NSUserActivity>()
content.insert(actionToAdd)
internalDataCache.value = content
}
func resolveAction(_ action: NSUserActivity) {
var content = internalDataCache.value
_ = content?.remove(action)
internalDataCache.value = content
}
func expectActions(_ filter: #escaping ((NSUserActivity) -> Bool)) -> Observable<NSUserActivity> {
let observable = Observable<NSUserActivity>.create { (observer) -> Disposable in
return self.internalDataCache.asObservable().subscribeNext { (newActions) in
guard let newActions = newActions else { return }
for eachAction in newActions where filter(eachAction) {
observer.onNext(eachAction)
self.resolveAction(eachAction)
}
}
}
return observable
}
}
When an action is added to this, it adds the item to the set correctly. However, the observer (in expectActions) catches that change and resolves it. Since this is all in a single thread, the error "Warning: Recursive call or synchronization error!" is triggered.
I think this is a perfectly legitimate error and that RxSwift is probably correct in its handling. However, I can't help thinking that this is a bad model. The code is essentially handling a queue of NSUserActivity objects itself.
Is this genuinely a modelling error / abuse of RxSwift or is my limited understanding of RxSwift misunderstanding this? As a hacky solution, I've tried replacing the resolveAction function with a single line internalDataCache.value?.remove(action) but that still triggers the observable and hence the bug.
Changing the observable to use a different queue (Serial or Concurrent dispatch) fixes the problem but I'm not convinced its the correct fix.

JavacriptCore WebKit EXC_BAD_ACCESS crash after executing callback with data from background thread

I'm currently trying to debug crashes in a JavascriptCore implementation of an interface for native code to perform some work on behalf of the javascript code in the WebView.
The crash sometimes occurs very soon after launching the application, other times it may take a few minutes of executing hundreds of calls to the native code for it to occur.
These are the top two lines for the backtrace of every crash:
(lldb) thread backtrace
* thread #1: tid = 0x37960c, 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x700001a2c000)
frame #0: 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15
Here is a simplified version of my view controller:
class MyViewController: NSViewController, WebFrameLoadDelegate {
let worker = Worker()
// other setup code...
func webView(webView: WebView!, didCreateJavaScriptContext context: JSContext!, forFrame frame: WebFrame!) {
context.setObject(worker, forKeyedSubscript: "ClientWorker")
}
}
The JSExport protocol itself, and implementation of the code performing work. For testing I removed the actual work and just return a dictionary with dummy data, and the crash still occurs.
#objc protocol WorkerJSExports: JSExport {
func doWork(params: [String:AnyObject], callback: JSValue)
}
#objc class Worker: NSObject, WorkerJSExports {
func doWork(params: [String:AnyObject], callback: JSValue) {
executeBackground(callback) {
return [
"test": "data"
]
}
}
private func executeBackground(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let result = f()
dispatch_sync(dispatch_get_main_queue()) {
self.executeCallback(callback, result: result)
}
}
}
private func executeCallback(callback: JSValue!, result: AnyObject) {
callback.context.evaluateScript("setTimeout").callWithArguments([callback, 0, result])
}
}
The executeBackground and executeCallback methods are helper functions, and executeCallback is making use of setTimeout in response to what I read in this SO post/answer: JavaScriptCore -- Passing a function as a parameter to ObjC and it seems to have resolved other crashes related to locking.
When I swap out executeBackground for the following function that runs just on the main thread, I have not been able to replicate the crash:
private func executeMain(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_main_queue()) {
self.executeCallback(callback, result: f())
}
}
It seems that there is some sort of issue that occurs when passing data created in a background thread into the WebView, but after pouring through the documentation I'm uncertain that what could be. The only taboo I found mentioned was passing data between multiple JSVirtualMachine instances, which doesn't seem applicable since I'm only interacting with a single WebView instance. Any assistance in figuring this out is greatly appreciated!
Update
I seem to have solved the issue by switching out the use of Grand Central Dispatch directly for NSOperationQueues. After changing executeBackground to the following, the crashes have not recurred.
private let workerQueue = NSOperationQueue()
private func executeAsync(callback: JSValue!, f: ()->([String:AnyObject])) {
self.workerQueue.addOperationWithBlock({
let result = f()
NSOperationQueue.mainQueue().addOperationWithBlock({
self.executeCallback(callback, result: result)
})
})
}
Though I can't really prove that this has fixed the crash, we've done fairly extensive testing of this functionality and haven't seen it again. The reason I didn't post this an an answer to my own question is that I'm at a loss as to why exactly this is different and/or better. If anyone has insight into what the change to NSOperationQueues may have solved, it would be very much appreciated!