Slide 1

Slide 1 text

@imk 2 o Concurrency in JavaScriptCore.framework potatotips # 8 3

Slide 2

Slide 2 text

2 ⾃⼰紹介 ίόϠγ Ϣ΢Πν iOS Engineer@MedPeer Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. @imk 2 o

Slide 3

Slide 3 text

3 JavaScriptCore.framework使ったことあります?🙋 Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 4

Slide 4 text

基本のおさらい JavaScriptCore.frameworkͱ͸ʁ

Slide 5

Slide 5 text

5 JavaScriptCore.frameworkの今北産業 ΞϓϦʹJavaScriptίʔυͷ࣮ߦ؀ڥΛ૊ΈࠐΊΔϑϨʔϜϫʔΫ Swift/Objective-Cͷܕ΍ؔ਺ΛΤΫεϙʔτͯ͠ར༻Մೳ ΞϓϦͷػೳΛJSͰ੍ޚͨ͠ΓɺৼΔ෣͍ΛΧελϚΠζͰ͖Δ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 6

Slide 6 text

6 JavaScriptコードの実⾏⽅法 1. JavaScriptCore.frameworkΛΠϯϙʔτ 2. JSContextΛ༻ҙ 3. evaluateScript()Λίʔϧ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. import JavaScriptCore let jsContext = JSContext() jsContext.evaluateScript("JavaScript code here...")

Slide 7

Slide 7 text

7 JSValueについて JS؀ڥ্ͷ஋Λද͢ΦϒδΣΫτ Swiftͱ૬ޓʹσʔλΛ΍ΓͱΓ͢Δཁ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 8

Slide 8 text

8 Swiftオブジェクトのエクスポート 1. JSExportʹ४ڌͨ͠@objc protocolͰΤΫεϙʔτϝιουΛఆٛ 2. ্هͷϓϩτίϧΛΫϥεͰ࣮૷ 3. JSContext#setObject()ͰΤΫεϙʔτ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. @objc protocol ModuleJS: NSObjectProtocol, JSExport { static func foo(_ value: Double) -> JSValue } final class Module: NSObject, ModuleJS { static func foo(_ value: Double) -> JSValue { ... } } jsContext.setObject(Module.self, forKeyedSubscript: "module" as NSString) const result = module.foo(9.41); .swift .js

Slide 9

Slide 9 text

9 Tips: Swiftクラスのエクスポート 1. ΤΫεϙʔτ͍ͨ͠ΫϥεΛJSExportʹ४ڌ 2. ϑΝΫτϦؔ਺Λ༻ҙ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. @objc protocol ColorJS: NSObjectProtocol, JSExport { var r: CGFloat { get } var g: CGFloat { get } var b: CGFloat { get } var a: CGFloat { get } } final class ColorImp: NSObject, ColorJS { ... } @objc protocol ModuleJS: NSObjectProtocol, JSExport { static func Color(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat, _ a: CGFloat) -> ColorJS } const color = module.Color(0.5, 0.6, 0.7); const red = color.r; .swift .js

Slide 10

Slide 10 text

だいたいわかったところで ຊ୊ʹೖΓ·͢ʂ

Slide 11

Slide 11 text

11 JavaScriptCore.frameworkでもConcurrencyやれんのか? Ͱ͖·͢ʂ ͨͩͪ͠ΐͬͱ޻෉͕ཁΓ·͢ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 12

Slide 12 text

12 Promise, async/await@JS JavaScript(~ECMAScript 2015)ʹ͓͚Δඇಉظॲཧͷ࢓૊Έ async/await͸Promise/thenͷγϯλοΫεγϡΨʔ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. const process = new Promise((resolve, reject) => { ... resolve(result); }); process.then((result) => { ... }); .js async function process() { ... return result; } const result = await process();

Slide 13

Slide 13 text

13 [email protected] JSValue(newPromiseIn:fromExecutor:)Λ࢖͏ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. iOS 1 3 でしれっと追加されていた... final class Module: NSObject, JSModule { static func bar() -> JSValue { return .init(newPromiseIn: .current()) { resolve, reject in ... } } } const result = await module.bar(); .swift .js

