Upgrade to Pro — share decks privately, control downloads, hide ads and more …

(Extension DC 2025) Actor境界を越える技術

Avatar for Himeshi Himeshi
October 01, 2025

(Extension DC 2025) Actor境界を越える技術

Extension DC 2025 Day 1にて発表したトークの発表資料です。
https://dena.connpass.com/event/362412/

Avatar for Himeshi

Himeshi

October 01, 2025
Tweet

More Decks by Himeshi

Other Decks in Programming

Transcript

  1. 1VMTF$PEF.PEVMBUJPO 1$. ৼ෯ ࣌ؒ v 0 v 1 v 2

    v 3 v 4 v 5 v 6 v 7 v 8 v 9 v 10 v 11 var soundData: [Float]
  2. "7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =

    48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } }
  3. "7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =

    48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } }
  4. "7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =

    48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } }
  5. "7"VEJP4PVSDF/PEF let frequency: Float = 440 let sampleRate: Float =

    48_000 var phase: Float = 0 var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } }
  6. "DUPSͰҐ૬Λอ࣋ actor SineWavePlayer { var phase: Float = 0 var

    sourceNode: AVAudioSourceNode { AVAudioSourceNode { ... } } func play() throws { phases = 0 let audioEngine = AVAudioEngine() let sourceNode = sourceNode audioEngine.attach(sourceNode) audioEngine.connect(sourceNode, to: audioEngine.mainMixerNode, format: ...) audioEngine.prepare() try audioEngine.start() } }
  7. "DUPSDPOUFYU "DUPS" var a: Int = 42 func foo() var

    b: Int = 123 "DUPS" "DUPS# ࣌ؒ bar() getter B Actor B bar() (Other) func bar() ಉظ ඇಉظ
  8. એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU actor MyActor { var value: Int = 42 var

    nonSendableBlock: () -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } }
  9. actor MyActor { var value: Int = 42 var nonSendableBlock:

    () -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } var sendableBlock: @Sendable () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } } એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU
  10. actor MyActor { var value: Int = 42 var nonSendableBlock:

    () -> Void { // Assumed to be executed on MyActor's context { [self] in value = value + 1 } } var sendableBlock: @Sendable () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } nonisolated var nonisolatedBlock: () -> Void { // Cannot touch MyActor's property synchronously { /* [self] in value = value + 1 */ } } } એݴɾΫϩʔδϟʔͱ"DUPSDPOUFYU
  11. "7"VEJP4PVSDF/PEF3FOEFS#MPDL typealias AVAudioSourceNodeRenderBlock = ( UnsafeMutablePointer<ObjCBool>, // isSilence UnsafePointer<AudioTimeStamp>, //

    timestamp AVAudioFrameCount, // frameCount UnsafeMutablePointer<AudioBufferList> // outputData ) -> OSStatus @Sendable͕ͳ͍4FOEBCMFͳΫϩʔδϟͰ͸ͳ͍
  12. ͦΕͰ΋ಉظؔ਺Ͱ͋Δඞཁ͕͋Δ actor SineWavePlayer { var phase: Float = 0 nonisolated

    var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } Actor-isolated property 'phase' can not be 
 referenced from a nonisolated context
  13. actor MyActor { func sample() async { funcA() await funcB()

    funcC() await funcD() funcE() } } actor MyActor { } &YFDVUPS Job Job Job Job Job Job "DUPS+PCͷ࣮ߦ
  14. BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT "DDFTT

    $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL΋%JTQBUDI2VFVFͰ࣮ߦɻ
  15. %JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT

    "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL΋%JTQBUDI2VFVFͰ࣮ߦɻ
  16. %JTQBUDI2VFVF w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL΋%JTQBUDI2VFVFͰ࣮ߦɻ BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF

    3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ
  17. &YFDVUPSͷ࣮૷ final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue

    = DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } }
  18. final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =

    DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } &YFDVUPSͷ࣮૷
  19. final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =

    DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } &YFDVUPSͷ࣮૷
  20. final class PlayerExecutor: @unchecked Sendable, SerialExecutor { let queue =

    DispatchQueue(label: "Player") func enqueue(_ job: consuming ExecutorJob) { let unownedSelf = asUnownedSerialExecutor() let unownedJob = UnownedJob(job) queue.async { unownedJob.runSynchronously(on: unownedSelf) } } func checkIsolated() { dispatchPrecondition(condition: .onQueue(queue)) } } queueͷ֎Ͱݺ͹ΕΔͱΫϥογϡ͢Δ &YFDVUPSͷ࣮૷
  21. &YFDVUPSΛࢦఆ actor SineWavePlayer { let executor = PlayerExecutor() nonisolated var

    unownedExecutor: UnownedSerialExecutor { executor.asUnownedSerialExecutor() } }
  22. %JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT

    "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL΋%JTQBUDI2VFVFͰ࣮ߦɻ
  23. %JTQBUDI2VFVF BDUPS4JOF8BWF1MBZFS var phase: Float "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE 3FOEFSCMPDL 4ZODISPOPVT

    "DDFTT $BQUVSFE %JTQBUDI2VFVFͰ"DUPSΛ͚ͬͭ͘Δ w BDUPSͷδϣϒΛ%JTQBUDI2VFVFͰ࣮ߦɻ w TPVOESFOEFSJOHCMPDL΋%JTQBUDI2VFVFͰ࣮ߦɻ
  24. %JTQBUDI2VFVF্Ͱ4PVOESFOEFSJOH var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [executor] _, _,

    frameCount, outputPointer in executor.queue.sync { [weak self] in guard let self else { return noErr } guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(self.phase) // Set the amplitude self.phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } }
  25. 4PVOESFOEFSJOHMPHJDΛִ཭͢Δ var sourceNode: AVAudioSourceNode { AVAudioSourceNode { [executor] _, _,

    frameCount, outputPointer in executor.queue.sync { [weak self] in guard let self else { return noErr } return assumeIsolated { this in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(this.phase) // Set the amplitude this.phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } }
  26. .VUFYͰҐ૬ม਺ʹΞΫηε͢Δ DMBTT4JOF8BWF1MBZFS let phase: Mutex<Float> "7"VEJP4PVSDF/PEF 3FOEFSCMPDL 4ZTUFN"VEJP5ISFBE $BQUVSFE w

    ΦʔσΟΦॲཧ༻εϨου͔Β.VUFYʹ௚઀ΞΫηε͢Δɻ w 4PVOESFOEFSJOHCMPDLશମΛ.VUFYͰϩοΫ͢Δɻ 4JOF8BWF1MBZFS
  27. class SineWavePlayer { let phase = Mutex<Float>(0) var sourceNode: AVAudioSourceNode

    { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in phase.withLock { phase in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } } .VUFYʹΑΔ࣮૷
  28. .VUFYʹΑΔ࣮૷ class SineWavePlayer { let phase = Mutex<Float>(0) var sourceNode:

    AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in phase.withLock { phase in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { outputBuffer[index] = sin(phase) // Set the amplitude phase += 2 * .pi * frequency / sampleRate // Increment phase } return noErr } } } }
  29. XJUI-PDLͱॲཧͷΞτϛοΫੑ class SineWavePlayer: Sendable { let phase = Mutex<Float>(0) var

    sourceNode: AVAudioSourceNode { AVAudioSourceNode { [self] _, _, frameCount, outputPointer in guard let outputBuffer = outputPointer.pointee.mBuffers.mData? .assumingMemoryBound(to: Float.self) else { return kAudioUnitErr_Uninitialized } for index in 0..<Int(frameCount) { phase.withLock { outputBuffer[index] = sin($0) } phase.withLock { $0 += 2 * .pi * frequency / sampleRate } } return noErr } } }