iOS/APIExample/.agent/skills/review-case/SKILL.md
Structured code review for a case in the APIExample (UIKit + Swift) project. Checks engine lifecycle, thread safety, permissions, error handling, API correctness, and code conventions.
npx skillsauth add agoraio/api-examples review-caseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The most critical dimension. Leaks here cause crashes in subsequent examples.
Check:
AgoraRtcEngineKit.sharedEngine(with:delegate:) called in viewDidLoad (not in Entry VC)leaveChannel() + AgoraRtcEngineKit.destroy() called in willMove(toParent:) when parent == nilCorrect:
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
agoraKit?.leaveChannel()
AgoraRtcEngineKit.destroy()
}
}
Wrong:
// Missing destroy — engine leaks
override func viewDidDisappear(_ animated: Bool) {
agoraKit?.leaveChannel()
}
All AgoraRtcEngineDelegate callbacks may arrive on a background thread.
Check:
DispatchQueue.main.async { }UIView, UILabel, or other UIKit objects mutated directly in callbacksCorrect:
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
DispatchQueue.main.async {
self.remoteView.isHidden = false
self.setupRemoteVideo(uid: uid)
}
}
Wrong:
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
remoteView.isHidden = false // UI update on background thread
}
Check:
joinChannel() for video casesjoinChannel() for all casesjoinChannel() called only inside the permission grant callback, not beforeCorrect:
AgoraAudioSession.sharedInstance().requestRecordPermission { [weak self] granted in
guard granted else { return }
self?.agoraKit?.joinChannel(...)
}
Check:
joinChannel() checked (non-zero = error)rtcEngine(_:didOccurError:) delegate method implemented and loggedrtcEngine(_:tokenPrivilegeWillExpire:) if token is usedCheck:
UIViewController, Main class inherits BaseViewController<ExampleName>Entry / <ExampleName>Main patternconfigs dictionary used to pass data from Entry to Main (no direct property injection)Examples/Basic/ or Examples/Advanced/ matching the MenuItem sectioncontroller field in MenuItemCheck:
setVideoEncoderConfiguration called before joinChannel, not aftersetupLocalVideo called before startPreview and joinChannelenableVideo() called before setupLocalVideo for video casessetClientRole called before joinChannel for live streaming casesCheck:
setExternalVideoSource(false, ...))agoraKit.destroy(mediaPlayer))stopScreenCapture())stopCameraCapture(.cameraSecondary))For each issue found, report:
[SEVERITY] file/line — issue description
Suggestion: how to fix
Severity levels:
[CRITICAL] — will cause crash, leak, or incorrect behavior[WARNING] — violates convention or may cause subtle bugs[INFO] — style or minor improvement suggestionAVAudioSession category is set appropriately and UIBackgroundModes includes audio if background playback is neededwillMove(toParent:) is the correct hook — do NOT use viewWillDisappear or deinit for engine cleanup in navigation-based flows[weak self] must be used in all closures that capture self to avoid retain cycles with the engine delegatedevelopment
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including dialog class structure, message map registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
development
Add a new API example or modify an existing one. Covers both creation and modification scenarios, including file structure, registration, and ARCHITECTURE.md updates.
development
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.