Slide 14

Slide 14 text

14 できたッ! Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 15

Slide 15 text

15 ...と思ったらRuntime Error!! Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 16

Slide 16 text

16 なんでよ? Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 17

Slide 17 text

17 Top-level await ECMAScript 2022͔ΒରԠ JavaScriptCore.frameworkͰ͸·ͩରԠ͍ͯ͠ͳ͍ iOS17 SDKͰ΋ରԠͯ͠ͳͦ͞͏😢 Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. async関数の外、つまりメインコード中でもawaitできる仕組み evaluateScript() asyncが出るまでConcurrencyできないのか😇

Slide 18

Slide 18 text

18 じゃあ、Swiftでやればいい。 Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 19

Slide 19 text

SwiftͰTop-level awaitʹରԠ͢Δʹ͸

Slide 20

Slide 20 text

20 Top-level awaitの⽣存戦略 Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. 1. JSͷϝΠϯίʔυͰͷawait͸ఘΊΔ 2. ೖΓޱͱͳΔJSඇಉظؔ਺ΛSwiftଆ͔Βݺͼग़͠ɺͦͷ׬ྃΛ଴ػ 3. ׬ྃ଴ػʹ͸SemaphoreΛ࢖͏ // Swift͔Β͜ͷඇಉظؔ਺Λݺͼग़͠ɺ଴ػͯ͠΋Β͏ async main() { const result = await module.bar(); } // Top-level awaitͰ͖ͳͷͰɺϝΠϯίʔυ͔Βͷݺͼग़͠͸͖͋ΒΊΔ // await main(); .js

Slide 21

Slide 21 text

21 SwiftからJS上の⾮同期関数を呼ぶ JSඇಉظؔ਺ΛPromiseͱͯ͠ࢀর͠ɺthen()ΛݺͿ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. // main()͸ϩʔυ͞ΕΔ͕ɺಈ࡞͸͠ͳ͍ jsContext.evaluateScript(code) let semaphore = DispatchSemaphore(value: 0) // JavaScript͔Βݺͼग़͠ՄೳͳClosureΛ༻ҙ // (@convention(block)ʹ͍ͭͯ͸JSValueͷυΩϡϝϯτࢀর) let onFulfilled: @convention(block) (JSValue) -> Void = { _ in semaphore.signal() } let onRejected: @convention(block) (JSValue) -> Void = { _ in // TODO: Handle error semaphore.signal() } // main()͔ΒPromiseΦϒδΣΫτΛࢀর͠ɺthen()ΛݺͿ jsContext .objectForKeyedSubscript("main")?.call(withArguments: [])? .invokeMethod("then", withArguments: [ JSValue(object: onFulfilled, in: jsContext)!, JSValue(object: onRejected, in: jsContext)! ]) // signal()͞ΕΔ·ͰεϨουΛఀࢭͯ͠଴ػ _ = semaphore.wait(timeout: .distantFuture)

Slide 22

Slide 22 text

22 JSコード実⾏部をContinuationでConcurrency化 εϨουͷϒϩοΫΛ཈ࢭ Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc. func run(code: String) async throws { return try await withCheckedThrowingContinuation { continuation in jsContext.evaluateScript(code) let semaphore = DispatchSemaphore(value: 0) ... _ = semaphore.wait(timeout: .distantFuture) continuation.resume() } }

Slide 23

Slide 23 text

23 まとめ JavaScriptCore.frameworkͰ΋ConcurrencyʹରԠͰ͖Δ JSValueʹ͸ൿີ͕͍ͬͺ͍ ΤϥʔϋϯυϦϯάʹ͍ͭͯ͸ฐࣾςοΫϒϩάʹॻ͘(༧ఆ) Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.

Slide 24

Slide 24 text

24 Copyright(C) 2 0 2 3 ALL RIGHTS RESERVED, MedPeer, Inc.