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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
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 } } }