Sharing the mutable binary data (#2311)
This commit is contained in:
@@ -26,9 +26,9 @@ typealias StableObjPtr = StableRef<*>
|
||||
*
|
||||
* Any [StableRef] should be manually [disposed][dispose]
|
||||
*/
|
||||
data class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||
private val stablePtr: COpaquePointer,
|
||||
private val ref: T // Note: storing the reference itself to avoid unchecked casts.
|
||||
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
|
||||
public inline class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||
private val stablePtr: COpaquePointer
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -36,7 +36,7 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||
/**
|
||||
* Creates a handle for given object.
|
||||
*/
|
||||
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any), any)
|
||||
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any))
|
||||
|
||||
/**
|
||||
* Creates [StableRef] from given raw value.
|
||||
@@ -46,7 +46,6 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||
@Deprecated("Use CPointer<*>.asStableRef<T>() instead", ReplaceWith("ptr.asStableRef<T>()"))
|
||||
fun fromValue(value: COpaquePointer) = value.asStableRef<Any>()
|
||||
}
|
||||
|
||||
@Deprecated("Use .asCPointer() instead", ReplaceWith("this.asCPointer()"))
|
||||
val value: COpaquePointer get() = this.asCPointer()
|
||||
|
||||
@@ -66,12 +65,13 @@ data class StableRef<out T : Any> @PublishedApi internal constructor(
|
||||
/**
|
||||
* Returns the object this handle was [created][StableRef.create] for.
|
||||
*/
|
||||
fun get() = this.ref
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun get() = derefStablePointer(this.stablePtr) as T
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to [StableRef] this opaque pointer produced by [StableRef.asCPointer].
|
||||
*/
|
||||
inline fun <reified T : Any> CPointer<*>.asStableRef() =
|
||||
StableRef<T>(this, derefStablePointer(this) as T)
|
||||
inline fun <reified T : Any> CPointer<*>.asStableRef(): StableRef<T> = StableRef<T>(this)
|
||||
.also { it.get() as T }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
depends = CFNetwork CoreFoundation Foundation Security darwin posix
|
||||
language = Objective-C
|
||||
package = platform.Contacts
|
||||
headers = Contacts/Contacts.h
|
||||
|
||||
headerFilter = Contacts/**
|
||||
|
||||
compilerOpts = -framework Contacts
|
||||
linkerOpts = -framework Contacts
|
||||
@@ -414,14 +414,14 @@ void KRefSharedHolder::initRefOwner() {
|
||||
}
|
||||
|
||||
void KRefSharedHolder::verifyRefOwner() const {
|
||||
// Note: checking for 'permanentOrFrozen()' and retrieving 'type_info()'
|
||||
// Note: checking for 'shareable()' and retrieving 'type_info()'
|
||||
// are supposed to be correct even for unowned object.
|
||||
if (owner_ != memoryState) {
|
||||
// Initialized runtime is required to throw the exception below
|
||||
// or to provide proper execution context for shared objects:
|
||||
if (memoryState == nullptr) Kotlin_initRuntimeIfNeeded();
|
||||
|
||||
if (!obj_->container()->permanentOrFrozen()) {
|
||||
if (!obj_->container()->shareable()) {
|
||||
// TODO: add some info about the owner.
|
||||
ThrowIllegalObjectSharingException(obj_->type_info(), obj_);
|
||||
}
|
||||
@@ -648,7 +648,7 @@ void MarkGray(ContainerHeader* start) {
|
||||
auto childContainer = ref->container();
|
||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||
|
||||
if (!childContainer->permanentOrFrozen()) {
|
||||
if (!childContainer->shareable()) {
|
||||
childContainer->decRefCount<false>();
|
||||
toVisit.push_front(childContainer);
|
||||
}
|
||||
@@ -675,7 +675,7 @@ void ScanBlack(ContainerHeader* start) {
|
||||
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
|
||||
auto childContainer = ref->container();
|
||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||
if (!childContainer->permanentOrFrozen()) {
|
||||
if (!childContainer->shareable()) {
|
||||
childContainer->incRefCount<false>();
|
||||
if (useColor) {
|
||||
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
||||
@@ -744,7 +744,7 @@ void Scan(ContainerHeader* container) {
|
||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||
auto childContainer = ref->container();
|
||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||
if (!childContainer->permanentOrFrozen()) {
|
||||
if (!childContainer->shareable()) {
|
||||
Scan(childContainer);
|
||||
}
|
||||
});
|
||||
@@ -764,7 +764,7 @@ void CollectWhite(MemoryState* state, ContainerHeader* start) {
|
||||
if (ref == nullptr) return;
|
||||
auto childContainer = ref->container();
|
||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||
if (childContainer->permanentOrFrozen()) {
|
||||
if (childContainer->shareable()) {
|
||||
UpdateRef(location, nullptr);
|
||||
} else {
|
||||
toVisit.push_front(childContainer);
|
||||
@@ -787,6 +787,7 @@ inline void AddRef(ContainerHeader* header) {
|
||||
IncrementRC<false>(header);
|
||||
break;
|
||||
case CONTAINER_TAG_FROZEN:
|
||||
case CONTAINER_TAG_ATOMIC:
|
||||
IncrementRC<true>(header);
|
||||
break;
|
||||
default:
|
||||
@@ -806,6 +807,7 @@ inline void Release(ContainerHeader* header, bool useCycleCollector) {
|
||||
DecrementRC<false>(header, useCycleCollector);
|
||||
break;
|
||||
case CONTAINER_TAG_FROZEN:
|
||||
case CONTAINER_TAG_ATOMIC:
|
||||
DecrementRC<true>(header, useCycleCollector);
|
||||
break;
|
||||
default:
|
||||
@@ -1537,7 +1539,7 @@ bool hasExternalRefs(ContainerHeader* container, ContainerHeaderSet* visited) {
|
||||
bool result = container->refCount() != 0;
|
||||
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
||||
auto child = ref->container();
|
||||
if (!child->permanentOrFrozen() && (visited->find(child) == visited->end())) {
|
||||
if (!child->shareable() && (visited->find(child) == visited->end())) {
|
||||
result |= hasExternalRefs(child, visited);
|
||||
}
|
||||
});
|
||||
@@ -1560,7 +1562,7 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
||||
if (!checked) {
|
||||
hasExternalRefs(container, &visited);
|
||||
} else {
|
||||
if (!container->permanentOrFrozen()) {
|
||||
if (!container->shareable()) {
|
||||
container->decRefCount<false>();
|
||||
MarkGray<false>(container);
|
||||
auto bad = hasExternalRefs(container, &visited);
|
||||
@@ -1604,7 +1606,7 @@ void depthFirstTraversal(ContainerHeader* container, bool* hasCycles,
|
||||
return;
|
||||
}
|
||||
ContainerHeader* objContainer = obj->container();
|
||||
if (!objContainer->permanentOrFrozen()) {
|
||||
if (!objContainer->shareable()) {
|
||||
// Marked GREY, there's cycle.
|
||||
if (objContainer->seen()) *hasCycles = true;
|
||||
|
||||
@@ -1647,7 +1649,7 @@ void freezeAcyclic(ContainerHeader* rootContainer) {
|
||||
current->freeze();
|
||||
traverseContainerReferredObjects(current, [current, &queue](ObjHeader* obj) {
|
||||
ContainerHeader* objContainer = obj->container();
|
||||
if (!objContainer->permanentOrFrozen()) {
|
||||
if (!objContainer->shareable()) {
|
||||
if (objContainer->marked())
|
||||
queue.push_back(objContainer);
|
||||
}
|
||||
@@ -1666,7 +1668,7 @@ void freezeCyclic(ContainerHeader* rootContainer, const KStdVector<ContainerHead
|
||||
reversedEdges.emplace(current, KStdVector<ContainerHeader*>(0));
|
||||
traverseContainerReferredObjects(current, [current, &queue, &reversedEdges](ObjHeader* obj) {
|
||||
ContainerHeader* objContainer = obj->container();
|
||||
if (!objContainer->permanentOrFrozen()) {
|
||||
if (!objContainer->shareable()) {
|
||||
if (objContainer->marked())
|
||||
queue.push_back(objContainer);
|
||||
reversedEdges.emplace(objContainer, KStdVector<ContainerHeader*>(0)).first->second.push_back(current);
|
||||
@@ -1698,7 +1700,7 @@ void freezeCyclic(ContainerHeader* rootContainer, const KStdVector<ContainerHead
|
||||
for (auto* container : component) {
|
||||
totalCount += container->refCount();
|
||||
traverseContainerReferredObjects(container, [&internalRefsCount](ObjHeader* obj) {
|
||||
if (!obj->container()->permanentOrFrozen())
|
||||
if (!obj->container()->shareable())
|
||||
++internalRefsCount;
|
||||
});
|
||||
}
|
||||
@@ -1746,7 +1748,7 @@ void FreezeSubgraph(ObjHeader* root) {
|
||||
// First check that passed object graph has no cycles.
|
||||
// If there are cycles - run graph condensation on cyclic graphs using Kosoraju-Sharir.
|
||||
ContainerHeader* rootContainer = root->container();
|
||||
if (rootContainer->permanentOrFrozen()) return;
|
||||
if (rootContainer->shareable()) return;
|
||||
|
||||
// Do DFS cycle detection.
|
||||
bool hasCycles = false;
|
||||
@@ -1872,4 +1874,11 @@ KBoolean Konan_ensureAcyclicAndSet(ObjHeader* where, KInt index, ObjHeader* what
|
||||
return true;
|
||||
}
|
||||
|
||||
void Kotlin_Any_share(ObjHeader* obj) {
|
||||
auto container = obj->container();
|
||||
if (container->shareable()) return;
|
||||
RuntimeCheck(container->objectCount() == 1, "Must be a single object container");
|
||||
container->makeShareable();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -27,14 +27,16 @@ typedef enum {
|
||||
CONTAINER_TAG_NORMAL = 0,
|
||||
// Container is frozen, could only refer to other frozen objects.
|
||||
// Refcounter update is atomics.
|
||||
CONTAINER_TAG_FROZEN = 1,
|
||||
CONTAINER_TAG_FROZEN = 1 | 1, // shareable
|
||||
// Stack container, no need to free, children cleanup still shall be there.
|
||||
CONTAINER_TAG_STACK = 2,
|
||||
// Those container tags shall not be refcounted.
|
||||
// Permanent container, cannot refer to non-permanent containers, so no need to cleanup those.
|
||||
CONTAINER_TAG_PERMANENT = 2,
|
||||
// Stack container, no need to free, children cleanup still shall be there.
|
||||
CONTAINER_TAG_STACK = 3,
|
||||
CONTAINER_TAG_PERMANENT = 3 | 1, // shareable
|
||||
// Atomic container, reference counter is atomically updated.
|
||||
CONTAINER_TAG_ATOMIC = 5 | 1, // shareable
|
||||
// Shift to get actual counter.
|
||||
CONTAINER_TAG_SHIFT = 2,
|
||||
CONTAINER_TAG_SHIFT = 3,
|
||||
// Actual value to increment/decrement container by. Tag is in lower bits.
|
||||
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
|
||||
// Mask for container type.
|
||||
@@ -83,10 +85,18 @@ struct ContainerHeader {
|
||||
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_FROZEN;
|
||||
}
|
||||
|
||||
inline void makeShareable() {
|
||||
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_ATOMIC;
|
||||
}
|
||||
|
||||
inline bool permanentOrFrozen() const {
|
||||
return tag() == CONTAINER_TAG_PERMANENT || tag() == CONTAINER_TAG_FROZEN;
|
||||
}
|
||||
|
||||
inline bool shareable() const {
|
||||
return (tag() & 1) != 0; // CONTAINER_TAG_PERMANENT || CONTAINER_TAG_FROZEN || CONTAINER_TAG_ATOMIC
|
||||
}
|
||||
|
||||
inline bool stack() const {
|
||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
||||
}
|
||||
|
||||
@@ -78,4 +78,8 @@ const void* Kotlin_Any_getTypeInfo(KConstRef obj) {
|
||||
return obj->type_info();
|
||||
}
|
||||
|
||||
void Kotlin_CPointer_CopyMemory(KNativePtr to, KNativePtr from, KInt count) {
|
||||
memcpy(to, from, count);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package kotlin.native.concurrent
|
||||
|
||||
import kotlin.native.internal.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
public class Continuation0(block: () -> Unit,
|
||||
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||
private val singleShot: Boolean = false): Function0<Unit> {
|
||||
|
||||
private val stable = StableRef.create(block)
|
||||
|
||||
init {
|
||||
freeze()
|
||||
}
|
||||
|
||||
public override operator fun invoke() {
|
||||
invoker(stable.asCPointer())
|
||||
if (singleShot) {
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public fun dispose() {
|
||||
assert(!singleShot)
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public class Continuation1<T1>(
|
||||
block: (p1: T1) -> Unit,
|
||||
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||
private val singleShot: Boolean = false) : Function1<T1, Unit> {
|
||||
|
||||
private val stable = StableRef.create(block)
|
||||
|
||||
init {
|
||||
freeze()
|
||||
}
|
||||
|
||||
public override operator fun invoke(p1: T1) {
|
||||
val args = StableRef.create(Pair(stable, p1))
|
||||
try {
|
||||
invoker(args.asCPointer())
|
||||
} finally {
|
||||
args.dispose()
|
||||
}
|
||||
if (singleShot) {
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public fun dispose() {
|
||||
assert(!singleShot)
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public class Continuation2<T1, T2>(
|
||||
block: (p1: T1, p2: T2) -> Unit,
|
||||
private val invoker: CPointer<CFunction<(COpaquePointer?) -> Unit>>,
|
||||
private val singleShot: Boolean = false) : Function2<T1, T2, Unit> {
|
||||
|
||||
private val stable = StableRef.create(block)
|
||||
|
||||
init {
|
||||
freeze()
|
||||
}
|
||||
|
||||
public override operator fun invoke(p1: T1, p2: T2) {
|
||||
val args = StableRef.create(Triple(stable, p1, p2))
|
||||
try {
|
||||
invoker(args.asCPointer())
|
||||
} finally {
|
||||
args.dispose()
|
||||
}
|
||||
if (singleShot) {
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
assert(!singleShot)
|
||||
stable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public fun COpaquePointer.callContinuation0() {
|
||||
val single = this.asStableRef<() -> Unit>()
|
||||
single.get()()
|
||||
}
|
||||
|
||||
public fun <T1> COpaquePointer.callContinuation1() {
|
||||
val pair = this.asStableRef<Pair<StableRef<(T1) -> Unit>, T1>>().get()
|
||||
pair.first.get()(pair.second)
|
||||
}
|
||||
|
||||
public fun <T1, T2> COpaquePointer.callContinuation2() {
|
||||
val triple = this.asStableRef<Triple<StableRef<(T1, T2) -> Unit>, T1, T2>>().get()
|
||||
triple.first.get()(triple.second, triple.third)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package kotlin.native.concurrent
|
||||
|
||||
import kotlin.native.internal.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
@SymbolName("Kotlin_Any_share")
|
||||
external private fun Any.share()
|
||||
|
||||
@SymbolName("Kotlin_CPointer_CopyMemory")
|
||||
external private fun CopyMemory(to: COpaquePointer?, from: COpaquePointer?, count: Int)
|
||||
|
||||
/**
|
||||
* Mutable concurrently accessible data buffer. Could be accessed from several workers simulteniously.
|
||||
*/
|
||||
@Frozen
|
||||
public class MutableData constructor(capacity: Int = 16) {
|
||||
init {
|
||||
if (capacity <= 0) throw IllegalArgumentException()
|
||||
// Instance of MutableData is shared.
|
||||
share()
|
||||
}
|
||||
|
||||
private var buffer = ByteArray(capacity).apply { share() }
|
||||
private var size_ = 0
|
||||
private val lock = Lock()
|
||||
|
||||
private fun resizeDataLocked(newSize: Int): Int {
|
||||
assert(newSize >= size)
|
||||
if (newSize > buffer.size) {
|
||||
val actualSize = maxOf(buffer.size * 3 / 2 + 1, newSize)
|
||||
val newBuffer = ByteArray(actualSize)
|
||||
buffer.copyRangeTo(newBuffer, 0, size, 0)
|
||||
newBuffer.share()
|
||||
buffer = newBuffer
|
||||
}
|
||||
val position = size
|
||||
size_ = newSize
|
||||
return position
|
||||
}
|
||||
|
||||
/**
|
||||
* Current data size, may concurrently change later on.
|
||||
*/
|
||||
public val size: Int
|
||||
get() = size_
|
||||
|
||||
/**
|
||||
* Reset the data buffer, makings its size 0.
|
||||
*/
|
||||
public fun reset() = locked(lock) {
|
||||
size_ = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends data to the buffer.
|
||||
*/
|
||||
public fun append(data: MutableData) = locked(lock) {
|
||||
val toCopy = data.size
|
||||
val where = resizeDataLocked(size + toCopy)
|
||||
data.copyInto(buffer, 0, toCopy, where)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends byte array to the buffer.
|
||||
*/
|
||||
public fun append(data: ByteArray, fromIndex: Int = 0, toIndex: Int = data.size): Unit = locked(lock) {
|
||||
if (fromIndex > toIndex)
|
||||
throw IndexOutOfBoundsException("$fromIndex is bigger than $toIndex")
|
||||
if (toIndex == toIndex) return
|
||||
val where = resizeDataLocked(this.size + (toIndex - fromIndex))
|
||||
data.copyRangeTo(buffer, fromIndex, toIndex, where)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends C data to the buffer, if `data` is null or `count` is non-positive - return.
|
||||
*/
|
||||
public fun append(data: COpaquePointer?, count: Int): Unit = locked(lock) {
|
||||
if (data == null || count <= 0) return
|
||||
val where = resizeDataLocked(this.size + count)
|
||||
buffer.usePinned {
|
||||
it -> CopyMemory(it.addressOf(where), data, count)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies range of mutable data to the byte array.
|
||||
*/
|
||||
public fun copyInto(output: ByteArray, destinationIndex: Int, startIndex: Int, endIndex: Int): Unit = locked(lock) {
|
||||
buffer.copyRangeTo(output, startIndex, endIndex, destinationIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a byte from the mutable data.
|
||||
*
|
||||
* @Throws IndexOutOfBoundsException if index is beyond range.
|
||||
*/
|
||||
public operator fun get(index: Int): Byte = locked(lock) {
|
||||
// index < 0 is checked below by array access.
|
||||
if (index >= size)
|
||||
throw IndexOutOfBoundsException("$index is not below $size")
|
||||
buffer[index]
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes provided block under lock with raw pointer to the data stored in the buffer.
|
||||
* Block is executed under the spinlock, and must be short.
|
||||
*/
|
||||
public fun <R> withPointerLocked(block: (COpaquePointer, dataSize: Int) -> R) = locked(lock) {
|
||||
buffer.usePinned {
|
||||
it -> block(it.addressOf(0), size)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes provided block under lock with the raw data buffer.
|
||||
* Block is executed under the spinlock, and must be short.
|
||||
*/
|
||||
public fun <R> withBufferLocked(block: (array: ByteArray, dataSize: Int) -> R) = locked(lock) {
|
||||
block(buffer, size)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,15 @@ package sample.objc
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.AppKit.*
|
||||
import platform.Contacts.CNContactStore
|
||||
import platform.Contacts.CNEntityType
|
||||
import platform.Foundation.*
|
||||
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
|
||||
import platform.darwin.dispatch_get_main_queue
|
||||
import platform.darwin.dispatch_sync_f
|
||||
import platform.posix.memcpy
|
||||
import kotlin.native.concurrent.*
|
||||
|
||||
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
|
||||
dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph {
|
||||
@@ -23,8 +26,51 @@ inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline produce
|
||||
})
|
||||
}
|
||||
|
||||
inline fun mainContinuation(singleShot: Boolean = true, noinline block: () -> Unit) = Continuation0(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation0()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation0()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
inline fun <T1> mainContinuation(singleShot: Boolean = true, noinline block: (T1) -> Unit) = Continuation1(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation1<T1>()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation1<T1>()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
inline fun <T1, T2> mainContinuation(singleShot: Boolean = true, noinline block: (T1, T2) -> Unit) = Continuation2(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation2<T1, T2>()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation2<T1, T2>()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
|
||||
data class QueryResult(val json: Map<String, *>?, val error: String?)
|
||||
|
||||
private fun MutableData.asNSData() = this.withPointerLocked { it, size ->
|
||||
val result = NSMutableData.create(length = size.convert())!!
|
||||
memcpy(result.mutableBytes, it, size.convert())
|
||||
result
|
||||
}
|
||||
|
||||
private fun MutableData.asJSON(): Map<String, *>? =
|
||||
NSJSONSerialization.JSONObjectWithData(this.asNSData(), 0, null) as? Map<String, *>
|
||||
|
||||
fun main() {
|
||||
autoreleasepool {
|
||||
runApp()
|
||||
@@ -45,7 +91,7 @@ private fun runApp() {
|
||||
|
||||
class Controller : NSObject() {
|
||||
private var index = 1
|
||||
private var httpDelegate = HttpDelegate()
|
||||
private val httpDelegate = HttpDelegate()
|
||||
|
||||
@ObjCAction
|
||||
fun onClick() {
|
||||
@@ -63,16 +109,28 @@ class Controller : NSObject() {
|
||||
NSApplication.sharedApplication().stop(this)
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun onRequest() {
|
||||
val addressBookRef = CNContactStore()
|
||||
addressBookRef.requestAccessForEntityType(CNEntityType.CNEntityTypeContacts, mainContinuation {
|
||||
granted, error ->
|
||||
appDelegate.contentText.string = if (granted)
|
||||
"Access granted!"
|
||||
else
|
||||
"Access denied: $error"
|
||||
})
|
||||
}
|
||||
|
||||
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
|
||||
private val asyncQueue = NSOperationQueue()
|
||||
internal val receivedData = NSMutableData()
|
||||
private val receivedData = MutableData()
|
||||
|
||||
init {
|
||||
freeze()
|
||||
}
|
||||
|
||||
fun fetchUrl(url: String) {
|
||||
receivedData.setLength(0)
|
||||
receivedData.reset()
|
||||
val session = NSURLSession.sessionWithConfiguration(
|
||||
NSURLSessionConfiguration.defaultSessionConfiguration(),
|
||||
this,
|
||||
@@ -83,7 +141,7 @@ class Controller : NSObject() {
|
||||
|
||||
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
|
||||
initRuntimeIfNeeded()
|
||||
receivedData.appendData(didReceiveData)
|
||||
receivedData.append(didReceiveData.bytes, didReceiveData.length.convert())
|
||||
}
|
||||
|
||||
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
|
||||
@@ -94,10 +152,7 @@ class Controller : NSObject() {
|
||||
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
|
||||
)
|
||||
else -> QueryResult(receivedData.asJSON(), null)
|
||||
}, { result: QueryResult ->
|
||||
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
|
||||
appDelegate.canClick = true
|
||||
@@ -157,6 +212,14 @@ class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
action = NSSelectorFromString("onQuit")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonQuit)
|
||||
|
||||
val buttonRequest = NSButton(NSMakeRect(230.0, 10.0, 100.0, 40.0)).apply {
|
||||
title = "Request"
|
||||
target = controller
|
||||
action = NSSelectorFromString("onRequest")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonRequest)
|
||||
|
||||
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
|
||||
string = "Press 'Click' to start fetching"
|
||||
verticallyResizable = false
|
||||
|
||||
Reference in New Issue
Block a user