Is there a way to wait for the Box API to finish all requests? So for example, if I make a folder item request, I would like to have my program wait for the completion handler to finish before moving on.
As an example:
BOXContentClient *contentClient = [BOXContentClient defaultClient];
BOXFolderItemsRequest *listAllInRoot = [contentClient folderItemsRequestWithID:BOXAPIFolderIDRoot];
[listAllInRoot performRequestWithCompletion:^(NSArray *items, NSError *error) {
//Do something with the results here
}
// Wait here for the completion handler to finish before moving on
I had a go at using an NSCondition, however I am wondering if there's a better way.
(Swift 5.x) You can use this code :
var a: [String:Any]
func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
// For example your action on a
}
}
myFunction { (status) in
if status {
print(a!)
}
}
Related
My app may create / delete thousands of managed objects while running. I have used secondary NSManagedObjectContexts(MOCs) with NSPrivateQueueConcurrencyType and NSOperations to make the app more responsive and most parts work well. But when I pressed ⌘Q and if the number of unsaved objects are large, the app hangs for quite a while before the window closes (the beach ball keeps on rotating...).
How to make the window disappear immediately, before the save of the MOC?
I tried to insert window.close() in applicationShouldTerminate in the AppDelegate, but it has no effect.
My code for deletion is nothing special, except the hierachy is really large. Something like
let items = self.items as! Set<Item>
Group.removeItems(items)
for i in items {
self.managedObjectContext?.deleteObject(i)
}
Item is a hierarchic entity. Group has a one-to-many relationship to items.
The removeItems is generated by CoreData with #NSManaged.
Many thanks.
Updates
I tried the following code, the save still blocks the UI.
#IBAction func quit(sender: AnyObject) {
NSRunningApplication.currentApplication().hide()
NSApp.terminate(sender)
}
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply
{
let op = NSBlockOperation { () -> Void in
do {
try self.managedObjectContext.save()
} catch {
print("error")
}
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
NSApp.replyToApplicationShouldTerminate(true)
})
}
op.start()
return .TerminateLater
}
This doesn't make the window close first, when the amount of created / deleted managed objects is large.
Then I changed to the following, as suggested by #bteapot. Still has no effect. The window still won't close immediately.
#IBAction func quit(sender: AnyObject) {
NSRunningApplication.currentApplication().hide()
NSApp.terminate(sender)
}
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
let op = NSBlockOperation { () -> Void in
self.managedObjectContext.performBlock({ () -> Void in
do {
try self.managedObjectContext.save()
} catch {
print("errr")
}
})
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
NSApp.replyToApplicationShouldTerminate(true)
})
}
dispatch_async ( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
{() -> Void in
op.start()
})
return .TerminateLater
}
Finally I sort of solved the problem, though the UI is still blocked sometimes, even with the same test data.
The approach used can be found here: https://blog.codecentric.de/en/2014/11/concurrency-coredata/ , Core Data background context best practice , https://www.cocoanetics.com/2012/07/multi-context-coredata/
First I made a backgroundMOC with .PrivateQueueConcurrencyType
lazy var backgroundMOC : NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
let moc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
moc.persistentStoreCoordinator = coordinator
moc.undoManager = nil
return moc
}()
Then made it prent of the original moc.
lazy var managedObjectContext: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
// managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.parentContext = self.backgroundMOC
managedObjectContext.undoManager = nil
return managedObjectContext
}()
Two methods for the save.
func saveBackgroundMOC() {
self.backgroundMOC.performBlock { () -> Void in
do {
try self.backgroundMOC.save()
NSApp.replyToApplicationShouldTerminate(true)
} catch {
print("save error: bg")
}
}
}
func saveMainMOC() {
self.managedObjectContext.performBlock { () -> Void in
do {
try self.managedObjectContext.save()
self.saveBackgroundMOC()
} catch {
print("save error")
}
}
}
Change the applicationShouldTerminate() to
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
if !managedObjectContext.commitEditing() {
NSLog("\(NSStringFromClass(self.dynamicType)) unable to commit editing to terminate")
return .TerminateCancel
}
if !managedObjectContext.hasChanges {
return .TerminateNow
}
saveMainMOC()
return .TerminateLater
}
The reason it was so slow was I was using NSXMLStoreType instead of NSSQLiteStoreType.
Quitting an application might take a while since it will first empty the processes in queue.
Do you want immediate quit discarding everything in the Parent or children MOCs? But this will result in data loss.
If you have multi window application then, then close the window only but not quit the app.
Also thousands of entry should not take longer than 5 seconds to get processed and saved, if you have managed it properly. There could be some loopholes in your code, try to optimize using Instruments, CoreData profiler tool that would help you to understand the amount of time it is eating up.
To hide the window you can use the below, and in background all the coredata processing will happen, and once everything is done the app will terminate.
[self.window orderOut:nil];
In my app I'm trying to navigate to a given URL using a WebView (non visible). However, loading web content happens asynchronously. For later processing I need this however to wait until all web content is loaded (including redirections).
I experimented with CFRunLoopRunInMode to make it wait (or timeout) but cannot get it to work properly with the available loop modes. With kCFRunLoopDefaultMode no redirection is done and the load request stops with the first address. The mode kCFRunLoopCommonModes even crashs after the load request (swift code, so I don't have any useful error information).
I have registered my class as frame load delegate so I know when everything is loaded (even after multiple redirections). I just need make it work while a run loop is active (it works nicely without). Code:
override func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
jsLogger.logInfo("==> Start loading");
}
override func webView(sender: WebView!, willPerformClientRedirectToURL URL: NSURL!,
delay seconds: NSTimeInterval, fireDate date: NSDate!, forFrame frame: WebFrame!) {
redirecting = true;
}
override func webView(sender: WebView!, didCreateJavaScriptContext context: JSContext, forFrame: WebFrame!) {
jsLogger.logInfo("==> JS create");
}
override func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!) {
if redirecting {
redirecting = false;
return;
}
jsLogger.logInfo("==> Navigating to: " + sender.mainFrameURL);
}
override func webView(sender: WebView!, didFailLoadWithError error: NSError!, forFrame frame: WebFrame!) {
jsLogger.logError("Navigating to webpage failed with error \(error.localizedDescription)")
}
And here the code to load a URL with the runloop:
func navigateAndWait(location: String) -> String {
if let url = NSURL(string: location) {
redirecting = false;
webClient.mainFrame.loadRequest(NSURLRequest(URL: url));
let result: Int = Int(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, Boolean(0)));
switch result {
case kCFRunLoopRunFinished:
return "Invalid run loop";
case kCFRunLoopRunTimedOut:
return "Call timed out";
case kCFRunLoopRunStopped:
fallthrough;
default: // Everything ok.
return "";
}
}
return "Invalid URL";
}
What other ways exist to make my thread wait for the load request to finish?
Note: a solution can be given for both Obj-C and Swift.
You can use performSelector:onThread: in webViewDidFinishLoad: and didFailLoadWithError: to perform any task you want when the webview finishes load.
My scenerio:
I asynchronously call a downloader object function to try to download a file.
Within the downloader's function, it asynchronously initiates a connection for downloading.
Even so, I cannot interact with my UI elements.
For example, I cannot edit my text field while download is in progress.
I want to be able to interact with my UI elements while downloading.
Where did I miss? What should I do? I truly appreciate your help!
ViewController's snippet for async download
//relevant UI elements I am referring to
#IBOutlet var renderButton: UIButton!
#IBOutlet var urlField: UITextField!
func handleOpenURL(url: NSURL){
var downloader: aDownloader = aDownloader(url: url)
//I try to put download to background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {() -> Void in
//This function initiates a connection call in an attempt to download the file
downloader.executeConnection({
(dataURL:NSURL!,error:NSError!) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageView.image = UIImage(data:NSData(contentsOfURL: url)!)
})
//rest are nonsense
}, progress: { (percentage:Double) -> Void in
//nonsense
}
)
})
}
aDownloader.swift 's snippet for relevant code
class aDownloader: NSObject, allOtherProtocols {
unowned var url: NSURL
//this block reports the progress as a % number
var progressBlock : ((Double)->Void)?
//this block will return either the destinationFileURL after download finishes,
//or return error of any sort
var dataBlock: ((NSURL!,NSError!)->Void)?
init(url: NSURL) {
self.url = url
}
func executeConnection(received:(NSURL!,NSError!)->Void, progress:(Double)->Void){
var request : NSURLRequest = NSURLRequest(URL: self.url,
cachePolicy: .ReloadIgnoringLocalCacheData,
timeoutInterval: 60.0)
//I attempt to async-ly download the file here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { () -> Void in
var connectionManager : NSURLConnection? = NSURLConnection(request:request,
delegate: self,
startImmediately: false)
connectionManager?.scheduleInRunLoop(NSRunLoop.mainRunLoop(),
forMode: NSRunLoopCommonModes)
connectionManager?.start()
})
self.dataBlock = received
self.progressBlock = progress
}
//nonsense
}
Two things.
Most importantly, you are still configuring your configuration manager to run on the main loop:
connectionManager?.scheduleInRunLoop(NSRunLoop.mainRunLoop(),
forMode: NSRunLoopCommonModes)
You will probably find it easier to just use NSURLConnection's class method
sendAsynchronousRequest:queue:completionHandler: instead of manually starting the request in a background thread.
Also, it is a bad idea to update UI elements from a background thread:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageView.image = UIImage(data:NSData(contentsOfURL: url)!)
})
Instead, you should load the image into a temporary variable in the background and then perform the actual assignment back on the main thread.
Am using a Class to make all the network calls to fetch data.
// Helper Class method for network calls
- (void) dataForUser:user withCompletionHandler:(void(^)(id response)) onComplete {
[[webClient sharedObject] fetchDataForUser:user withCompletionHandler:(void(^)(id response)) onComplete {
// do something to get data
onComplete(data);
}];
}
// View Controller's Model Class
- (void) getDataWithCompletionHandler:(void(^)(id)) onComplete {
// helperClassObj is a class variable
[helperClassObj dataForUser:userInfo withCompletionHandler:^(id response) {
// process response and store it as response 1.
onComplete(response1);
}];
}
I cannot make another request until one gets completed. How can I cancel a previous request so that I dont have to wait until I get data. Like I requested data for user1 and request for data for user2 and I need to display user2 data and be able to cancel the previous call.
// Helper Class method for network calls
- (void) dataForUser:user withCompletionHandler:(void(^)(id response)) onComplete {
[webClient fetchDataForUser:user withCompletionHandler:^(id response) {
// do something to get data
if(onComplete) {
onComplete(data);
}
}];
}
// View Controller's Model Class
BOOL isLastRequestCancelled = NO;
- (void) getDataWithCompletionHandler:(void(^)(id)) onComplete {
isLastRequestCancelled = YES;
// helperClassObj is a class variable
[helperClassObj dataForUser:userInfo withCompletionHandler:^(id response) {
// process response and store it as response1
if(!isLastRequestCancelled) {
if(onComplete) {
onComplete(response1);
}
}
isLastRequestCancelled = NO;
}];
}
I need to wait for savePhototoImage to complete before moving on in my processing. I assume a completion block is the way to do this.
I have seen a few completion blocks in IOS code, but do not know much about how they are made up.
Can a completion block be added to any function and if so, what would be the correct syntax to add one to this function?
BOOL saved = [Network savePhotoImage:img :self.description :#"Photo"];
ViewController.m
[Network savePhotoImage:img :self.description :#"Photo" withCallback:^(BOOL success)
{
NSLog(#"executing callback");
if (success)
{
NSLog(#"got callback success");
}
else
{
NSLog(#"got callback failure");
}
}];
Network.m
+ (void)savePhotoImage:(UIImage*)PhotoImage :(NSString*)description :(NSString*)imageName withCallback:(ASCompletionBlock)callback
{
add workdone code here...
if (workdone)
callback(YES);
} else {
callback(NO);
}
}
Network.h
typedef void (^ASCompletionBlock)(BOOL success);
+ (void)savePhotoImage:(UIImage*)PhotoImage :(NSString*)description : (NSString*)imageName withCallback:(ASCompletionBlock)callback;