{"id":49830811,"url":"https://github.com/swifdroid/jni-kit","last_synced_at":"2026-05-13T20:48:56.063Z","repository":{"id":294253523,"uuid":"605946583","full_name":"swifdroid/jni-kit","owner":"swifdroid","description":"📦 The best JNI wrapper ever made","archived":false,"fork":false,"pushed_at":"2026-01-22T05:07:39.000Z","size":213,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-05-13T20:48:50.810Z","etag":null,"topics":["java","jni","swift","wrapper"],"latest_commit_sha":null,"homepage":"https://docs.swifdroid.com/jni-kit/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/swifdroid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-02-24T08:48:27.000Z","updated_at":"2026-03-26T17:32:34.000Z","dependencies_parsed_at":"2025-05-19T15:58:31.189Z","dependency_job_id":"56fc1ed4-e3cf-4d26-bf68-61525992e24c","html_url":"https://github.com/swifdroid/jni-kit","commit_stats":null,"previous_names":["swifdroid/jni-kit"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/swifdroid/jni-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swifdroid%2Fjni-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swifdroid%2Fjni-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swifdroid%2Fjni-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swifdroid%2Fjni-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swifdroid","download_url":"https://codeload.github.com/swifdroid/jni-kit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swifdroid%2Fjni-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32999522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"ssl_error","status_checked_at":"2026-05-13T13:14:51.610Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","jni","swift","wrapper"],"created_at":"2026-05-13T20:48:55.442Z","updated_at":"2026-05-13T20:48:56.057Z","avatar_url":"https://github.com/swifdroid.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://avatars.githubusercontent.com/u/87046691?s=200\u0026v=4\"\u003e\n\u003c/p\u003e\n\n**JNIKit** is a foundation for convenient Swift-JNI development.\n\n## Installation\n\nAdd it to the `Package.swift` file of your project:\n```swift\n// package dependencies\n.package(url: \"https://github.com/swifdroid/jni-kit.git\", from: \"2.10.0\")\n\n// target dependencies\n.product(name: \"JNIKit\", package: \"jni-kit\")\n```\n\u003e [!NOTE]\n\u003e The most convenient way to start developing your Swift-JNI code is via [Swift Stream IDE](https://swift.stream) \n\u003e using templates it provides in a fully preconfigured development environment.\n\n## Preambula\n\nLet's talk about declaring JNI methods on the Swift side with `@_cdecl`. \n\n\u003e [!IMPORTANT]\n\u003e `@_cdecl` naming convention is important as it follows JNI naming pattern `Java_\u003cpackage\u003e_\u003cclass\u003e_\u003cmethod\u003e`, where:  \n\u003e - `\u003cpackage\u003e` is the fully qualified package name with underscores instead of dots  \n\u003e - `\u003cclass\u003e` is the class name\n\u003e - `\u003cmethod\u003e` is the method name\n\nLet's say you have a package `com.mylib.somecode` which contains `AwesomeCode` class which contains exported `hello` method.  \n`@_cdecl` for it will be:\n```swift\n@_cdecl(\"Java_com_mylib_somecode_AwesomeCode_hello\")\n```\n\nMethod arguments vary depending on your class and method declaration.\n\n**The first** argument is required and is always a pointer to the JNI environment, which is used to interact with the JVM.\n\n```swift\nenvPointer: UnsafeMutablePointer\u003cJNIEnv?\u003e\n```\n\n**The second** argument is required and depends on whether the method is static or not.\n\nSo it could be\n```swift\nthizRef: jobject\n```\nif the method is **instance-based (non-static)**, in which case it will pass the instance from which it was called.\n\nOr it could be\n```swift\nclazzRef: jobject\n```\nif method was `static` so it will pass just a reference to the Java class.\n\n**The next** arguments are optional and directly represent the arguments declared in the Java/Kotlin method.\n\n**The return** type can be empty (`void`) or can return a value (even an optional one).\n\nThe method can't be marked `async`, as it is not supported by `@_cdecl`. However, it is still possible to run asynchronous code in Swift if the original Java method was running on a **non-UI** thread.\n\nJNI types, how they represented in Swift:\n\n| JNI      | Swift  |\n|----------|--------|\n| jbyte    | Int8   |\n| jshort   | Int16  |\n| jint     | Int32  |\n| jlong    | Int64  |\n| jchar    | UInt16 |\n| jboolean | Bool   |\n| jfloat   | Float  |\n| jdouble  | Double |\n\nAny other Swift types are `jobject`.  \nAlso `jclass`, `jstring`, and `jarray` are typealiases for `jobject`.\n\n\u003e [!NOTE]\n\u003e Wrapping up: first two arguments are required and passed into Swift automatically by JVM-JNI mechanism, while third and subsequent arguments are actually arguments from your Java/Kotlin method.\n\n## The beginning\n\nRequired imports\n```swift\nimport JNIKit\nimport Android\n```\n\nYour first task is to initialize the JVM connection.\n\nFor that, you can declare a JNI initialization method like this:\n\n```swift\n@_cdecl(\"Java_com_mylib_mypackage_MyClass_initialize\")\npublic func initialize(\n    // pointer to the JNI environment\n    // which is used to interact with the JVM\n    envPointer: UnsafeMutablePointer\u003cJNIEnv?\u003e,\n    // reference to the Java class\n    // or could be `thizRef` for the call from the instance\n    clazzRef: jobject,\n    // optional, but recommended if you don't have a `thizRef`\n    callerRef: jobject\n) {\n    // Initialize JVM\n    let jvm = envPointer.jvm()\n    JNIKit.shared.initialize(with: jvm)\n}\n```\nAt this point, your JNI connection is ready to use from the Swift side.\n\nBut I also highly recommend caching the class loader instance by taking it from the `thizRef` or `callerRef` objects:\n```swift\n// Access current environment\nlet localEnv = JEnv(envPointer)\n\n// Convert caller's local ref into global ref\nlet callerBox = callerRef.box(localEnv)\n\n// Defer block to clean up local references\ndefer {\n    // Releases local ref to caller object\n    localEnv.deleteLocalRef(callerRef)\n}\n\n// Initialize `JObject` from boxed global reference to the caller object\nguard let callerObject = callerBox?.object() else { return }\n\n// Cache the class loader from the caller object\n// It is important for loading non-system classes later\n// e.g. your own Java/Kotlin classes\nif let classLoader = callerObject.getClassLoader(localEnv) {\n    JNICache.shared.setClassLoader(classLoader)\n    logger.info(\"🚀 class loader cached successfully\")\n}\n```\n\nWhy do you need the class loader instance? Because without it, JNIKit will use the system-wide class loader, which can't load dynamically added classes like those from your app or your app's Gradle dependencies.  \nSo, it is a really important step.\n\n## Cache\n\nWhen working with JNI, it is important to minimize roundtrips to retrieve the same resources repeatedly. The `JNICache` implementation handles this by automatically storing `jclass` instances (by name), methodIDs, and fieldIDs for classes you have already used. You don't need to do anything for this; it caches these things automatically under the hood.\n\n## Class\n\nLoading a class is the first thing you will face.\n\nThere are a few ways to load it.\n\n#### Via `env`\n\n```swift\nlet env = JEnv.current()!\nlet clazz = env.findClass(\"com/mylib/mypackage/MyClass\")\n```\nThis way you will use the system's class loader, which will fail loading your dynamically loaded class. It should be used only for fundamental classes like `java/lang/Object`.\n\n#### Via `classLoader`\n\nThe goal is to retrieve the application's class loader, which has access to dynamically loaded classes.\n\nIn pure Java you could retrieve a class loader from any `jobject`, as was shown above in the initialize method.\n\nIn Android the class loader is available in `ApplicationContext` or `ActivityContext`, as well as in any `jobject` of course, but there is a shorter convenience getter like `context.getClassLoader()`.\n\nThe usage is as simple as\n```swift\nlet clazz = classLoader.loadClass(\"com/mylib/mypackage/MyClass\")\n```\n\n#### Via `cached` instance\n\nThe preferred way is to use a convenience `JClass` method:\n\n```swift\nlet clazz = JClass.load(\"com/mylib/mypackage/MyClass\")\n```\nThis way it tries to get it first from cache, then from the cached class loader, and if there is no cached class loader, then from the system's class loader.\n\n##### Class names\n\nAs you can see above, the class name is represented as a `String`, but actually it is not. The `JClassName` object contains the class name in all possible forms: dotted, slashed, and even just its pure name.\n\nYou can predefine classnames as constants in the code for easy reuse and typo safety:\n```swift\nlet class1 = JClassName(\"com/mylib/mypackage/MyClass1\")\nlet class2 = JClassName(\"com/mylib/mypackage/MyClass2\")\n```\nand then pass it like this:\n```swift\nJClass.load(class1)\n```\nor even put it into a `JClassName` extension:\n```swift\nextension JClassName {\n    static let class1 = JClassName(\"com/mylib/mypackage/MyClass1\")\n    static let class2 = JClassName(\"com/mylib/mypackage/MyClass2\")\n}\n```\nand then use it anywhere like this:\n```swift\nJClass.load(.class1)\n```\n\n## Environment\n\nEnvironment is the real bridge for any JNI call which in JNIKit's implementation is hidden under the hood but can also be used directly if needed.\n\nThe environment pointer is wrapped into convenient `JEnv` object.\n\nRetrieve it this way:\n```swift\nlet env = JEnv.current()\n```\n\n\u003e [!IMPORTANT]\n\u003e Each instance works only in the thread where it was retrieved, so call `JEnv.current()` again if you switched thread or moved into `Task {}`\n\nThe possibilities of `JEnv` are a huge topic to describe in detail. I would say if you know what you want from it you will get it.\n\nLet's describe it in short, what it provides:\n- creating/deleting object references\n- converting local ref into global ref\n- finding and definig classes\n- throwing and handling exceptions\n- objects creation, allocation, comparison\n- looking up methods and fields of object\n- calling object instance methods\n- calling class static methods\n- getting and setting object fields\n- strings and byte buffers\n- etc.\n\nAll its methods are ergonomically wrapped for easy use with Swift types and convenience types like `JClass`, `JClassName`, `JObject`, `JMethodId`, `JFieldId`, \n\n### How to create a new object\n\nJNIKit provides the `JObject` class, which holds a reference to a Java object.\n\n#### Construct JObject with no arguments\n\nConvenient way with `clazz`:\n```swift\nguard\n    let clazz = JClass.load(\"com/mylib/mypackage/MyClass\"),\n    let object = clazz.newObject()\nelse { return }\n```\nMore explicit way with `env`:\n```swift\nguard\n    let env = JEnv.current(),\n    let clazz = JClass.load(\"com/mylib/mypackage/MyClass\"),\n    let methodId = clazz.methodId(\n        env: env,\n        name: \"\u003cinit\u003e\",\n        signature: .returning(.void)\n    ),\n    let object = env.newObject(\n        clazz: clazz,\n        constructor: methodId\n    )\nelse { return }\n```\n\n#### Construct JObject with arguments\n\nArguments have to be listed in both `methodId` and `newObject` in case of creating with `env`.\n\nLet's assume a class constructor expects `jint`, `jfloat`, and `jobject` (which is `java/lang/String`).\n\nConvenient way with `clazz`:\n```swift\nlet object = clazz.newObject(\n    123,\n    1.23,\n    \"Hello\" // or \"Hello\".wrap().signedAsString()\n)\n```\n\nExplicit way with `env`:\n```swift\nlet methodId = clazz.methodId(\n    env: env,\n    name: \"\u003cinit\u003e\",\n    signature: .init(\n        .int,\n        .float,\n        .object(\"java/lang/String\"),\n        returning: .void // optional, .void by default\n    )\n)\nlet object = env.newObject(\n    clazz: clazz,\n    constructor: methodId,\n    args: [\n        123, // Int32 -\u003e jint\n        1.23, // Float -\u003e jfloat\n        \"Hello\".wrap()!.object // JObject -\u003e jstring\n    ]\n)\n```\n\nThe constructed object is now a `JObject` that holds a global reference to the instance and a global reference to its class. Keep this object for as long as you need it.\n\n\u003e [!NOTE]\n\u003e **When `JObject` is deinitialized, its underlying `jobject` reference is released automatically.**\n\n\u003e [!WARNING]\n\u003e Do not delete the underlying `jobject` reference manually, and do not use `jobject` from `JObject` anywhere outside.\n\n### How to call an instance method\n\nFirst of all, you need to know that the method call name depends on what type your method returns:\n\n| Name              | Return type |\n|-------------------|-------------|\n| callObjectMethod  | JObject     |\n| callBooleanMethod | Bool        |\n| callByteMethod    | Int8        |\n| callCharMethod    | UInt16      |\n| callShortMethod   | Int16       |\n| callIntMethod     | Int32       |\n| callLongMethod    | Int64       |\n| callFloatMethod   | Float       |\n| callDoubleMethod  | Double      |\n| callVoidMethod    | Void        |\n\nThen you have a choice of wether to call it on `env` which is a bit more complex or call it on an `object` much shorter convenience way.\n\n#### Calling method that returns an object\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet methodId = clazz.methodId(\n    env: env,\n    name: \"getSomeObject\",\n    signature: .returning(.object(\"com/mylib/mypackage/SomeObject\"))\n)\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = env.callObjectMethod(\n    object: object,\n    methodId: methodId,\n    returningClass: returningClazz\n)\n```\n\nThe way with `object`:\n```swift\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = object.callObjectMethod(\n    name: \"getSomeObject\",\n    returningClass: returningClazz\n)\n```\nSo the difference is that you don't have to get the `methodID` yourself, as it is handled automatically under the hood. You also have the option to not pass the `env`.\n\u003e [!NOTE]\n\u003e The way with `object` is always shorter than with `env`.\n\n#### Calling method that pass an object and returns nothing\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet methodId = clazz.methodId(\n    env: env,\n    name: \"setSomeObject\",\n    signature: .init(\n        .object(\"com/mylib/mypackage/SomeObject\"),\n        returning: .void // optional, .void by default\n    )\n)\nlet resultObject = env.callVoidMethod(\n    object: object,\n    methodId: methodId,\n    args: [someObject.object]\n)\n```\n\nThe way with `object`:\n```swift\nobject.callVoidMethod(\n    name: \"setSomeObject\",\n    args: someObject.signed(as: \"com/mylib/mypackage/SomeObject\")\n    // or just someObject if you sure that automatic class is correct\n)\n```\nIt is even shorter than the getter.\n\n#### Calling method that pass a string and returns nothing\nThe way with `object`:\n```swift\nobject.callVoidMethod(\n    name: \"setString\",\n    args: \"Hello\" // or \"Hello\".wrap().signedAsString()\n    // or \"Hello\".signedAsCharSequence()\n)\n// String is always signed as java/lang/String by default\n```\n\n### How to get a field value\n\nThe principle is the same, but you need a fieldID and can't pass any arguments. The signature is needed only if the field returns an object.\n\n#### Getting an object\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet fieldId = object.clazz.fieldId(\n    env: env,\n    name: \"someField\",\n    signature: .object(\"com/mylib/mypackage/SomeObject\")\n)\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = env.getObjectField(\n    object: object,\n    fieldId,\n    clazz: returningClazz)\n```\n\nThe way with `object`:\n```swift\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nobject.objectField(name: \"someField\", returningClass: returningClazz)\n```\n\n#### Getting an Int\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet fieldId = object.clazz.fieldId(\n    env: env,\n    name: \"numberField\",\n    signature: .int\n)\nlet resultInt = env.getIntField(object: object, fieldId)\n```\n\nThe way with `clazz`:\n```swift\nobject.intField(name: \"numberField\")\n```\n\n### How to set a field value\n\n#### Setting an object\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet fieldId = object.clazz.fieldId(\n    env: env,\n    name: \"someField\",\n    signature: .object(\"com/mylib/mypackage/SomeObject\")\n)\nenv.setObjectField(\n    object: object,\n    fieldId,\n    object.signed(as: \"com/mylib/mypackage/SomeObject\")\n)\n```\n\nThe way with `object`:\n```swift\nobject.objectField(\n    name: \"someField\",\n    object.signed(as: \"com/mylib/mypackage/SomeObject\")\n)\n```\n\n#### Setting an Int\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet fieldId = object.clazz.fieldId(\n    env: env,\n    name: \"numberField\",\n    signature: .int\n)\nlet resultInt = env.setIntField(object: object, fieldId, 123)\n```\n\nThe way with `object`:\n```swift\nobject.intField(name: \"numberField\", 123)\n```\n\n### How to call a static method\n\n#### Calling static method that returns an object\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet methodId = clazz.staticMethodId(\n    name: \"getSomeObject\",\n    signature: .returning(.object(\"com/mylib/mypackage/SomeObject\"))\n)\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = env.callStaticObjectMethod(\n    clazz: clazz,\n    methodId: methodId,\n    returningClass: returningClazz\n)\n```\n\nThe way with `clazz`:\n```swift\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = clazz.staticObjectMethod(\n    name: \"getSomeObject\",\n    returningClass: returningClazz\n)\n```\n\n### How to get static field value\n\nThe way with `env`:\n```swift\nlet env = JEnv.current()\nlet currentClazz = JClass.load(\"com/mylib/mypackage/MyClass\")\nlet fieldId = currentClazz.fieldId(env: env, name: \"someField\", signature: .object(\"com/mylib/mypackage/SomeObject\"))\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\nlet resultObject = env.getStaticObjectField(currentClazz, fieldId, clazz: returningClazz)\n```\n\nThe way with `clazz`:\n```swift\nlet currentClazz = JClass.load(\"com/mylib/mypackage/MyClass\")\nlet returningClazz = JClass.load(\"com/mylib/mypackage/SomeObject\")\ncurrentClazz.staticFieldId(name: String, signature: JSignatureItem)\n```\n\n## Arrays\n\nSwift array wrappers for Java types:\n\n| Swift Type | JNI Type | Swift Array                |\n|------------|----------|----------------------------|\n| `[Int8]`       | `jbyte[]`    | `JInt8Array` or `JByteArray`   |\n| `[Int16]`      | `jshort[]`   | `JInt16Array` or `JShortArray` |\n| `[Int32]`      | `jint[]`     | `JInt32Array` or `JIntArray`   |\n| `[Int64]`      | `jlong[]`    | `JInt64Array` or `JLongArray`  |\n| `[UInt16]`     | `jchar[]`    | `JUInt16Array` or `JCharArray` |\n| `[Bool]`       | `jboolean[]` | `JBoolArray`                 |\n| `[Float]`      | `jfloat[]`   | `JFloatArray`                |\n| `[Double]`     | `jdouble[]`  | `JDoubleArray`               |\n| `[Any]`        | `jobject[]`  | `JObjectArray`               |\n\n### Ways of initialization\n\nWith a Swift `Array`\n```swift\nJIntArray([1, 2, 3])\n```\n\nWith a Swift `InlineArray`\n```swift\nlet inlineArray: InlineArray\u003c5, Int32\u003e = [1, 2 ,3, 4, 5]\nJIntArray(inlineArray)\n```\n\nManually\n```swift\nlet intArray = JIntArray(length: 3)!\n#if os(Android)\nvar cArray: [Int32] = [1, 2, 3]\nJEnv.current()?.setIntArrayRegion(intArray.object.ref.ref, start: 0, length: Int32(intArray.length), buffer: \u0026cArray)\n#endif\n```\n\n### Reading\n\n#### Copy into a Swift `Array`\n```swift\nlet intArray: JIntArray = ...\nlet swiftArray: [Int32] = intArray.toArray()\n```\n\u003e ⚠️ Be careful with large arrays, copying them may consume a lot of memory.\n\n### Iteration\n\nUse this approach when working with large arrays:\n```swift\nlet intArray: JIntArray = ...\nfor value in intArray {\n    Log.d(\"value: \\(value)\")\n}\n```\n\n##  2D Arrays\n\nYou can pass and receive not only `[Int]`, but also `[[Int]]`, `[[Float]]`, and other similar types (listed above).\nThis is a common pattern in Java and is often used in Android APIs, for instance, `ColorStateList` expects nested arrays for its state definitions.\n\nThe naming convention is the same, just add `2D` in the end\n\n| Swift Type | JNI Type | Swift Array                |\n|------------|----------|----------------------------|\n| `[[Int8]]`       | `jbyte[][]`    | `JInt8Array2D` or `JByteArray2D`   |\n| `[[Int16]]`      | `jshort[][]`   | `JInt16Array2D` or `JShortArray2D` |\n| `[[Int32]]`      | `jint[][]`     | `JInt32Array2D` or `JIntArray2D`   |\n| `[[Int64]]`      | `jlong[][]`    | `JInt64Array2D` or `JLongArray2D`  |\n| `[[UInt16]]`     | `jchar[][]`    | `JUInt16Array2D` or `JCharArray2D` |\n| `[[Bool]]`       | `jboolean[][]` | `JBoolArray2D`                 |\n| `[[Float]]`      | `jfloat[][]`   | `JFloatArray2D`                |\n| `[[Double]]`     | `jdouble[][]`  | `JDoubleArray2D`               |\n\n### Initialization\nJust as easy as with 1D arrays:\n```swift\nlet intArray2d = JIntArray2D([[1, 2, 3], [4, 5, 6]])\n```\n\n### Reading\nEqually straightforward:\n```swift\nlet swiftArray: [[Int32]] = intArray2d.toArray()\n```\n\n### Iteration\nIteration is supported as well:\n```swift\nfor array in intArray2d {\n    Log.d(\"arrray: \\(array)\")\n}\n```\n\n### Passing Arrays as Arguments\n\nIf a JNI method or constructor accepts a 1D or 2D array,  \nyou can simply pass it directly, no extra work needed:\n```\nobject.callVoidMethod(name: \"send1DArray\", args: [1, 2, 3])\nobject.callVoidMethod(name: \"send2DArray\", args: [[1, 2, 3], [4, 5, 6]])\n```\n\n## Signature\n\nWe use it every time when discovering `methodID` or `fieldID`.\n\nFor methods it consists of two parts, the arguments types and the returning type:\n```swift\n// method with no arguments, returns Void\nJSignature(returning: .void)\n// method with no arguments, returns Int32\nJSignature(returning: .int)\n// method with no arguments, returns JObject\nJSignature(returning: .object(\"com/mylib/mypackage/SomeObject\"))\n// method with no arguments, returns String\nJSignature(returning: .object(JString.className))\n// or\nJSignature(returning: .object(JString.charSequenseClassName))\n// method with Int32 argument, returns Void\nJSignature(.int, returning: .void)\n// method with JObject argument, returns Void\nJSignature(.object(\"com/mylib/mypackage/SomeObject\"), returning: .void)\n// method with String argument, returns Void\nJSignature(.object(JString.className), returning: .void)\n// or\nJSignature(.object(JString.charSequenseClassName), returning: .void)\n```\n\nFor fields only returning type matters:\n```swift\n.int // for Int32\n.float // for Float\n// similar for the other primitive types\n.object(\"com/mylib/mypackage/SomeObject\") // for JObject\n.object(JString.className) // for String\n.object(JString.charSequenseClassName) // for String\n```\n\n## Signing objects\n\nWhen passing a `JObject` as a method argument, you have two options for specifying its type signature\n\n**Rely on automatic signature inference**, which uses the `JClass` associated with the `JObject`\n\nExample with a generic `JObject`:\n```swift\nlet object: JObject\nobject.callVoidMethod(name: \"setView\", args: object)\n```\nExample with a `JString`:\n```swift\nlet string = \"Hello\"\nobject.callVoidMethod(name: \"setView\", args: string) // signed as java/lang/String by default\n```\n\n**Manually sign the object with a specific class** using the `signed(as:)` method\n\nExample with a generic `JObject`:\n```swift\nlet object: JObject\nobject.callVoidMethod(name: \"setView\", args: object.signed(as: \"com/my/lib/SomeObject\"))\n```\nExample with a `JString` (signed as a `CharSequence`):\n```swift\nlet string = \"Hello\"\nobject.callVoidMethod(name: \"setView\", args: string.signedAsCharSequence())\n```\n\n## Wrapping Java/Kotlin class\n\nA common use case is to wrap an existing Java/Kotlin class into a convenient Swift class.\n\nThe following example demonstrates this using `java/util/Date`:\n\n\u003cdetails\u003e\n    \u003csummary\u003eJDate.swift\u003c/summary\u003e\n\n```swift\n// Example of Date object wrapper\n\n/// A classic example of how to wrap a Java object into a Swift class.\n/// \n/// Here we wrap `java.util.Date` object and provide some convenience methods.\npublic final class JDate: JObjectable, Sendable {\n    /// The JNI class name\n    public static let className: JClassName = \"java/util/Date\"\n\n    /// JNI global reference object wrapper, it contains class metadata as well.\n    public let object: JObject\n\n    /// Initializer for when you already have a `JObject` reference.\n    /// \n    /// This is useful when you receive a `Date` object from Java code.\n    public init (_ object: JObject) {\n        self.object = object\n    }\n\n    /// Allocates a `Date` object and initializes it so that it represents the time\n    /// at which it was allocated, measured to the nearest millisecond.\n    public init? () {\n        #if os(Android)\n        guard\n            // Access current environment\n            let env = JEnv.current(),\n            // It finds the `java.util.Date` class and loads it directly or from the cache\n            let clazz = JClass.load(Self.className),\n            // Call to create a new instance of `java.util.Date` and get a global reference to it\n            let global = clazz.newObject(env)\n        else { return nil }\n        // Store the object to access it from methods\n        self.object = global\n        #else\n        // For non-Android platforms, return nil\n        return nil\n        #endif\n    }\n\n    /// Allocates a `Date` object and initializes it to represent the specified number of milliseconds since the standard base time known as \"the epoch\", namely January 1, 1970, 00:00:00 GMT.\n    /// \n    /// - Parameter milliseconds: The number of milliseconds since January 1, 1970, 00:00:00 GMT.\n    public init? (_ milliseconds: Int64) {\n        #if os(Android)\n        guard\n            // Access current environment\n            let env = JEnv.current(),\n            // It finds the `java.util.Date` class and loads it directly or from the cache\n            let clazz = JClass.load(Self.className),\n            // Call to create a new instance of `java.util.Date`\n            // with `milliseconds` parameter and get a global reference to it\n            let global = clazz.newObject(env, args: milliseconds)\n        else { return nil }\n        // Store the object to access it from methods\n        self.object = global\n        #else\n        // For non-Android platforms, return nil\n        return nil\n        #endif\n    }\n\n    /// Returns the day of the week represented by this date.\n    public func day() -\u003e Int32? {\n        // Convenience call to `java.util.Date.getDay()`\n        object.callIntMethod(name: \"getDay\")\n    }\n\n    /// Returns the hour represented by this Date object.\n    public func hours() -\u003e Int32? {\n        // Convenience call to `java.util.Date.getHours()`\n        object.callIntMethod(name: \"getHours\")\n    }\n\n    /// Returns the number of minutes past the hour represented by this date\n    public func minutes() -\u003e Int32? {\n        // Convenience call to `java.util.Date.getMinutes()`\n        object.callIntMethod(name: \"getMinutes\")\n    }\n\n    /// Returns the number of seconds past the minute represented by this date.\n    public func seconds() -\u003e Int32? {\n        // Convenience call to `java.util.Date.getSeconds()`\n        object.callIntMethod(name: \"getSeconds\")\n    }\n\n    /// Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT for this date instance.\n    public func time() -\u003e Int32? {\n        // Convenience call to `java.util.Date.getTime()`\n        object.callIntMethod(name: \"getTime\")\n    }\n\n    /// Tests if this date is before the specified date.\n    public func before(_ date: JDate) -\u003e Bool {\n        // Convenience call to `java.util.Date.before(Date date)`\n        // which passes another `Date` object as a parameter\n        // and returns a boolean result\n        object.callBoolMethod(name: \"before\", args: date.object.signed(as: JDate.className)) ?? false\n    }\n\n    /// Tests if this date is after the specified date.\n    public func after(_ date: JDate) -\u003e Bool {\n        // Convenience call to `java.util.Date.after(Date date)`\n        // which passes another `Date` object as a parameter\n        // and returns a boolean result\n        object.callBoolMethod(name: \"after\", args: date.object.signed(as: JDate.className)) ?? false\n    }\n\n    /// Converts this java `Date` object to a Swift `Date`.\n    public func date() -\u003e Date? {\n        // Get milliseconds since epoch using `getTime` method\n        guard let time = time() else { return nil }\n        // Convert milliseconds to seconds and create a Swift `Date` object\n        return Date(timeIntervalSince1970: TimeInterval(time) / 1000.0)\n    }\n}\n```\n\u003c/details\u003e\n\n## Casting\n\nThere are two situations where you need to cast an object from one class to another.\n\n1. **When you want to treat an existing object as an instance of another class** (usually a parent) to call a method or access a field.\n\nThis is done using the `cast(to:)` method of `JObject`. It creates a proxy `JObject` of the target class but retains the same underlying JNI reference:\n```swift\nlet editText: EditText\nlet textView = editText.cast(to: TextView.className)\n```\n\n2. **When you need to pass an object to a method that expects a different class** (also usually a parent).\n\nThis is done using the `signed(as:)` method of `JObject`:\n```swift\nlet customView: CustomView\nobject.callVoidMethod(name: \"setView\", args: customView.signed(as: View.className))\n```\n\n## Optionals and NULL\n\nYou may have Java/Kotlin methods that accept nullable arguments. \nFor any `JObject`, you can pass Swift `nil`, which will be converted to JNI `NULL`:\n```swift\nlet object1: JObject\nlet object2: JObject? // this could be nil\nlet object3: JObject\nobject.callVoidMethod(name: \"setView\", args: object1, object2, object3)\n```\n\n## Primitive type objects\n\nPure primitive types can be passed as method arguments directly:\n```swift\nlet intValue: Int32 = 9.41\nlet floatValue: Float = 9.41\nlet doubleValue: Double = 9.41\nobject.callVoidMethod(name: \"setValues\", args: intValue, floatValue, doubleValue)\n```\nThese values cannot be `nil` and are always represented in the JNI signature as primitives.\n\nHowever, if the Java/Kotlin side expects full object types like `java.lang.Integer` or `java.lang.Long`, you need to use their Swift wrapper equivalents. \nRefer to the table below:\n| Swift Type | Swift Wrapper               | Java Type           |\n|------------|-----------------------------|---------------------|\n| Int8       | JInt8 (JByte)               | java/lang/Byte      |\n| Int16      | JInt16 (JShort)             | java/lang/Short     |\n| Int32      | JInt32 (JInt, JInteger)     | java/lang/Integer   |\n| Int64      | JInt64 (JLong)              | java/lang/Long      |\n| Bool       | JBool                       | java/lang/Boolean   |\n| Float      | JFloat                      | java/lang/Float     |\n| Double     | JDouble                     | java/lang/Double    |\n| UInt16     | JUInt16 (JChar, JCharacter) | java/lang/Character |\n\n**Usage Example:**\n```swift\nlet nilDoubleObject: JDouble? = nil\nlet doubleObject: JDouble? = 9.41\nlet double: Double = 9.41\nobject.callVoidMethod(name: \"sendDouble\", args: nilDoubleObject) // Object Double is NULL\nobject.callVoidMethod(name: \"sendDouble\", args: doubleObject) // Object Double is NOT NULL: 9.41\nobject.callVoidMethod(name: \"sendDouble\", args: double) // Primitive Double: 9.41\n```\n**Corresponding Kotlin Code**:\n```kotlin\nfun sendDouble(value: Double) {\n    Log.d(\"CHECK\", \"Primitive Double: $value\")\n}\nfun sendDouble(value: Double?) {\n    if (value != null) {\n        Log.d(\"CHECK\", \"Object Double is NOT NULL: $value\")\n    } else {\n        Log.d(\"CHECK\", \"Object Double is NULL\")\n    }\n}\n```\n\n## License\n\n**MIT License**\n\nCopyright (c) 2021 Mikhail Isaev\n\n## Contribution\n\nContributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.\n\nIf you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag \"enhancement\".\n\nDon't forget to give the project a star! Thanks again!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswifdroid%2Fjni-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswifdroid%2Fjni-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswifdroid%2Fjni-kit/lists"}