Background HTTP fetcher and JSON parser example. (#2177)

This commit is contained in:
Nikolay Igotti
2018-10-08 08:07:54 +03:00
committed by GitHub
parent 10b05b6229
commit e98ef837b7
+92 -25
View File
@@ -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 kotlinx.cinterop.*
import platform.AppKit.* import platform.AppKit.*
import platform.Foundation.* import platform.Foundation.*
import platform.darwin.* import platform.darwin.NSObject
import platform.posix.* 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>) { fun main(args: Array<String>) {
autoreleasepool { autoreleasepool {
@@ -11,53 +29,97 @@ fun main(args: Array<String>) {
} }
} }
val appDelegate = MyAppDelegate()
private fun runApp() { private fun runApp() {
val app = NSApplication.sharedApplication() val app = NSApplication.sharedApplication()
app.delegate = MyAppDelegate() app.delegate = appDelegate
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular) app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
app.activateIgnoringOtherApps(true) app.activateIgnoringOtherApps(true)
app.run() app.run()
} }
data class Data(val stamp: ULong) class Controller : NSObject() {
private var index = 1
private class Controller : NSObject() { private var httpDelegate = HttpDelegate()
private val asyncQueue = dispatch_queue_create("com.jetbrains.CustomQueue", null)
@ObjCAction @ObjCAction
fun onClick() { fun onClick() {
// Execute some async action on button click. if (!appDelegate.canClick) {
dispatch_async_f(asyncQueue, DetachedObjectGraph { appDelegate.contentText.string = "Another load in progress..."
Data(clock_gettime_nsec_np(CLOCK_REALTIME)) return
}.asCPointer(), staticCFunction { }
it -> appDelegate.canClick = false
initRuntimeIfNeeded() // Fetch URL in the background on the button click.
val data = DetachedObjectGraph<Data>(it).attach() httpDelegate.fetchUrl("https://jsonplaceholder.typicode.com/todos/${index++}")
println("in async: $data")
})
} }
@ObjCAction @ObjCAction
fun onQuit() { fun onQuit() {
NSApplication.sharedApplication().stop(this) 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 window: NSWindow
private val controller = Controller() private val controller = Controller()
val contentText: NSText
var canClick = true
init { init {
val mainDisplayRect = NSScreen.mainScreen()!!.frame val mainDisplayRect = NSScreen.mainScreen()!!.frame
val windowRect = mainDisplayRect.useContents { val windowRect = mainDisplayRect.useContents {
NSMakeRect( NSMakeRect(
origin.x + size.width * 0.25, origin.x + size.width * 0.25,
origin.y + size.height * 0.25, origin.y + size.height * 0.25,
size.width * 0.5, size.width * 0.5,
size.height * 0.5 size.height * 0.5
) )
} }
@@ -66,7 +128,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply { window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
title = "Окошко Konan" title = "URL async fetcher"
opaque = true opaque = true
hasShadow = true hasShadow = true
preferredBackingLocation = NSWindowBackingLocationVideoMemory preferredBackingLocation = NSWindowBackingLocationVideoMemory
@@ -83,7 +145,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
} }
val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply { val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply {
title = "Press me" title = "Click"
target = controller target = controller
action = NSSelectorFromString("onClick") action = NSSelectorFromString("onClick")
} }
@@ -94,11 +156,16 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
action = NSSelectorFromString("onQuit") action = NSSelectorFromString("onQuit")
} }
window.contentView!!.addSubview(buttonQuit) 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) { override fun applicationWillFinishLaunching(notification: NSNotification) {
window.makeKeyAndOrderFront(this) window.makeKeyAndOrderFront(this)
} }
} }