PeerConnectivity is meant to have a lightweight easy to use syntax, be extensible and flexible, and handle the heavy lifting and edge cases of the MultipeerConnectivity framework quietly in the background.
๐ Blog post https://goo.gl/HJcMbE
Add PeerConnectivity as a Swift Package dependency:
.package(url: "https://github.com/rchatham/PeerConnectivity.git", from: "0.6.0")Use the core networking product for UIKit-free peer connectivity:
.product(name: "PeerConnectivity", package: "PeerConnectivity")Add the UI helper product only when you need UIKit browser view controller support:
.product(name: "PeerConnectivityUI", package: "PeerConnectivity")CocoaPods and Carthage are no longer the recommended distribution paths for new releases.
- Start with the app-developer MultipeerConnectivity to Network.framework Migration Guide.
- Coding agents can follow the repository-local
migrate-multipeer-to-networkskill. - See the Network backend guide for the current production setup and support matrix.
- Consult the readiness audit for authoritative stable/default/removal gates and the trust model plan for the stronger identity roadmap.
- The original staged architecture and implementation sequence remain in NetworkFrameworkMigrationPlan.md and NetworkMigrationPRPlan.md.
PeerConnectionManager can be explicitly initialized with backend: .networkFramework on supported OS versions. The default backend remains .multipeerConnectivity.
Use .preSharedKey with at least 32 CSPRNG-generated bytes of app-managed key material for encrypted Network sessions authenticated as members of the same key-sharing group:
let secret : Data = loadProvisionedNetworkPSK() // At least 32 random bytes; app-defined provisioning.
let pcm = PeerConnectionManager(serviceType: "local",
backend: .networkFramework,
networkSecurity: .preSharedKey(secret))The default networkSecurity: .unauthenticated mode is plaintext TCP, remains available only for source compatibility and diagnostics, and must not be used for sensitive data.
Network adopters must add local-network privacy metadata to the app target, not the package or framework plist. For the service type above, the minimum declarations are:
<key>NSLocalNetworkUsageDescription</key>
<string>Discover and connect to nearby devices running this app.</string>
<key>NSBonjourServices</key>
<array>
<string>_local._tcp</string>
</array>The backend maps the bare serviceType: "local" to the TCP Bonjour type _local._tcp; declare every service type the app uses. It does not advertise _local._udp. Physical-device testing is required to validate the Local Network permission flow and the app's supported Wi-Fi/peer-to-peer topologies; simulator success is not sufficient for production readiness.
See NetworkBackendGuide.md for the complete production setup, service-type mapping, physical-device checklist, support matrix, demo launch arguments, and known limitations. See NetworkTrustModelPlan.md for the current group-membership boundary, display-name spoofing threat model, and future individual-identity options.
The current Network backend supports reliable Data and PeerMessage exchange, but it does not implement a custom stream or resource-transfer protocol. The following APIs remain available and supported only when the manager uses .multipeerConnectivity:
sendDataStream(streamName:toPeer:)sendResourceAtURL(_:withName:toPeers:withCompletionHandler:).receivedStream.startedReceivingResourceand.finishedReceivingResource
With .networkFramework, stream sends throw an unsupported-operation error, resource sends return nil progress and report an unsupported-operation error, and the corresponding receive events are never emitted. Use sendData or sendMessage for bounded payloads, or retain the default MultipeerConnectivity backend when stream/resource behavior is required. See the compatibility decision for rationale and exact behavior.
var pcm = PeerConnectionManager(serviceType: "local")
// Start
pcm.start()
// Stop
// - You should always stop the connection manager
// before attempting to create a new one
pcm.stop()
// Can join chatrooms using PeerConnectionType.automatic, .inviteOnly, and .custom
// - .automatic : automatically searches and joins other devices with the same service type
// - .inviteOnly : provides MultipeerConnectivity advertiser assistant behavior; Network backend requires app-owned UI using found/lost peer events
// - .custom : no default behavior is implemented
// The manager can be initialized with a contructed peer representing the local user
// with a custom displayName
pcm = PeerConnectionManager(serviceType: "local", connectionType: .automatic, displayName: "I_AM_KING")
// Start again at any time
pcm.start() {
// Do something when finished starting the session
}UIKit browser view controller support lives in the separate PeerConnectivityUI SwiftPM product and is available only for the MultipeerConnectivity backend:
import PeerConnectivity
import PeerConnectivityUI
let pcm = PeerConnectionManager(serviceType: "local", connectionType: .inviteOnly)
let browserViewController = pcm.browserViewController { event in
switch event {
case .didFinish, .wasCancelled, .none:
break
}
}Use the optional peerFilter to hide nearby peers before they are presented by
MCBrowserViewController. Discovery info is public, unauthenticated metadata, so
only use it for non-secret values such as protocol versions, public capabilities,
or non-secret room labels.
let filteredBrowserViewController = pcm.browserViewController({ _ in }, peerFilter: { peer, discoveryInfo in
return discoveryInfo?["protocol"] == "2"
})The Network backend does not have an MCBrowserViewController equivalent. During this migration phase, PeerBrowserModel is the supported foundation for app-owned UIKit or SwiftUI peer-selection UI:
let browserModel = PeerBrowserModel(manager: pcm) { peers in
// Update app-owned UI; this callback runs on the main queue.
}
browserModel.startObserving()
if let approvedPeer = browserModel.discoveredPeers.first {
browserModel.invitePeer(approvedPeer)
}A reusable Network browser view is intentionally deferred until app-owned integrations establish common UI requirements. See NetworkBackendGuide.md for the decision and lifecycle guidance.
Run PeerConnectivityDemo.xcodeproj on two simulators or devices. Before starting, select the
backend (Multipeer or Network), connection behavior (Automatic or Require
Invitation), and backend-specific security. Multipeer offers compatible optional encryption or
required encryption; because the demo has a nil identity and accepts all certificates, neither
setting authenticates peers and both remain MITM-vulnerable. Network offers unauthenticated plain
TCP or TLS with a strictly validated 32+-byte Base64 shared key. TLS-PSK authenticates group
membership, not individual identity, and invalid input blocks Start without downgrading. The demo
remembers each backend's selection while running; security and connection behavior remain
independent and disabled while networking runs. See PeerConnectivityDemo/README.md
for key handling, debug-only launch arguments, and the full validation flow. Both peers must select
the same backend; no interoperability bridge is included.
The opt-in .networkFramework backend does not currently support app-provided
discoveryInfo. A value passed to PeerConnectionManager is retained as configuration but
ignored by Network advertising; the backend's Bonjour TXT record contains only reserved
internal peer identity fields. Peers discovered through the Network backend therefore emit
foundPeerWithDiscoveryInfo(peer:discoveryInfo:) with discoveryInfo == nil. Use the default
.multipeerConnectivity backend when application discovery metadata is required.
PeerConnectionEvent.foundPeer(peer:) is still emitted for existing listeners. When
advertised discovery metadata is available, PeerConnectivity also emits
foundPeerWithDiscoveryInfo(peer:discoveryInfo:); listeners should handle one of these
discovery events to avoid processing the same peer twice. Callers with exhaustive
switches over PeerConnectionEvent need to add the new case or a default branch.
In .automatic mode, invitation decisions are controlled by PeerInvitationPolicy.
Non-manual automatic policies still emit .receivedInvitation for observation/API
compatibility, but that event's handler is a no-op and does not change the policy decision.
let event: [String: Any] = [
"EventKey" : Date()
]
// Sends to all connected peers
pcm.sendEvent(event)
// Use this to access the connectedPeers
let connectedPeers: [Peer] = pcm.connectedPeers
// Events can be sent to specific peers
if let somePeerThatIAmConnectedTo = connectedPeers.first {
pcm.sendEvent(event, toPeers: [somePeerThatIAmConnectedTo])
}sendData and sendMessage also have completion overloads that report per-peer failures on the main queue. An empty result means the local networking stack accepted every send; it does not confirm delivery. See Send outcomes.
pcm.sendData(payload, toPeers: pcm.connectedPeers) { failures in
failures.forEach { peer, error in print("\(peer.displayName): \(error)") }
}// Listen to an event
pcm.observeEventListenerForKey("someEvent") { (eventInfo, peer) in
print("Received some event \(eventInfo) from \(peer.displayName)")
guard let date = eventInfo["eventKey"] as? Date else { return }
print(date)
}
// Stop listening to an event
pcm.removeListenerForKey("SomeEvent")Icon from the Noun Project.
-
Check out the Demo project and Playground for examples to help you take you're app to the next level!!!
-
Read the docs!
