VoIP calling with CallKit
At a glance
| Item | Summary |
|---|---|
| Purpose | Use the CallKit framework to integrate native VoIP calling. |
| App architecture | A C++, C/Objective-C header, Objective-C++, Swift sample bundle with entry-bearing project variants Speakerbox-Watch, Speakerbox, each leading to CallKit APIs. |
| Main patterns | View-controller organization, Protocol-oriented abstraction, Delegate or data-source callbacks, SwiftUI environment injection |
| Project style | 28 scanned source file(s) across C++, C/Objective-C header, Objective-C++, Swift, organized around ranked entry, type, and file boundaries. |
| Execution model | Source-visible boundaries: DispatchQueue.main; none alone proves a background thread. |
| State/event model | Source-visible mechanisms: SwiftUI state property wrapper, ObservableObject, @Published. |
| Key frameworks/packages | SwiftUI, Foundation, UIKit, AVFoundation, CallKit; these are source dependencies, not architecture labels. |
Project structure
Source bundle/
├── Speakerbox-Watch/
│ ├── Speakerbox_WatchApp.swift
│ ├── ContentView.swift
│ └── PushRegistryDelegate.swift
├── Speakerbox/
│ ├── AppDelegate.swift
│ ├── AudioController.mm
│ ├── NewCallView.swift
│ ├── AudioController.h
│ ├── CallView.swift
│ ├── CallsListView.swift
│ ├── EmptyCallsView.swift
│ └── MainView.swift
└── IntentsExtension/
└── IntentHandler.swift
Structure observations
- Architecturally prominent files are ranked from entry points and role-named declarations; resource-only paths are omitted.
- Primary languages: C++, C/Objective-C header, Objective-C++, Swift.
- The verified tree contains 8 project/configuration file(s) and 22 source declaration(s).
Overall architecture
flowchart LR
Bundle["Sample bundle"]
V1["Speakerbox-Watch"]
V2["Speakerbox"]
Boundary["CallKit APIs"]
Bundle --> V1
V1 --> Boundary
Bundle --> V2
V2 --> Boundary
Reference code
Speakerbox-Watch/Speakerbox_WatchApp.swift:11 — architecture anchor
@main
struct SpeakerboxWatchApp: App {
@Environment(\.scenePhase) private var phase
// ...
}Interpretation
The branches represent separate entry-bearing project variants in the downloaded bundle, not runtime calls between those variants. Each branch is intentionally collapsed at the documented framework boundary; the detailed target-local flow remains in the cited files. Ownership is claimed only where the next section cites a stored property or assignment.
Ownership and state
classDiagram
SpeakerboxWatchApp *-- ProviderDelegate : callProvider
ContentView o-- Callback : incomingCallback
var *-- PKPushRegistry : pushRegistry
var *-- SpeakerboxCallManager : callManager
Ownership evidence
Speakerbox-Watch/Speakerbox_WatchApp.swift:14 — stored dependency or nearest verified ownership anchor
@main
struct SpeakerboxWatchApp: App {
// ...
var callProvider = ProviderDelegate(callManager: SpeakerboxCallManager())
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
SpeakerboxWatchApp |
ProviderDelegate (callProvider) |
creates and retains | App/module collaborators |
ContentView |
Callback (incomingCallback) |
stores a callback | App/module collaborators |
var |
PKPushRegistry (pushRegistry) |
creates and retains | Initialized by the owner; the binding is immutable |
var |
SpeakerboxCallManager (callManager) |
creates and retains | Initialized by the owner; the binding is immutable |
Composition arrows indicate a source-visible construction expression or locally owned value state; aggregation means the owner stores or receives a dependency without proving exclusive lifetime ownership.
Concurrency, scheduling, and thread safety
Evidence limit: actor isolation, async/await, or Task creation does not by itself prove background-thread execution; Sendable conformance alone does not prove thread-safe mutation.
| Concern | Source mechanism | Verified placement or handoff | Evidence |
|---|---|---|---|
| Queue scheduling | DispatchQueue.main |
The source addresses the main dispatch queue. | Speakerbox-Watch/PushRegistryDelegate.swift:13 |
@MainActor/MainActor.run, DispatchQueue.main, and RunLoop.main are reported as distinct isolation, queue, and event-loop mechanisms. A plain Task is kept separate from Task.detached; neither is labeled as a background thread.
Reference code
Speakerbox-Watch/PushRegistryDelegate.swift:13 — representative execution boundary
class PushRegistryDelegate: NSObject {
private let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
// ...
}State propagation, frameworks, and dependencies
Evidence limit: an import proves a source-level compilation dependency at the cited line; it does not prove runtime use, architectural adoption, or whether a Swift package is a direct application dependency.
| Category | Mechanism or module | Verified role | Evidence |
|---|---|---|---|
| State propagation | SwiftUI state property wrapper |
A SwiftUI property wrapper supplies or observes UI state. | Speakerbox-Watch/Speakerbox_WatchApp.swift:13 |
| State propagation | ObservableObject |
ObservableObject supplies an observation contract. | Speakerbox/ProviderDelegate.swift:13 |
| State propagation | @Published |
A published property can emit owner-controlled changes. | Speakerbox/SpeakerboxCall.swift:20 |
| Source import | SwiftUI |
The cited file imports this module; runtime use and architectural role are not inferred. | Speakerbox-Watch/ContentView.swift:8 |
| Source import | Foundation |
The cited file imports this module; runtime use and architectural role are not inferred. | Speakerbox-Watch/PushRegistryDelegate.swift:8 |
| Source import | UIKit |
The cited file imports this module; runtime use and architectural role are not inferred. | Speakerbox/AppDelegate.swift:8 |
| Source import | AVFoundation |
The cited file imports this module; runtime use and architectural role are not inferred. | Speakerbox/AudioController.h:8 |
| Source import | CallKit |
The cited file imports this module; runtime use and architectural role are not inferred. | Speakerbox-Watch/PushRegistryDelegate.swift:9 |
receive(on:) describes downstream delivery scheduling, while subscribe(on:) describes upstream subscription/request/cancel scheduling. An import Combine alone establishes neither behavior nor a Store, reducer, Redux, or other application architecture.
Class and protocol design
Speakerbox/StartCallConvertible.swift:8 — representative type boundary
protocol StartCallConvertible {
var startCallHandle: String? { get }
var video: Bool? { get }
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
SpeakerboxWatchApp |
Application entry and top-level composition | App |
AppDelegate |
Receives callback-driven events | UIResponder, UIApplicationDelegate |
ContentView |
User-interface presentation and input forwarding | View |
NewCallView |
User-interface presentation and input forwarding | View |
IntentHandler |
Handles callbacks or feature events | INExtension, INStartCallIntentHandling |
PushRegistryDelegate |
Receives callback-driven events | NSObject |
AudioController |
View lifecycle, callbacks, and feature coordination | NSObject |
CallView |
User-interface presentation and input forwarding | View |
CallsListView |
User-interface presentation and input forwarding | View |
EmptyCallsView |
User-interface presentation and input forwarding | View |
The source explicitly defines local protocol relationships: NSUserActivity → StartCallConvertible, URL → StartCallConvertible.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
pushRegistry (Speakerbox-Watch/PushRegistryDelegate.swift:13) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
phase (Speakerbox-Watch/Speakerbox_WatchApp.swift:13) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
application (Speakerbox/AppDelegate.swift:44) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: hide an implementation step that is not part of the collaboration surface. |
muteAudio (Speakerbox/AudioController.h:12) |
header-visible |
The declaration is exposed to translation units that import the header. | Inference: declare a contract needed by other Objective-C/C translation units. |
Reference code
Speakerbox-Watch/PushRegistryDelegate.swift:13 — representative boundary
class PushRegistryDelegate: NSObject {
private let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
// ...
}Swift declarations without a modifier are internal; explicit private, fileprivate, private(set), public, or open entries above are interpreted by language semantics. Objective-C/C samples instead rely on header and implementation boundaries, which are not equivalent to Swift lexical privacy.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application entry and top-level composition | SpeakerboxWatchApp |
The source’s App suffix makes this role explicit. |
| View lifecycle, callbacks, and feature coordination | AudioController |
The source’s Controller suffix makes this role explicit. |
| Receives callback-driven events | AppDelegate, ProviderDelegate, PushRegistryDelegate, SceneDelegate |
The source’s Delegate suffix makes this role explicit. |
| Handles callbacks or feature events | IntentHandler |
The source’s Handler suffix makes this role explicit. |
| Long-lived feature or framework coordination | SpeakerboxCallManager |
The source’s Manager suffix makes this role explicit. |
| User-interface presentation and input forwarding | CallView, CallsListView, ContentView, EmptyCallsView |
The source’s View suffix makes this role explicit. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| View-controller organization | Speakerbox/AudioController.h:10 |
A controller is the verified coordination boundary; this is MVC-style only where a separate model is present. |
| Protocol-oriented abstraction | Speakerbox/NSUserActivity+StartCallConvertible.swift:11 |
A local protocol and concrete conformance create an explicit capability boundary. |
| Delegate or data-source callbacks | Speakerbox/AppDelegate.swift:12 |
Callback protocols invert event delivery back into the sample’s owner. |
| SwiftUI environment injection | Speakerbox/CallView.swift:24 |
The environment supplies state or a capability without threading it through every initializer. |
Main application flow
sequenceDiagram
participant AudioController
participant AVAudioSession
participant sessionInstance
participant NSNotificationCenter
AudioController->>AVAudioSession: sharedInstance()
AudioController->>sessionInstance: setCategory()
AudioController->>sessionInstance: setMode()
AudioController->>sessionInstance: setPreferredIOBufferDuration()
AudioController->>sessionInstance: setPreferredSampleRate()
AudioController->>NSNotificationCenter: addObserver()
AudioController->>NSNotificationCenter: addObserver()
AudioController->>NSNotificationCenter: addObserver()
Reference code
Speakerbox/AudioController.mm:135 — setupAudioSession()
#if TARGET_OS_IOS
// ...
[sessionInstance setPreferredSampleRate:44100 error:&error];
#endif
Naming conventions
- Types: App: SpeakerboxWatchApp; Controller: AudioController; Delegate: AppDelegate, ProviderDelegate, PushRegistryDelegate, SceneDelegate; Handler: IntentHandler; Manager: SpeakerboxCallManager; View: CallView, CallsListView, ContentView, EmptyCallsView, MainView.
- Protocols:
StartCallConvertible. - Methods:
application,pushRegistry,displayIncomingCall,setupAudioSession,setupIOUnit,setupAudioChain,init,handleInterruption. - Files:
Speakerbox-Watch/ContentView.swift,Speakerbox/AppDelegate.swift,Speakerbox/AudioController.mm,Speakerbox/NewCallView.swift,IntentsExtension/IntentHandler.swift,Speakerbox-Watch/PushRegistryDelegate.swift.
Architecture takeaways
Speakerbox_WatchAppis the main source-visible entry or composition anchor for this sample.- Framework work reaches SwiftUI, UIKit, AVFoundation, CallKit through a deliberately small high-level chain; the detailed API graph remains inside the cited implementation files.
- Stored-property evidence identifies lifecycle collaboration; it does not by itself prove exclusive object ownership.
- Access-control conclusions separate verified language visibility from the likely design rationale.
- Local protocol relationships provide an explicit substitution boundary.
Source map
| Source file | Relevant symbols |
|---|---|
Speakerbox-Watch/Speakerbox_WatchApp.swift |
Cited implementation, SwiftUI state property wrapper, SpeakerboxWatchApp |
Speakerbox/StartCallConvertible.swift |
StartCallConvertible |
Speakerbox-Watch/PushRegistryDelegate.swift |
Cited implementation, DispatchQueue.main, Foundation, CallKit, PushRegistryDelegate |
Speakerbox/AppDelegate.swift |
Cited implementation, UIKit, AppDelegate, var |
Speakerbox/AudioController.h |
muteAudio, AudioController, AVFoundation |
Speakerbox/NSUserActivity+StartCallConvertible.swift |
Cited implementation, Feature implementation |
Speakerbox/CallView.swift |
Cited implementation, CallView |
Speakerbox/ProviderDelegate.swift |
ObservableObject, ProviderDelegate |
Speakerbox/SpeakerboxCall.swift |
@Published, SpeakerboxCall |
Speakerbox-Watch/ContentView.swift |
SwiftUI, ContentView, ContentView_Previews |
Speakerbox/AudioController.mm |
AudioController |
Speakerbox/NewCallView.swift |
NewCallView, NewCallDetails |
IntentsExtension/IntentHandler.swift |
IntentHandler |
Speakerbox/CallsListView.swift |
CallsListView |
Speakerbox/EmptyCallsView.swift |
EmptyCallsView |
Speakerbox/MainView.swift |
MainView |