From e98ef837b7a486f75407c51c28d86abe966acfd6 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Mon, 8 Oct 2018 08:07:54 +0300 Subject: [PATCH] Background HTTP fetcher and JSON parser example. (#2177) --- samples/objc/src/main/kotlin/Window.kt | 117 +++++++++++++++++++------ 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/samples/objc/src/main/kotlin/Window.kt b/samples/objc/src/main/kotlin/Window.kt index 98b7c5379b3..8be465e9b37 100644 --- a/samples/objc/src/main/kotlin/Window.kt +++ b/samples/objc/src/main/kotlin/Window.kt @@ -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 executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair Unit>) { + dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph { + producerConsumer() + }.asCPointer(), staticCFunction { it -> + val result = DetachedObjectGraph Unit>>(it).attach() + result.second(result.first) + }) +} + +data class QueryResult(val json: Map?, val error: String?) fun main(args: Array) { autoreleasepool { @@ -11,53 +29,97 @@ fun main(args: Array) { } } +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(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, + 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) } } -