GameKit on macOS: Real-Time Matchmaking, Connection State, and Teardown
Design GameKit multiplayer with repeatable authentication callbacks, explicit matchmaking state, bounded packet semantics, disconnect recovery, and clean teardown.
GameKit’s real-time match APIs help signed-in Game Center players discover one another and exchange game data. A match is not an application server, a durable session database, or a guarantee that every participant stays connected. The game owns its protocol, state authority, reconnection policy, and decision about when a match is complete.
On macOS, authentication and matchmaking presentation have platform-specific details. GKLocalPlayer.authenticateHandler may be called several times while initialization proceeds, and a provided view controller must be presented using the macOS GameKit presentation mechanism. Matchmaking can use a GameKit view controller or the programmatic GKMatchmaker API. In either path, attach a delegate, model connection changes, and explicitly disconnect when the session ends.
Initialize Game Center before using match features
Set the local player’s authentication handler early in the game lifecycle and treat it as a multi-callback state machine. A nonnil view controller indicates that GameKit needs user interaction; present it through GKDialogController with the relevant parent window. A nil controller and no error can indicate initialization has completed. An error means the player is unavailable for the requested Game Center feature, not that the base game must fail to launch.
Keep Game Center-only features gated on isAuthenticated and on the player’s applicable restrictions. A local game mode can remain available if the person declines sign-in or Game Center is unavailable. Avoid presenting authentication UI from arbitrary background callbacks or from a window that has already closed.
Matchmaking is a transition, not one callback
For a real-time match, configure a GKMatchRequest with the desired player bounds and any applicable matchmaking rules. GKMatchmaker.findMatch returns asynchronously. Retain the resulting GKMatch, assign its delegate immediately, and wait for the required players before starting a synchronized game. expectedPlayerCount and player connection-state callbacks describe progress; do not assume that a successful matchmaking request means the game can already run with a complete roster.
import GameKit
final class MatchSession: NSObject, GKMatchDelegate {
private(set) var match: GKMatch?
func findTwoPlayerMatch() {
guard GKLocalPlayer.local.isAuthenticated else { return }
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 2
GKMatchmaker.shared().findMatch(for: request) { [weak self] match, error in
guard error == nil, let match else {
self?.reportMatchFailure(error)
return
}
self?.match = match
match.delegate = self
self?.updatePlayerCount(match.expectedPlayerCount)
}
}
func endMatch() {
match?.delegate = nil
match?.disconnect()
match = nil
}
func match(
_ match: GKMatch,
player: GKPlayer,
didChange state: GKPlayerConnectionState
) {
updateConnection(player, state: state)
}
func match(
_ match: GKMatch,
didReceive data: Data,
fromRemotePlayer player: GKPlayer
) {
receivePacket(data, from: player)
}
private func reportMatchFailure(_ error: Error?) {}
private func updatePlayerCount(_ remaining: Int) {}
private func updateConnection(_ player: GKPlayer, state: GKPlayerConnectionState) {}
private func receivePacket(_ data: Data, from player: GKPlayer) {}
}
The sample omits the app-specific interface and authentication presentation. GameKit may complete matching with an error, with too few players, or after a participant later disconnects. Keep a session generation ID so callbacks from an ended match cannot update a newly started game. If adding players or waiting for invitations, define a timeout and a cancel path rather than leaving a matchmaking screen open indefinitely.
Choose packet mode from game semantics
GKMatch.SendDataMode.reliable is intended for data that needs ordered reliable delivery when speed is less important. .unreliable is intended for small, time-sensitive updates that become obsolete when delayed, such as position or velocity. Do not send every packet reliably by default for high-rate movement, and do not use unreliable delivery for a purchase, turn result, or other state transition that must not be lost.
sendData reports whether GameKit can queue the data for transmission, not whether every game rule was applied by the remote participant. Encode a versioned protocol with message type, sequence number, and bounded payload size. Validate decoding and reject unknown or malformed messages. Limit send frequency and payload length to avoid memory pressure and network congestion.
For action games, account for out-of-order or missing updates and smooth presentation without changing authoritative simulation state. If all peers simulate, define how they resolve divergence. If one peer acts as a host, electing a host does not make that process trusted or permanently available. Competitive or valuable game state may require a custom authoritative server and a different hosted-match architecture.
Never assume a message arrives exactly once. Even with reliable mode, connection failure can terminate delivery. A peer may reconnect only through an explicit match flow; it is not equivalent to restoring a durable server session. Make state transitions idempotent and include a match/session identity so a stale packet cannot affect a later round.
Handle connection loss and completion
Implement player state callbacks and distinguish connected, disconnected, and unknown states. The match may continue to send state callbacks while other players remain connected, so end the match and clear the delegate when the product is done. Disconnecting is an explicit resource operation; releasing a view does not tell GameKit that gameplay is finished.
For a two-player match, GameKit offers a delegate decision about whether to reinvite a disconnected player. Use it only if the game can safely pause or reconcile state. Define who owns the current turn, whether the simulation continues, and how long the match waits. If recovery is impossible, terminate cleanly and let both players return to a stable menu state.
Do not update gameplay directly from a delegate callback if the rendering or simulation model belongs to a different actor or queue. Transfer a compact event to the game session owner, apply it in order, and update UI separately. A packet handler should parse bounded data quickly and avoid blocking on persistence or network calls.
Presentation on macOS
The GameKit matchmaker UI is a view controller, but macOS games use GKDialogController to present and dismiss it. Retain a reference to the parent window and keep the UI delegate alive until completion. If the person closes the window while matching, cancel the flow or keep it under a deliberate application-level owner; do not leave a hidden controller retaining the game session.
For programs that do not need Apple’s matchmaker UI, GKMatchmaker can start a programmatic request. That does not remove the need for local-player authentication, invitation handling, a discoverable cancel action, or user-visible error recovery. Keep UI-specific and network-specific state machines separate so a presentation failure does not corrupt the match protocol.
Verification and telemetry
Test sign-in already complete, sign-in required, canceled sign-in, restricted account, no network, matchmaking timeout, declined invitations, one player joining late, peer disconnect, data decode failure, send failure, repeated end calls, window closure, and starting a second match while old callbacks are queued. Run multi-device tests with different Game Center accounts; a local simulator-only test does not establish real network behavior.
Measure time to authenticate, time to match, player wait duration, packet bytes and rate, connection transitions, disconnect reason, and cleanup count. Do not collect personal identifiers or raw chat content in diagnostic telemetry. Add a test assertion that every terminal path clears the match delegate and releases session-owned tasks and buffers.
GameKit supplies player discovery and a data transport for real-time matches. Production gameplay still needs explicit authentication handling, packet semantics, disconnect policy, state authority, and teardown that are correct for a Mac windowed app.
Define a versioned game protocol
Use an envelope that identifies protocol version, match generation, message kind, sequence, and a bounded payload. Decode the envelope before parsing game-specific state. Reject impossible lengths and unsupported major versions without allocating based on untrusted size fields. Even in a Game Center match, each peer is a separate process with its own app version, clock, and potentially delayed work.
Separate transient movement from durable round transitions. Movement snapshots can be superseded by newer snapshots; a round result or inventory change needs a stronger application-level acknowledgement and duplicate policy. Reliable packet mode concerns delivery and ordering while the connection is available; it does not create a transaction across every peer or persist a result after a match ends.
If peers exchange authoritative simulation state, define a deterministic tick or reconciliation policy and include enough sequence context to detect stale updates. If using a host peer, elect and announce the host explicitly, then define host loss behavior. The chooseBestHostingPlayer helper can inform a topology choice; it does not provide a backend authority service or guarantee the selected peer remains online.
Distinguish turn-based state from real-time state
GameKit’s real-time GKMatch is optimized for participants interacting while connected. It is not interchangeable with GKTurnBasedMatch, which stores and forwards match state across turns. A game that must survive players quitting for days should model a turn-based session or a server-backed persistence system instead of trying to keep a real-time socket alive in the background.
Likewise, use hosted-match APIs when the product has its own server topology; do not reinterpret peer-to-peer GKMatch data as a trusted server command. Choose the session type from the game’s availability and state durability requirements, not only from which sample code is shortest.
Test the product protocol under adverse timing
Inject delayed, duplicate, malformed, and stale messages into the game-state adapter without requiring Game Center. Assert that repeated round-complete messages have one effect and that a packet from a previous match generation is ignored. Add multi-client tests for one peer changing network, sleeping, changing accounts, or closing the app while another peer continues.
Keep the network transport thin enough to replace in tests. A deterministic in-process fake can exercise state transitions, while a physical multi-Mac test validates Game Center authentication, matchmaking, and actual transport. Record which layer each test proves; a mock passing does not prove Apple’s matchmaking service or a home router behaves the same way.
Related:
- Game Controller on macOS: Device Discovery, Profiles, and Input Ownership
- NWConnection on macOS: Stream Framing, State, and Cancellation
Sources: