π Network Framework
β Difficulty: βββ
β±οΈ Est. Time: 2-3h
π System & Network
νλμ μΈ μ μμ€ λ€νΈμν¬ ν΅μ νλ μμν¬
iOS 12+TLS 1.3 Supported
β¨ Network Framework?
Network is a modern low-level framework for implementing network communication using TCP, UDP, TLS, WebSocket and more. It provides finer control than URLSession, using NWConnection and NWListener for client/server communication. It offers advanced features like Bonjour service discovery, IPv6 support, and network path monitoring.
π‘ Key Features: TCP/UDP Communication Β· TLS 1.3 Encryption Β· WebSocket Support Β· Bonjour Discovery Β· Network Path Monitoring Β· IPv4/IPv6 Β· Custom Protocols
π 1. NWConnection β TCP Client
TCP μ°κ²°μ μμ±νκ³ λ°μ΄ν°λ₯Ό μ£Όκ³ λ°.
TCPClient.swift β TCP ν΄λΌμ΄μΈνΈ
import Network import SwiftUI @Observable class TCPClient { private var connection: NWConnection? var isConnected = false var receivedMessages: [String] = [] // TCP μ°κ²° μμ func connect(to host: String, port: UInt16) { // νΈμ€νΈμ ν¬νΈ μ€μ guard let endpoint = NWEndpoint.Host(host) else { print("β μλͺ»λ νΈμ€νΈ") return } let port = NWEndpoint.Port(rawValue: port) ?? NWEndpoint.Port.any // TCP νλΌλ―Έν° let parameters = NWParameters.tcp // TLS νμ±ν (HTTPS) parameters.includeTLS = true // IPv4/IPv6 μ°μ μμ parameters.preferNoProxies = true parameters.requiredInterfaceType = .wifi // .wifi, .cellular, .loopback // μ°κ²° μμ± connection = NWConnection(host: endpoint, port: port, using: parameters) // μν νΈλ€λ¬ connection?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("β μ°κ²° μ±κ³΅") self?.isConnected = true self?.receiveMessage() case .waiting(let error): print("β³ μ°κ²° λκΈ° μ€: \(error)") case .failed(let error): print("β μ°κ²° μ€ν¨: \(error)") self?.isConnected = false case .cancelled: print("β μ°κ²° μ·¨μλ¨") self?.isConnected = false default: break } } // μ°κ²° μμ connection?.start(queue: .global(qos: .userInitiated)) print("π μ°κ²° μλ: \(host):\(port)") } // λ©μμ§ μ μ‘ func sendMessage(_ message: String) { guard let connection = connection, isConnected else { print("β οΈ μ°κ²°λμ§ μμ") return } guard let data = message.data(using: .utf8) else { return } connection.send( content: data, completion: .contentProcessed { error in if let error = error { print("β μ μ‘ μ€ν¨: \(error)") } else { print("π€ μ μ‘ μλ£: \(message)") } } ) } // λ©μμ§ μμ private func receiveMessage() { connection?.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, context, isComplete, error in if let error = error { print("β μμ μλ¬: \(error)") return } if let data = data, !data.isEmpty { if let message = String(data: data, encoding: .utf8) { print("π₯ μμ : \(message)") self?.receivedMessages.append(message) } } // κ³μ μμ λκΈ° if !isComplete { self?.receiveMessage() } } } // μ°κ²° μ’ λ£ func disconnect() { connection?.cancel() connection = nil isConnected = false print("β μ°κ²° μ’ λ£") } }
π§ 2. NWListener - TCP μλ²
TCP μλ²λ₯Ό λ§λ€μ΄ ν΄λΌμ΄μΈνΈ μ°κ²°μ μμ .
TCPServer.swift β TCP μλ²
import Network @Observable class TCPServer { private var listener: NWListener? private var connections: [NWConnection] = [] var isListening = false var port: UInt16 = 0 // μλ² μμ func startServer(on port: UInt16) { do { // TCP νλΌλ―Έν° let parameters = NWParameters.tcp parameters.acceptLocalOnly = true // λ‘컬 μ°κ²°λ§ νμ© // 리μ€λ μμ± let nwPort = NWEndpoint.Port(rawValue: port) ?? NWEndpoint.Port.any listener = try NWListener(using: parameters, on: nwPort) // μν νΈλ€λ¬ listener?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("β μλ² μμλ¨") self?.isListening = true if let port = self?.listener?.port { self?.port = port.rawValue print(" ν¬νΈ: \(port)") } case .failed(let error): print("β μλ² μ€ν¨: \(error)") self?.isListening = false case .cancelled: print("β μλ² μ·¨μλ¨") self?.isListening = false default: break } } // μ μ°κ²° νΈλ€λ¬ listener?.newConnectionHandler = { [weak self] newConnection in print("π μ ν΄λΌμ΄μΈνΈ μ°κ²°") self?.handleConnection(newConnection) } // 리μ€λ μμ listener?.start(queue: .global(qos: .userInitiated)) } catch { print("β μλ² μμ μ€ν¨: \(error)") } } // ν΄λΌμ΄μΈνΈ μ°κ²° μ²λ¦¬ private func handleConnection(_ connection: NWConnection) { connections.append(connection) // μν νΈλ€λ¬ connection.stateUpdateHandler = { state in switch state { case .ready: print(" β ν΄λΌμ΄μΈνΈ μ€λΉλ¨") self.receiveMessage(from: connection) case .failed(let error): print(" β ν΄λΌμ΄μΈνΈ μ€ν¨: \(error)") self.removeConnection(connection) case .cancelled: print(" β ν΄λΌμ΄μΈνΈ μ°κ²° ν΄μ ") self.removeConnection(connection) default: break } } connection.start(queue: .global(qos: .userInitiated)) } // λ©μμ§ μμ private func receiveMessage(from connection: NWConnection) { connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in if let error = error { print(" β μμ μλ¬: \(error)") return } if let data = data, !data.isEmpty { if let message = String(data: data, encoding: .utf8) { print(" π₯ μμ : \(message)") // Echo μλ΅ let response = "Echo: \(message)" self.sendMessage(response, to: connection) } } if !isComplete { self.receiveMessage(from: connection) } } } // λ©μμ§ μ μ‘ private func sendMessage(_ message: String, to connection: NWConnection) { guard let data = message.data(using: .utf8) else { return } connection.send(content: data, completion: .contentProcessed { error in if let error = error { print(" β μ μ‘ μ€ν¨: \(error)") } else { print(" π€ μ μ‘: \(message)") } }) } // μ°κ²° μ κ±° private func removeConnection(_ connection: NWConnection) { connections.removeAll { $0 === connection } } // μλ² μ€μ§ func stopServer() { listener?.cancel() listener = nil connections.forEach { $0.cancel() } connections.removeAll() isListening = false print("βΉοΈ μλ² μ€μ§") } }
π‘ 3. UDP ν΅μ
UDP νλ‘ν μ½λ‘ λ°μ΄ν°κ·Έλ¨μ μ£Όκ³ λ°.
UDPClient.swift β UDP ν΄λΌμ΄μΈνΈ
import Network @Observable class UDPClient { private var connection: NWConnection? var isConnected = false // UDP μ°κ²° func connect(to host: String, port: UInt16) { guard let endpoint = NWEndpoint.Host(host) else { return } let port = NWEndpoint.Port(rawValue: port) ?? NWEndpoint.Port.any // UDP νλΌλ―Έν° let parameters = NWParameters.udp connection = NWConnection(host: endpoint, port: port, using: parameters) connection?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("β UDP μ€λΉλ¨") self?.isConnected = true case .failed(let error): print("β UDP μ€ν¨: \(error)") self?.isConnected = false default: break } } connection?.start(queue: .global(qos: .userInitiated)) print("π‘ UDP μ°κ²°: \(host):\(port)") } // λ°μ΄ν°κ·Έλ¨ μ μ‘ func sendDatagram(_ message: String) { guard let connection = connection, isConnected else { return } guard let data = message.data(using: .utf8) else { return } connection.send(content: data, completion: .contentProcessed { error in if let error = error { print("β UDP μ μ‘ μ€ν¨: \(error)") } else { print("π€ UDP μ μ‘: \(message)") } }) } // λ°μ΄ν°κ·Έλ¨ μμ func receiveDatagram() { connection?.receiveMessage { data, context, isComplete, error in if let error = error { print("β UDP μμ μλ¬: \(error)") return } if let data = data, let message = String(data: data, encoding: .utf8) { print("π₯ UDP μμ : \(message)") } // κ³μ μμ self.receiveDatagram() } } // μ°κ²° μ’ λ£ func disconnect() { connection?.cancel() connection = nil isConnected = false } }
π 4. Bonjour μλΉμ€ κ²μ
λ‘컬 λ€νΈμν¬μμ Bonjour μλΉμ€λ₯Ό κ²μ.
BonjourBrowser.swift β Bonjour Discovery
import Network @Observable class BonjourBrowser { private var browser: NWBrowser? var discoveredServices: [NWBrowser.Result] = [] var isBrowsing = false // Bonjour κ²μ μμ func startBrowsing(serviceType: String = "_http._tcp", domain: String = "local.") { // Browser νλΌλ―Έν° let parameters = NWParameters() parameters.includePeerToPeer = true // Bonjour μλΉμ€ νμ let bonjourType = NWBrowser.Descriptor.bonjour(type: serviceType, domain: domain) // Browser μμ± browser = NWBrowser(for: bonjourType, using: parameters) // μν νΈλ€λ¬ browser?.stateUpdateHandler = { [weak self] state in switch state { case .ready: print("β Bonjour κ²μ μμ") self?.isBrowsing = true case .failed(let error): print("β κ²μ μ€ν¨: \(error)") self?.isBrowsing = false case .cancelled: print("β κ²μ μ·¨μλ¨") self?.isBrowsing = false default: break } } // κ²μ κ²°κ³Ό νΈλ€λ¬ browser?.browseResultsChangedHandler = { [weak self] results, changes in print("π μλΉμ€ λ³κ²½: \(changes.count)κ°") for change in changes { switch change { case .added(let result): print(" β μλΉμ€ λ°κ²¬: \(result.endpoint)") self?.discoveredServices.append(result) case .removed(let result): print(" β μλΉμ€ μ¬λΌμ§: \(result.endpoint)") self?.discoveredServices.removeAll { $0.endpoint == result.endpoint } case .changed(old: let old, new: let new, flags: _): print(" π μλΉμ€ λ³κ²½: \(old.endpoint) -> \(new.endpoint)") case .identical: break @unknown default: break } } } // κ²μ μμ browser?.start(queue: .global(qos: .userInitiated)) } // κ²μ μ€μ§ func stopBrowsing() { browser?.cancel() browser = nil isBrowsing = false discoveredServices.removeAll() print("βΉοΈ κ²μ μ€μ§") } }
π 5. λ€νΈμν¬ κ²½λ‘ λͺ¨λν°λ§
λ€νΈμν¬ μνμ κ²½λ‘ λ³νλ₯Ό κ°μ§.
NetworkMonitor.swift β Network Monitoring
import Network @Observable class NetworkMonitor { private let monitor = NWPathMonitor() var isConnected = false var connectionType: NWInterface.InterfaceType? var isExpensive = false var isConstrained = false init() { startMonitoring() } // λͺ¨λν°λ§ μμ func startMonitoring() { monitor.pathUpdateHandler = { [weak self] path in self?.isConnected = path.status == .satisfied self?.isExpensive = path.isExpensive self?.isConstrained = path.isConstrained // μ°κ²° νμ νμΈ if path.usesInterfaceType(.wifi) { self?.connectionType = .wifi print("π‘ Wi-Fi μ°κ²°") } else if path.usesInterfaceType(.cellular) { self?.connectionType = .cellular print("π± μ λ£°λ¬ μ°κ²°") } else if path.usesInterfaceType(.wiredEthernet) { self?.connectionType = .wiredEthernet print("π μ μ μ΄λλ· μ°κ²°") } else { self?.connectionType = nil print("β μ°κ²° μμ") } // μ°κ²° μν switch path.status { case .satisfied: print("β λ€νΈμν¬ μ°κ²°λ¨") case .unsatisfied: print("β λ€νΈμν¬ μ°κ²° μ λ¨") case .requiresConnection: print("β³ μ°κ²° νμ") @unknown default: break } // λΉμ© μ 보 if path.isExpensive { print("π° λΉμ©μ΄ λ°μνλ λ€νΈμν¬ (μ λ£°λ¬ λ°μ΄ν°)") } if path.isConstrained { print("β οΈ μ νλ λ€νΈμν¬ (μ μ λ ₯ λͺ¨λ)") } // μ¬μ© κ°λ₯ν μΈν°νμ΄μ€ print(" μ¬μ© κ°λ₯ν μΈν°νμ΄μ€:") for interface in path.availableInterfaces { print(" - \(interface.type): \(interface.name)") } } monitor.start(queue: .global(qos: .userInitiated)) } // λͺ¨λν°λ§ μ€μ§ func stopMonitoring() { monitor.cancel() } deinit { stopMonitoring() } }
π± 6. SwiftUI Integration
λ€νΈμν¬ λͺ¨λν°λ§ UI implementation.
NetworkMonitorView.swift β Network Status UI
import SwiftUI struct NetworkMonitorView: View { @State private var networkMonitor = NetworkMonitor() var body: some View { NavigationStack { Form { Section("μ°κ²° μν") { HStack { Circle() .fill(networkMonitor.isConnected ? Color.green : Color.red) .frame(width: 16, height: 16) Text(networkMonitor.isConnected ? "μ°κ²°λ¨" : "μ°κ²° μ λ¨") .font(.headline) } if let type = networkMonitor.connectionType { HStack { Text("μ°κ²° νμ ") Spacer() Text(connectionTypeString(type)) .foregroundStyle(.secondary) } } } Section("λ€νΈμν¬ νΉμ±") { HStack { Image(systemName: networkMonitor.isExpensive ? "checkmark.circle.fill" : "xmark.circle") .foregroundStyle(networkMonitor.isExpensive ? .orange : .secondary) Text("λΉμ© λ°μ") Spacer() Text(networkMonitor.isExpensive ? "μ" : "μλμ€") .foregroundStyle(.secondary) } HStack { Image(systemName: networkMonitor.isConstrained ? "checkmark.circle.fill" : "xmark.circle") .foregroundStyle(networkMonitor.isConstrained ? .orange : .secondary) Text("μ νλ¨") Spacer() Text(networkMonitor.isConstrained ? "μ" : "μλμ€") .foregroundStyle(.secondary) } } Section { Text("λ€νΈμν¬ μνλ₯Ό μ€μκ°μΌλ‘ λͺ¨λν°λ§ν©λλ€.") .font(.caption) .foregroundStyle(.secondary) } } .navigationTitle("λ€νΈμν¬ λͺ¨λν°") } } private func connectionTypeString(_ type: NWInterface.InterfaceType) -> String { switch type { case .wifi: return "Wi-Fi" case .cellular: return "μ λ£°λ¬" case .wiredEthernet: return "μ μ μ΄λλ·" case .loopback: return "λ‘컬" case .other: return "κΈ°ν" @unknown default: return "μ μ μμ" } } }
π‘ HIG Guidelines
- Permission Settings: Add NSLocalNetworkUsageDescription to Info.plist
- Bonjour μλΉμ€: Specify service types in NSBonjourServices
- TLS μ¬μ©: λ―Όκ°ν λ°μ΄ν° μ μ‘ μ TLS νμ
- λ€νΈμν¬ ν¨μ¨: Check isExpensive to optimize large downloads
- Error Handling: μ°κ²° μ€ν¨, νμμμμ λν μ μ ν μ²λ¦¬
π― Practical Usage
- μ€μκ° ν΅μ : μ±ν , κ²μ μλ² μ°λ
- IoT ν΅μ : Smart home device control
- λ‘컬 μλ²: λλ²κΉ μ© λ‘컬 μλ² κ΅¬ν
- μλΉμ€ κ²μ: λ‘컬 λ€νΈμν¬μ νλ¦°ν°, λ―Έλμ΄ μλ² μ°ΎκΈ°
- P2P ν΅μ : μ§μ μ μΈ κΈ°κΈ° κ° ν΅μ
π Learn More
β‘οΈ Performance Tips:
NWConnection automatically selects the optimal network path. It supports both IPv4 and IPv6, seamlessly switching between Wi-Fi and cellular. UDP is fast but unreliable, while TCP is reliable but has overhead.