How to run WebView.mainFrame.loadRequest synchronously? - objective-c

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.

Related

Java Selector for socket client not waked up after changing of interested ops from different thread

I use Java Selector for both server and client. For Server side it works perfect. It stops the thread when i call select() and wakes up when i change interest ops and it is ready for this operation..
But unfortunatelt it does not work for the same way for socket client. It stops the thread and does not wake up for reading or writing when i change interestedOps.
Creation of client connection:
selector = Selector.open()
SocketChannel.open().apply {
configureBlocking(false)
connect(address)
val key = socket.register(selector, SelectionKey.OP_READ or SelectionKey.OP_CONNECT)
val connection = ClientConnection(key) // Some stuff to hold the key for events
key.attach(connection)
}
Handle selection inside while loop:
val readyChannels = selector.select()
if (readyChannels == 0) continue
val keyIterator = selector.selectedKeys().iterator()
while (keyIterator.hasNext()) {
val key = keyIterator.next()
when (key.readyOps()) {
SelectionKey.OP_CONNECT -> {
val socket = (key.channel() as SocketChannel)
socket.finishConnect()
key.interestOps(key.interestOps() and SelectionKey.OP_CONNECT.inv())
// WORKS FINE!!!!!
key.interestOps(key.interestOps() and SelectionKey.OP_WRITE)
// Does not work at all. Selector will not wake up!
Thread(){
key.interestOps(key.interestOps() and SelectionKey.OP_WRITE)
}.start()
}
SelectionKey.OP_READ -> readPackets(key)
SelectionKey.OP_WRITE -> writePackets(key)
SelectionKey.OP_READ or SelectionKey.OP_WRITE -> {
writePackets(key)
readPackets(key)
}
}
keyIterator.remove()
}
So. The changing of interestOps from different thread does not work for socket clients. But it works fine for Server sockets..
Found solutions:
selector.select(300) -> use some timeout to wake up selector
selector.selectNow() -> use non blocking method and check the count of evetns
selector.wakeUp() -> save instance and wakeup it manually..
The question is Why it does not work ? Did I do some mistake? Something Missed?
UPD: Server side socket and selector
Creation of server socket:
selector = Selector.open()
serverSocket = ServerSocketChannel.open().apply {
socket().bind(address)
configureBlocking(false)
register(selector, SelectionKey.OP_ACCEPT)
}
Iteration of the selector inside Loop:
val readyChannels = selector.select()
if (readyChannels == 0) continue
val keyIterator = selector.selectedKeys().iterator()
while (keyIterator.hasNext()) {
val key = keyIterator.next()
when (key.readyOps()) {
SelectionKey.OP_ACCEPT -> {
val socket = serverSocket.accept().apply {
configureBlocking(false)
}
val client = clientFactory.createClient(selector,socket)
// Coroutines with Another thread context.
// There interestOps will be changed to send first data
_selectionAcceptFlow.tryEmit(client)
}
SelectionKey.OP_READ -> readPackets(key)
SelectionKey.OP_WRITE -> writePackets(key)
SelectionKey.OP_READ or SelectionKey.OP_WRITE -> {
writePackets(key)
readPackets(key)
}
}
keyIterator.remove()
}
If you call key.setInterestOps from a separate thread, you are creating a race condition between that call and the call to selector.select() in the client loop.
Your initial call to register does not contain SelectorKey.OP_WRITE. The first event triggered will be SelectorKey.OP_CONNECT. When handling that event, you indicate that in the future you are also interested in processing OP_WRITE.
If you do that in the same thread, then you are guaranteed that the interestOps are set the way you want them before the client loop reaches the call to selector.select(). If there is an OP_WRITE event available, you will process it immediatelly, otherwise the call blocks until it is available.
If you do that in a separate thread, then, depending on timing, you may run into a case where the client loop reaches the call to selector.select() and blocks even though there is an OP_WRITE event available. Since the separate thread did not yet change the interestOps, the OP_WRITE event is ignored.
I've included a self-contained example (client sending a message to server). To test different cases, you can comment/uncomment sections around line 90.
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.SelectionKey
import java.nio.channels.Selector
import java.nio.channels.ServerSocketChannel
import java.nio.channels.SocketChannel
import java.util.concurrent.CountDownLatch
val address = InetSocketAddress("localhost", 5454)
fun main() {
val serverSocketSignal = CountDownLatch(1)
Thread {
startServer(serverSocketSignal)
}.start()
Thread {
startClient(serverSocketSignal)
}.start()
}
fun startServer(serverSocketSignal: CountDownLatch) {
//prepare server socket
val selector = Selector.open()
val serverSocket = ServerSocketChannel.open().apply {
socket().bind(address)
configureBlocking(false)
register(selector, SelectionKey.OP_ACCEPT)
}
serverSocketSignal.countDown();
//run server loop
while (true) {
println("Server loop")
val readyChannels = selector.select()
if (readyChannels == 0) continue
val keyIterator = selector.selectedKeys().iterator()
while (keyIterator.hasNext()) {
val key = keyIterator.next()
when (key.readyOps()) {
SelectionKey.OP_ACCEPT -> {
println("Server ACCEPT")
val socket = serverSocket.accept().apply {
configureBlocking(false)
}
socket.register(selector, SelectionKey.OP_READ)
}
SelectionKey.OP_READ -> {
val buffer = ByteBuffer.allocate(1024)
val count = (key.channel() as SocketChannel).read(buffer)
val message = String(buffer.array(), 0, count)
println("Server READ - " + message)
}
}
keyIterator.remove()
}
}
}
fun startClient(serverSocketSignal: CountDownLatch) {
serverSocketSignal.await();
//prepare client socket
val selector = Selector.open()
SocketChannel.open().apply {
configureBlocking(false)
connect(address)
register(selector, SelectionKey.OP_CONNECT or SelectionKey.OP_READ)
}
//run client loop
while (true) {
println("Client loop")
val readyChannels = selector.select()
if (readyChannels == 0) continue
val keyIterator = selector.selectedKeys().iterator()
while (keyIterator.hasNext()) {
val key = keyIterator.next()
when (key.readyOps()) {
SelectionKey.OP_CONNECT -> {
println("Client CONNECT")
val socket = (key.channel() as SocketChannel)
socket.finishConnect()
key.interestOpsAnd(SelectionKey.OP_CONNECT.inv())
/*
This works
*/
key.interestOps(SelectionKey.OP_WRITE)
/*
This doesn't work because we're And-ing the interestOps an the OP_WRITE op was not specified when calling register()
*/
// key.interestOpsAnd(SelectionKey.OP_WRITE)
/*
This may or may not work, depending on which thread gets executed first
- it will work if the setting interestOps=OP_WRITE in the new thread gets executed before the selector.select() in the client loop
- it will not work if selector.select() in the client loop gets executed before setting interestOps=OP_WRITE in the new thread,
since there won't be anything to process and the selector.select() gets blocked
On my machine, pausing the client loop even for a small duration was enough to change the result (e.g. the Thread.sleep(1) below).
* */
// Thread {
// println("Client setting interestedOps to OP_WRITE from new thread")
// key.interestOps(SelectionKey.OP_WRITE)
// }.start()
// //Thread.sleep(1)
}
SelectionKey.OP_WRITE -> {
println("Client WRITE")
val buffer = ByteBuffer.wrap("test message from client".toByteArray());
(key.channel() as SocketChannel).write(buffer)
key.interestOps(0)
}
}
keyIterator.remove()
}
}
}
As for why it works for you on the server side - you would have to share the full code for the server and client (might be a timing issue or your selector might be woken up by some events you did not intend to listen for). The snippets provided in the question do not contain enough infomation.

