| name | iMessage Extension Development |
| description | Messages framework patterns, constraints, and best practices |
| when_to_use | Working with MessagesViewController, MSMessage, or message passing |
| version | 1.0.0 |
| languages | swift |
iMessage Extension Guidelines
Critical Constraints
Memory Limit (ABSOLUTE)
HARD LIMIT: 120MB
If exceeded, extension crashes. No exceptions.
- Monitor with Instruments constantly
- Optimize assets aggressively
- Use texture atlases
- Release resources immediately after games
- Profile every game individually
Extension Lifecycle
- No background execution - Suspended when not visible
- 30 seconds per message - Handle or timeout
- Handle backgrounding - Save state, release resources
- State persistence - Use NSUserDefaults or Core Data
- Quick resume - User expects instant return
Message Passing Constraints
- Max message size: No official limit, but keep <100KB
- Network unreliable - Assume poor conditions
- Delivery not guaranteed - Implement retry logic
- Messages can be out of order - Use sequence numbers
- Offline support - Queue messages
Message Structure
Encoding Game State
struct GameMessage: Codable {
let gameID: UUID
let messageType: MessageType
let gameState: Data // Encoded game-specific state
let timestamp: Date
let sequenceNumber: Int
let stateHash: String // For integrity checking
}
enum MessageType: String, Codable {
case invitation
case move
case gameEnd
case spectatorBet
}
Sending Messages
func sendGameState(_ state: GameState, to conversation: MSConversation) async {
let gameMessage = GameMessage(
gameID: currentGameID,
messageType: .move,
gameState: try! JSONEncoder().encode(state),
timestamp: Date(),
sequenceNumber: nextSequence(),
stateHash: computeHash(state)
)
let messageData = try! JSONEncoder().encode(gameMessage)
let layout = MSMessageTemplateLayout()
layout.caption = "Your turn!"
layout.image = generateGamePreview(state)
let message = MSMessage()
message.url = URL(string: "proppigeon://game/\(gameMessage.gameID)")!
message.layout = layout
// Important: Set messageData
message.url = message.url?.appending(
queryItems: [URLQueryItem(name: "data", value: messageData.base64EncodedString())]
)
conversation.insert(message) { error in
if let error = error {
// Implement retry logic
await retryMessage(message, after: 5.0)
}
}
}
Receiving Messages
override func didReceive(_ message: MSMessage, conversation: MSConversation) {
guard let url = message.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let dataItem = components.queryItems?.first(where: { $0.name == "data" }),
let dataString = dataItem.value,
let data = Data(base64Encoded: dataString) else {
return
}
do {
let gameMessage = try JSONDecoder().decode(GameMessage.self, from: data)
// Validate integrity
let state = try JSONDecoder().decode(GameState.self, from: gameMessage.gameState)
guard gameMessage.stateHash == computeHash(state) else {
throw GameError.stateCorrupted
}
// Handle message
await handleGameMessage(gameMessage, conversation: conversation)
} catch {
showError("Failed to decode message: \(error)")
}
}
Turn-Based Game Patterns
State Synchronization
// Games are inherently asynchronous
// Current player has authority over their move
// State updates propagate via messages
class TurnBasedGame {
var state: GameState
let localPlayer: PlayerID
var isMyTurn: Bool {
state.currentPlayer == localPlayer
}
func makeMove(_ move: GameMove) async throws {
guard isMyTurn else {
throw GameError.notPlayerTurn
}
// Apply locally
try applyMove(move)
// Send to opponent
await sendState()
}
func handleOpponentMove(_ newState: GameState) {
// Validate state transition
guard isValidTransition(from: state, to: newState) else {
// Conflict resolution
await requestStateResync()
return
}
state = newState
updateUI()
}
}
Handling Disconnections
struct GameState: Codable {
var lastUpdateTime: Date
var turnTimeLimit: TimeInterval = 120 // 2 minutes
func hasTimedOut() -> Bool {
Date().timeIntervalSince(lastUpdateTime) > turnTimeLimit
}
}
// In ViewModel
func checkForTimeout() async {
if gameState.hasTimedOut() {
// Award win to non-timeout player
await handleTimeout()
}
}
Group Chat Detection
extension MSConversation {
var isGroupChat: Bool {
// Group chats have multiple recipients
return recipients.count > 1
}
var playerIDs: [UUID] {
// In 1-on-1: [local, remote]
// In group: [local, ...spectators]
return recipients.compactMap { UUID(uuidString: $0) }
}
}
// Enable spectator betting in group chats
func handleConversation(_ conversation: MSConversation) {
if conversation.isGroupChat {
enableSpectatorMode()
} else {
enablePlayerMode()
}
}
Performance Optimization
Asset Loading
// Lazy load assets
class AssetManager {
private var loadedGames: Set<GameType> = []
func loadAssets(for gameType: GameType) async {
guard !loadedGames.contains(gameType) else { return }
// Load textures, sounds, etc.
await loadTextures(for: gameType)
loadedGames.insert(gameType)
}
func releaseAssets(for gameType: GameType) {
// Free memory when game ends
unloadTextures(for: gameType)
loadedGames.remove(gameType)
}
}
State Compression
// Keep message size small
func compressGameState(_ state: GameState) -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [] // No pretty printing
let data = try! encoder.encode(state)
// Optional: Use compression
return try! (data as NSData).compressed(using: .lzfse) as Data
}
Testing iMessage Extensions
Mocking Conversations
class MockMSConversation: MSConversation {
var insertedMessages: [MSMessage] = []
override func insert(_ message: MSMessage, completionHandler: ((Error?) -> Void)? = nil) {
insertedMessages.append(message)
completionHandler?(nil)
}
}
// Usage in tests
func testSendingMove() async {
let mockConversation = MockMSConversation()
let viewModel = GameViewModel(conversation: mockConversation)
await viewModel.makeMove(.hit)
XCTAssertEqual(mockConversation.insertedMessages.count, 1)
}
When This Skill Activates
- Working with MessagesViewController
- Encoding/decoding MSMessage
- Handling conversation events
- Testing iMessage integration
- Debugging message passing issues
References
- Apple Messages Framework Documentation
- iMessage App Programming Guide
- MSMessage API Reference