Background HTTP fetcher and JSON parser example. (#2177)
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
import kotlin.native.concurrent.*
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.AppKit.*
|
||||
import platform.Foundation.*
|
||||
import platform.darwin.*
|
||||
import platform.posix.*
|
||||
import platform.darwin.NSObject
|
||||
import platform.darwin.dispatch_async_f
|
||||
import kotlin.native.concurrent.DetachedObjectGraph
|
||||
import kotlin.native.concurrent.attach
|
||||
import kotlin.native.concurrent.freeze
|
||||
|
||||
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
|
||||
dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph {
|
||||
producerConsumer()
|
||||
}.asCPointer(), staticCFunction { it ->
|
||||
val result = DetachedObjectGraph<Pair<T, (T) -> Unit>>(it).attach()
|
||||
result.second(result.first)
|
||||
})
|
||||
}
|
||||
|
||||
data class QueryResult(val json: Map<String, *>?, val error: String?)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
autoreleasepool {
|
||||
@@ -11,53 +29,97 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
val appDelegate = MyAppDelegate()
|
||||
|
||||
private fun runApp() {
|
||||
val app = NSApplication.sharedApplication()
|
||||
|
||||
app.delegate = MyAppDelegate()
|
||||
app.delegate = appDelegate
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
|
||||
app.activateIgnoringOtherApps(true)
|
||||
|
||||
app.run()
|
||||
}
|
||||
|
||||
data class Data(val stamp: ULong)
|
||||
|
||||
private class Controller : NSObject() {
|
||||
private val asyncQueue = dispatch_queue_create("com.jetbrains.CustomQueue", null)
|
||||
class Controller : NSObject() {
|
||||
private var index = 1
|
||||
private var httpDelegate = HttpDelegate()
|
||||
|
||||
@ObjCAction
|
||||
fun onClick() {
|
||||
// Execute some async action on button click.
|
||||
dispatch_async_f(asyncQueue, DetachedObjectGraph {
|
||||
Data(clock_gettime_nsec_np(CLOCK_REALTIME))
|
||||
}.asCPointer(), staticCFunction {
|
||||
it ->
|
||||
initRuntimeIfNeeded()
|
||||
val data = DetachedObjectGraph<Data>(it).attach()
|
||||
println("in async: $data")
|
||||
})
|
||||
if (!appDelegate.canClick) {
|
||||
appDelegate.contentText.string = "Another load in progress..."
|
||||
return
|
||||
}
|
||||
appDelegate.canClick = false
|
||||
// Fetch URL in the background on the button click.
|
||||
httpDelegate.fetchUrl("https://jsonplaceholder.typicode.com/todos/${index++}")
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun onQuit() {
|
||||
NSApplication.sharedApplication().stop(this)
|
||||
}
|
||||
|
||||
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
|
||||
private val asyncQueue = NSOperationQueue()
|
||||
internal val receivedData = NSMutableData()
|
||||
|
||||
init {
|
||||
freeze()
|
||||
}
|
||||
|
||||
fun fetchUrl(url: String) {
|
||||
receivedData.setLength(0)
|
||||
val session = NSURLSession.sessionWithConfiguration(
|
||||
NSURLSessionConfiguration.defaultSessionConfiguration(),
|
||||
this,
|
||||
delegateQueue = asyncQueue
|
||||
)
|
||||
session.dataTaskWithURL(NSURL(string = url)).resume()
|
||||
}
|
||||
|
||||
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
|
||||
initRuntimeIfNeeded()
|
||||
receivedData.appendData(didReceiveData)
|
||||
}
|
||||
|
||||
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
|
||||
initRuntimeIfNeeded()
|
||||
|
||||
executeAsync(NSOperationQueue.mainQueue) {
|
||||
val response = task.response as? NSHTTPURLResponse
|
||||
Pair(when {
|
||||
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
|
||||
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
|
||||
else -> QueryResult(
|
||||
NSJSONSerialization.JSONObjectWithData(receivedData, 0, null) as? Map<String, *>,
|
||||
null
|
||||
)
|
||||
}, { result: QueryResult ->
|
||||
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
|
||||
appDelegate.canClick = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
|
||||
private val window: NSWindow
|
||||
private val controller = Controller()
|
||||
val contentText: NSText
|
||||
var canClick = true
|
||||
|
||||
init {
|
||||
val mainDisplayRect = NSScreen.mainScreen()!!.frame
|
||||
val windowRect = mainDisplayRect.useContents {
|
||||
NSMakeRect(
|
||||
origin.x + size.width * 0.25,
|
||||
origin.y + size.height * 0.25,
|
||||
size.width * 0.5,
|
||||
size.height * 0.5
|
||||
origin.x + size.width * 0.25,
|
||||
origin.y + size.height * 0.25,
|
||||
size.width * 0.5,
|
||||
size.height * 0.5
|
||||
)
|
||||
|
||||
}
|
||||
@@ -66,7 +128,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
|
||||
|
||||
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
|
||||
title = "Окошко Konan"
|
||||
title = "URL async fetcher"
|
||||
opaque = true
|
||||
hasShadow = true
|
||||
preferredBackingLocation = NSWindowBackingLocationVideoMemory
|
||||
@@ -83,7 +145,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
}
|
||||
|
||||
val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply {
|
||||
title = "Press me"
|
||||
title = "Click"
|
||||
target = controller
|
||||
action = NSSelectorFromString("onClick")
|
||||
}
|
||||
@@ -94,11 +156,16 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
action = NSSelectorFromString("onQuit")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonQuit)
|
||||
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
|
||||
string = "Press 'Click' to start fetching"
|
||||
verticallyResizable = false
|
||||
horizontallyResizable = false
|
||||
|
||||
}
|
||||
window.contentView!!.addSubview(contentText)
|
||||
}
|
||||
|
||||
override fun applicationWillFinishLaunching(notification: NSNotification) {
|
||||
window.makeKeyAndOrderFront(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user