macOS/.agent/skills/review-case/SKILL.md
Code review for API examples. Ensures examples follow project conventions, handle lifecycle correctly, manage threads safely, and use APIs properly.
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.
Use this skill when you need to:
Check:
initializeAgoraEngine() or similarAgoraRtcEngineConfigleaveChannel() is called before destroy()destroy() is called in viewWillClose() or cleanup methodCorrect Pattern:
override func viewDidLoad() {
super.viewDidLoad()
initializeAgoraEngine() // Create once
}
override func viewWillClose() {
leaveChannel()
agoraKit.destroy()
super.viewWillClose()
}
func joinChannel() {
agoraKit.joinChannel(byToken: token, channelName: channel, info: nil, uid: 0)
}
func leaveChannel() {
agoraKit.leaveChannel(nil)
}
Incorrect Pattern:
See references/incorrect-lifecycle.swift for common mistakes.
Check:
DispatchQueue.main.asyncCorrect Pattern:
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
// Callback may arrive on background thread
DispatchQueue.main.async {
self.statusLabel.stringValue = "Joined channel"
}
}
Incorrect Pattern:
See references/incorrect-thread-safety.swift for common mistakes.
Check:
enableAudio()enableVideo()Correct Pattern:
func initializeAgoraEngine() {
// Request permissions first
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted {
self.agoraKit.enableVideo()
}
}
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
self.agoraKit.enableAudio()
}
}
}
Check:
joinChannel() failures are handledCorrect Pattern:
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
DispatchQueue.main.async {
self.showError("Error: \(errorCode.rawValue)")
}
}
func rtcEngine(_ engine: AgoraRtcEngineKit, tokenPrivilegeWillExpire token: String) {
// Refresh token before expiration
let newToken = KeyCenter.Token(channelName: self.channelName)
self.agoraKit.renewToken(newToken)
}
Check:
<ExampleName>MainBaseViewController@IBOutlet or var// MARK: sectionsCorrect Pattern:
class ScreenShareMain: BaseViewController {
var agoraKit: AgoraRtcEngineKit!
var remoteUid: UInt = 0
@IBOutlet weak var Container: AGEVideoContainer!
// MARK: - Lifecycle
override func viewDidLoad() { ... }
// MARK: - Agora Engine Setup
func initializeAgoraEngine() { ... }
// MARK: - Actions
@IBAction func joinButtonTapped(_ sender: Any) { ... }
}
Check:
Correct Pattern:
// Correct order: enable -> setup -> join
agoraKit.enableVideo()
agoraKit.setupLocalVideo(AgoraRtcVideoCanvas(uid: 0))
agoraKit.joinChannel(byToken: token, channelName: channel, info: nil, uid: 0)
Incorrect Pattern:
// ❌ Wrong order
agoraKit.joinChannel(...) // Join first
agoraKit.enableVideo() // Enable after join (too late)
Check:
Correct Pattern:
func leaveChannel() {
agoraKit.stopAudioMixing() // Stop audio
agoraKit.stopScreenCapture() // Stop screen share
agoraKit.leaveChannel(nil)
}
override func viewWillClose() {
leaveChannel()
agoraKit.destroy()
super.viewWillClose()
}
When reviewing, provide feedback in this format:
## Review Results
### ✅ Passed
- Engine lifecycle correctly managed
- Thread safety ensured with DispatchQueue.main.async
- Permissions requested before device access
### ⚠️ Issues Found
**[HIGH] Thread Safety Issue**
- File: `ScreenShare.swift`
- Line: 45
- Issue: UI update in delegate callback without DispatchQueue.main.async
- Suggestion: Wrap UI update with `DispatchQueue.main.async { ... }`
**[MEDIUM] Missing Error Handling**
- File: `ScreenShare.swift`
- Line: 78
- Issue: joinChannel() result not checked
- Suggestion: Implement `rtcEngine(_:didOccurError:)` delegate method
### 🔧 Recommendations
- Add logging for debugging
- Consider adding retry logic for network failures
Check:
Correct Pattern:
// macOS: Use Cocoa
import Cocoa
import AgoraRtcKit
class ExampleMain: BaseViewController {
@IBOutlet weak var Container: AGEVideoContainer!
// Cocoa-based UI
}
Incorrect Pattern:
// ❌ iOS patterns in macOS
import UIKit // Wrong framework
class ExampleMain: UIViewController { } // Wrong base class
Do NOT accept:
leaveChannel() before destroy()APIExample/Examples/[Basic|Advanced]/ structureUse this checklist when reviewing an example:
Lifecycle:
leaveChannel() called before destroy()destroy() called in cleanupThread Safety:
DispatchQueue.main.asyncPermissions:
Error Handling:
Code Quality:
API Usage:
Resources:
Platform:
Cause: destroy() called without leaveChannel() first
Fix: Always call leaveChannel() before destroy()
Cause: Direct UI update from background thread
Fix: Wrap with DispatchQueue.main.async { ... }
Cause: destroy() not called or engine recreated
Fix: Ensure destroy() in viewWillClose() and create engine once
Cause: No token refresh handling
Fix: Implement tokenPrivilegeWillExpire() delegate method
Cause: Permissions not requested
Fix: Request permissions before enableAudio() / enableVideo()
APIExample/Examples/Basic/JoinChannelVideo/ for referenceAPIExample/Common/ for base class implementationdevelopment
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
Guide for implementing video call functionality in business scenarios, including SDK initialization, joining channels, video encoding configuration, and event handling