name: review-case description: > Structured code review for a case in the APIExample-SwiftUI project. Checks engine lifecycle, SwiftUI state ownership, thread safety, permissions, and API correctness. compatibility: [Cursor, Kiro, Windsurf, Claude, Copilot] license: MIT metadata: author: APIExample Team version: 1.0.0 platform: iOS
review-case — APIExample-SwiftUI
Review Dimensions (in priority order)
1. Engine Lifecycle
Check:
AgoraRtcEngineKitcreated insidesetupRTC()of the RTC class, not in the Entry viewleaveChannel()+AgoraRtcEngineKit.destroy()called insideonDestroy()setupRTC()called from.onAppear,onDestroy()called from.onDisappear- No engine instance retained beyond the RTC object's lifetime
Correct:
.onAppear { rtc.setupRTC(configs: configs) }
.onDisappear { rtc.onDestroy() }
Wrong:
// Missing onDisappear — engine never destroyed
.onAppear { rtc.setupRTC(configs: configs) }
2. SwiftUI State Ownership
Check:
- Main view declares RTC object as
@ObservedObject, not@StateObject - Entry view does not hold a reference to the RTC object
@Publishedproperties used for state that drives UI updates
Correct:
struct MyCase: View {
@ObservedObject private var rtc = MyCaseRTC() // correct
}
Wrong:
struct MyCase: View {
@StateObject private var rtc = MyCaseRTC() // wrong — SwiftUI owns lifetime, may outlive view
}
3. Thread Safety
All AgoraRtcEngineDelegate callbacks may arrive on a background thread.
Check:
- Every
@Publishedproperty mutation inside a delegate callback is dispatched toDispatchQueue.main - No direct SwiftUI state mutation on background thread
Correct:
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
DispatchQueue.main.async {
self.remoteUid = uid
}
}
4. Permissions
Check:
- Camera/microphone permissions requested before
joinChannel() joinChannel()called only inside the permission grant callback
5. Error Handling
Check:
- Return value of
joinChannel()checked rtcEngine(_:didOccurError:)implemented and logged- Token expiry handled if token is used
6. Code Conventions
Check:
- RTC class inherits
NSObject, conforms toObservableObjectandAgoraRtcEngineDelegate - Entry view named
<ExampleName>Entry, main view named<ExampleName> - RTC class named
<ExampleName>RTC configsdictionary used to pass data from Entry to Main- No SDK calls inside SwiftUI
bodycomputed property
7. Resource Cleanup
Check:
- Audio files / custom audio tracks stopped in
onDestroy() - External video sources unregistered in
onDestroy() - Media player destroyed if created
- Screen capture stopped if started
- Multi-camera capture stopped if started
Review Output Format
[SEVERITY] file/line — issue description
Suggestion: how to fix
Severity levels:
[CRITICAL]— crash, leak, or incorrect behavior[WARNING]— convention violation or subtle bug risk[INFO]— style or minor improvement
SwiftUI-Specific Checks
onAppearcan fire multiple times (e.g., sheet dismiss, navigation pop/push) — verifysetupRTCis idempotent or guardedonDisappearfires when the view is covered by another view in aTabView— verify this does not prematurely destroy the engine- Video rendering views (
VideoView/VideoUIView) must be created beforesetupRTCis called so the canvas can be set up correctly [weak self]required in all closures capturingselfinside the RTC class to avoid retain cycles