Spring reactor - trigger method with delay without waiting for the results [duplicate]

I have a method like below in my Spring boot app.
public Flux<Data> search(SearchRequest request) {
Flux<Data> result = searchService.search(request);//this returns Flux<Data>
Mono<List<Data>> listOfData = result.collectList();
// doThisAsync() // here I want to pass this list and run some processing on it
// the processing should happen async and the search method should return immediately.
return result;
}
//this method uses the complete List<Data> returned by above method
public void doThisAsync(List<Data> data) {
//do some processing here
}
Currently, I'm using #Async annotated service class with doThisAsync, but don't know how to pass the List<Data>, because I don't want to call block.
All I have is Mono<List<Data>>.
My main problem is how to process this Mono separately and the search method should return the Flux<Data>.
1, If your fire-and-forget is already async returning Mono/Flux
public Flux<Data> search(SearchRequest request)
{
return searchService.search(request)
.collectList()
.doOnNext(data -> doThisAsync(data).subscribe()) // add error logging here or inside doThisAsync
.flatMapMany(Flux::fromIterable);
}
public Mono<Void> doThisAsync(List<Data> data) {
//do some async/non-blocking processing here like calling WebClient
}
2, If your fire-and-forget does blocking I/O
public Flux<Data> search(SearchRequest request)
{
return searchService.search(request)
.collectList()
.doOnNext(data -> Mono.fromRunnable(() -> doThisAsync(data))
.subscribeOn(Schedulers.elastic()) // delegate to proper thread to not block main flow
.subscribe()) // add error logging here or inside doThisAsync
.flatMapMany(Flux::fromIterable);
}
public void doThisAsync(List<Data> data) {
//do some blocking I/O on calling thread
}
Note that in both of the above cases you lose backpressure support. If the doAsyncThis slows down for some reason, then the data producer won't care and keep producing items. This is a natural consequence of the fire-and-foget mechanism.
Have you considered running the processing in separate threads using publishOn like in the example below?
This may not be exactly what you are asking for but allows you to continue with other matters while the processing of the results in the flux is done by one or more threads, four in my example, from a dedicated scheduler (theFourThreadScheduler).
#Test
public void processingInSeparateThreadTest() {
final Scheduler theFourThreadScheduler = Schedulers.newParallel("FourThreads", 4);
final Flux<String> theResultFlux = Flux.just("one", "two", "three", "four", "five", "six", "seven", "eight");
theResultFlux.log()
.collectList()
.publishOn(theFourThreadScheduler)
.subscribe(theStringList -> {
doThisAsync(theStringList);
});
System.out.println("Subscribed to the result flux");
for (int i = 0; i < 20; i++) {
System.out.println("Waiting for completion: " + i);
try {
Thread.sleep(300);
} catch (final InterruptedException theException) {
}
}
}
private void doThisAsync(final List<String> inStringList) {
for (final String theString : inStringList) {
System.out.println("Processing in doThisAsync: " + theString);
try {
Thread.sleep(500);
} catch (final InterruptedException theException) {
}
}
}
Running the example produce the following output, showing that the processing performed in doThisAsync() is performed in the background.
Subscribed to the result flux
Waiting for completion: 0
Processing in doThisAsync: one
Waiting for completion: 1
Processing in doThisAsync: two
Waiting for completion: 2
Waiting for completion: 3
Processing in doThisAsync: three
Waiting for completion: 4
Waiting for completion: 5
Processing in doThisAsync: four
Waiting for completion: 6
Processing in doThisAsync: five
Waiting for completion: 7
Waiting for completion: 8
Processing in doThisAsync: six
Waiting for completion: 9
Processing in doThisAsync: seven
Waiting for completion: 10
Waiting for completion: 11
Processing in doThisAsync: eight
Waiting for completion: 12
Waiting for completion: 13
Waiting for completion: 14
Waiting for completion: 15
Waiting for completion: 16
Waiting for completion: 17
Waiting for completion: 18
Waiting for completion: 19
References:
Reactor 3 Reference: Schedulers
UPDATE 2023/01/31
Actually you anyway should use .subscribeOn() because even if you call your fire-and-forget function which returns Mono<Void> it is not guaranteed that within that reactive chain will be switching of executing thread or it will happen immediately (depends on the code inside that fire-and-forget function, more specificaly, operators that used on the chain).
So you may run into situation when your fire-and-forget function will be executed on the same thread that called this function, so your method will not return until this function is completed.
The case when fire-and-forget function returns Publisher<Void>:
public Flux<Data> search(SearchRequest request) {
return searchService.search(request)
.collectList()
.doOnNext(data ->
// anyway call subscribeOn(...)
fireAndForgetOperation(data)
.subscribeOn(...)
.subscribe()
)
.flatMapMany(Flux::fromIterable);
}
public Mono<Void> fireAndForgetOperation(List<String> list) {
...
}
The case when fire-and-forget function is just a common void returning method:
public Flux<Data> search(SearchRequest request) {
return searchService.search(request)
.collectList()
.doOnNext(data ->
Mono.fromRunnable(() -> fireAndForgetOperation(data))
.subscribeOn(...)
.subscribe()
)
.flatMapMany(Flux::fromIterable);
}
public void fireAndForgetOperation(List<String> list) {
...
}
Also you should consider what Scheduler you need to provide, depending on the nature of your fire-and-forget function.
Basically there are two scenarios:
1) If your fire-and-forget function does CPU-Bound work.
Then you want to specify Schedulers.parallel() inside subsribeOn()
2) If your fire-and-forget function does IO work (actually no matter in this case if it would be blocking or non-blocking IO).
Then you want to specify Schedulers.boundedElastic() inside subsribeOn()
So, using this approach you will truly return immediately after firing your fire-and-forget function

iOS Box API - wait for request to finish

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!)
}
}

CoreData app takes too long to quit

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];

Why do my async calls still block me from interacting with UI elements in Swift?

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.