I recently upgraded from AFNetworking 4 to 5.
This was the old way of initializing the listener:
let net = NetworkReachabilityManager()
net?.listener = { status in
if net?.isReachable ?? false {
switch status {
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
case .notReachable:
print("The network is not reachable")
case .unknown :
print("It is unknown whether the network is reachable")
}
}
net?.startListening()
The new documentation reads this:
#discardableResult
open func startListening(onQueue queue: DispatchQueue = .main,
onUpdatePerforming listener: #escaping Listener) -> Bool
https://alamofire.github.io/Alamofire/Classes/NetworkReachabilityManager.html
In my code, I am trying this:
let listener = NetworkReachabilityManager.Listener()
self.reachabilityManager?.startListening(onUpdatePerforming: listener){
}
The compilation error I am getting is Extra argument 'onUpdatePerforming' in call. It is a syntactical issue, I am transitioning form Objective C to Swift.
What I attempt to pass a closure, I also cannot seem to get the syntax correct:
self.reachabilityManager?.startListening(onUpdatePerforming: { (NetworkReachabilityManager.Listener) in
})
Listener is just a typealias for the closure type expected, so you need to pass a closure.
self.reachabilityManager?.startListening { status in
switch status {
...
}
}
Here is the code which runs after the AFNetworking update:
self.reachabilityManager?.startListening(onUpdatePerforming: {networkStatusListener in
print("Network Status Changed:", networkStatusListener)
switch networkStatusListener {
case .notReachable:
self.presentAlert(message: "The network is not reachable. Please reconnect to continue using the app.")
print("The network is not reachable.")
case .unknown :
self.presentAlert(message: "It is unknown whether the network is reachable. Please reconnect.")
print("It is unknown whether the network is reachable.")
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.cellular):
print("The network is reachable over the WWAN connection")
}
})
Related
I'm using Swift to connect an iOS app to a LG Smart TV (both on the same wifi network) via websockets and each time the connection gets a success message and then gets disconnected in less than a second. Below is the code I am using to establish the connection. I masked the TV IP address but have confirmed I am using the correct one.
public init(){
self.request = URLRequest(url: URL(string: "ws://192.xxx.x.xx:3000")!)
self.socket = WebSocket(request: request)
self.socket.delegate = self
}
public func connect(){
print("in connect")
self.socket.connect()
print("end of connect")
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
print("WebSocket is connected")
case .disconnected(let reason, let code):
print("Disconnected: code=\(code), reason=\(reason)")
case .text(let message):
print("Received: \(message)")
case .binary(_):
print("binary")
break
case .pong(_):
print("pong")
break
case .ping(_):
print("ping")
break
case .error(let error):
print(error ?? "")
case .viabilityChanged(_):
print("viability changed")
break
case .reconnectSuggested(_):
print("reconnect suggested")
break
case .cancelled:
print("WebSocket is cancelled")
}
}
The connect() method gets called when a button is pressed on a separate view and I can tell it is working based on the print messages. This is the output I get every time
in connect
end of connect
viability changed
WebSocket is connected
Disconnected: code=1008, reason=invalid origin
WebSocket is cancelled
I've tried searching for this 1008 error code but it seems very generic. I'm new to websockets/network programming and would appreciate any help with this
I have a question regarding how to set up a UDP listener on iOS 14. I have a UDP listener which has worked in the past, but after updating to iOS 14 it works sporadically/not at all.
This lives in an NSObject, and listens for a UDP broadcast across the local network on port 15000 (no specific IP address). It uses the CocoaAsyncSocket library. When I call setUpSocket() local network permissions are not triggered, but the app is able to sporadically pick up UDP packets.
var socket: GCDAsyncUdpSocket?
var broadcastPort: UInt16 = 15000
var broadcastAddress: String = ""
var connectAddress = ""
var connectPort = 0
func setUpSocket() {
findUDP()
let socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
socket.setIPv4Enabled(true)
socket.setIPv6Enabled(false)
do {
try socket.bind(toPort: broadcastPort) /*15000*/
try socket.enableBroadcast(false)
try socket.beginReceiving()
} catch let error as NSError {
print("Issue with setting up listener \(error)")
}
}
/*Called when UDP packets are received.*/
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress: Data, withFilterContext filterContext: Any?) {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
if (connected == false) {
if (jsonDictionary["Addresses"] != nil) {
if (jsonDictionary["Addresses"] is NSArray) {
let addresses = jsonDictionary["Addresses"] as! NSArray
for i in addresses {
let ipAddress:String = i as! String
if (ipAddress.range(of: "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", options: .regularExpression) != nil) {
connectAddress = ipAddress
}
}
connectPort = jsonDictionary["Port"] as! Int
}
/*Sets up a TCP connection on the IP and Port provided in the UDP broadcast.*/
setupNetworkCommunication(ip: connectAddress, port: connectPort)
closeSocket()
}
}
} catch let error {
return print(error)
}
}
How can I update this to comply with iOS 14? If I need to update to use Bonjour services, how can I listen on a port without specifying an address (and without having to look for a specific Bonjour service broadcast, because the broadcast I'm looking for doesn't use Bonjour).
Is it acceptable to quickly open and close a Bonjour NWBrowser in order to trigger the network permissions, and then use my code as-is? This seems to work but seems hacky at best.
Thanks in advance.
Here are the steps that we had to go through in order to use CocoaAsyncSocket with UDP in our app:
Request the multicast entitlement from Apple (requestor must be the Account Holder of the team): com.apple.developer.networking.multicast
In the target's Info.plist, set a string for the following key:
Privacy - Local Network Usage Description
After getting permission for the entitlement from Apple, add a true(1) Boolean value for the following key in the *.entitlements file for your app (this last step is what kept us from receiving UDP broadcast packets):
com.apple.developer.networking.multicast
I was able to explore this some more and got some help via the apple developer forums, posting an answer here as well for those who are interested.
I ended up using an NWListener to listen for UDP packets, then set up an NWConnection once once I'd received something. I use this NWConnection to read data from the UDP broadcast.
From Quinn "The Eskimo:"
Listening for UDP broadcasts via an NWListener and then using the NWConnection objects it vends (via the new connection handler) to communicate over unicast with the broadcast’s sender is an expected use case.
I encourage anyone reading this to check out our discussion on the Apple Developer Forum as well.
Here is my implementation:
var udpListener: NWListener?
var udpConnection: NWConnection?
var backgroundQueueUdpListener = DispatchQueue.main
func findUDP() {
let params = NWParameters.udp
udpListener = try? NWListener(using: params, on: 15000)
udpListener?.service = NWListener.Service.init(type: "_appname._udp")
self.udpListener?.stateUpdateHandler = { update in
print("update")
print(update)
switch update {
case .failed:
print("failed")
default:
print("default update")
}
}
self.udpListener?.newConnectionHandler = { connection in
print("connection")
print(connection)
self.createConnection(connection: connection)
self.udpListener?.cancel()
}
udpListener?.start(queue: self.backgroundQueueUdpListener)
}
func createConnection(connection: NWConnection) {
self.udpConnection = connection
self.udpConnection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("ready")
self.send()
self.receive()
case .setup:
print("setup")
case .cancelled:
print("cancelled")
case .preparing:
print("Preparing")
default:
print("waiting or failed")
}
}
self.udpConnection?.start(queue: .global())
}
func endConnection() {
self.udpConnection?.cancel()
}
I've been fighting with NWConnection to receive data on a long-running TCP socket all day. I've finally got it working after inflicting the following errors on myself due to lack of documentation:
Incomplete data (due to only calling receive once)
Getting TCP data out-of-order (due to "polling" receive from a timer...resulting in multiple simultaneous closures waiting to get data).
Suffering infinite loops (due to restarting receive after receiving without checking the "isComplete" Bool--once the socket is terminated from the other end this is....bad...very bad).
Summary of what I've learned:
Once you are in the .ready state you can call receive...once and only once
Once you receive some data, you can call receive again...but only if you are still in the .ready state and the isComplete is false.
Here's my code. I think this is right. But if it's wrong please let me know:
queue = DispatchQueue(label: "hostname", attributes: .concurrent)
let serverEndpoint = NWEndpoint.Host(hostname)
guard let portEndpoint = NWEndpoint.Port(rawValue: port) else { return nil }
connection = NWConnection(host: serverEndpoint, port: portEndpoint, using: .tcp)
connection.stateUpdateHandler = { [weak self] (newState) in
switch newState {
case .ready:
debugPrint("TcpReader.ready to send")
self?.receive()
case .failed(let error):
debugPrint("TcpReader.client failed with error \(error)")
case .setup:
debugPrint("TcpReader.setup")
case .waiting(_):
debugPrint("TcpReader.waiting")
case .preparing:
debugPrint("TcpReader.preparing")
case .cancelled:
debugPrint("TcpReader.cancelled")
}
}
func receive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in
debugPrint("\(Date()) TcpReader: got a message \(String(describing: content?.count)) bytes")
if let content = content {
self.delegate.gotData(data: content, from: self.hostname, port: self.port)
}
if self.connection.state == .ready && isComplete == false {
self.receive()
}
}
}
I think you can use a short time connection many times. For example a client connects to the host and asks the host to do something and then tells the host to shutdown the connection. The host switches to the waiting mode to ready a new connection. See the diagram below.
You should have the connection timer to shutdown opened connection when the client don't send the close connection or answer event to the host for a particular time.
On a long-running TCP socket, you should implement customized heartbeat for monitor the connection status is alive or disconnected.
The heartbeat can as message or encrypt data to send, usually according to the server spec documents to implement.
Below as sample concept code to explain the flow for reference (without network packet content handler).
I can no guarantee is this common and correct way, but that's work for my project.
import Network
class NetworkService {
lazy var heartbeatTimeoutTask: DispatchWorkItem = {
return DispatchWorkItem { self.handleHeartbeatTimeOut() }
}()
lazy var connection: NWConnection = {
// Create the connection
let connection = NWConnection(host: "x.x.x.x", port: 1234, using: self.parames)
connection.stateUpdateHandler = self.listenStateUpdate(to:)
return connection
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
parames.expiredDNSBehavior = .allow
parames.multipathServiceType = .interactive
parames.serviceClass = .background
return parames
}()
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.enableFastOpen = true // Enable TCP Fast Open (TFO)
options.connectionTimeout = 5 // connection timed out
return options
}()
let queue = DispatchQueue(label: "hostname", attributes: .concurrent)
private func listenStateUpdate(to state: NWConnection.State) {
// Set the state update handler
switch state {
case .setup:
// init state
debugPrint("The connection has been initialized but not started.")
case .waiting(let error):
debugPrint("The connection is waiting for a network path change with: \(error)")
self.disconnect()
case .preparing:
debugPrint("The connection in the process of being established.")
case .ready:
// Handle connection established
// this means that the handshake is finished
debugPrint("The connection is established, and ready to send and receive data.")
self.receiveData()
self.sendHeartbeat()
case .failed(let error):
debugPrint("The connection has disconnected or encountered an: \(error)")
self.disconnect()
case .cancelled:
debugPrint("The connection has been canceled.")
default:
break
}
}
// MARK: - Socket I/O
func connect() {
// Start the connection
self.connection.start(queue: self.queue)
}
func disconnect() {
// Stop the connection
self.connection.stateUpdateHandler = nil
self.connection.cancel()
}
private func sendPacket() {
var packet: Data? // do something for heartbeat packet
self.connection.send(content: packet, completion: .contentProcessed({ (error) in
if let err = error {
// Handle error in sending
debugPrint("encounter an error with: \(err) after send Packet")
} else {
// Send has been processed
}
}))
}
private func receiveData() {
self.connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] (data, context, isComplete, error) in
guard let weakSelf = self else { return }
if weakSelf.connection.state == .ready && isComplete == false, var data = data, !data.isEmpty {
// do something for detect heart packet
weakSelf.parseHeartBeat(&data)
}
}
}
// MARK: - Heartbeat
private func sendHeartbeat() {
// sendHeartbeatPacket
self.sendPacket()
// trigger timeout mission if the server no response corresponding packet within 5 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 5.0, execute: self.heartbeatTimeoutTask)
}
private func handleHeartbeatTimeOut() {
// this's sample time out mission, you can customize this chunk
self.heartbeatTimeoutTask.cancel()
self.disconnect()
}
private func parseHeartBeat(_ heartbeatData: inout Data) {
// do something for parse heartbeat
// cancel heartbeat timeout after parse packet success
self.heartbeatTimeoutTask.cancel()
// send heartbeat for monitor server after computing 15 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15.0) {
self.sendHeartbeat()
}
}
}
I need to send the status of the network to some analytics server, so I need to send it once the app starts. I tried to use Alamofire, but I usually get Unknown status, if there is some sort of delay it shows the right status :
These code would run in my AppDelegate (didFinishLaunchingWithOptions):
AFNetworkReachabilityManager.shared().startMonitoring()
AFNetworkReachabilityManager.shared().localizedNetworkReachabilityStatusString()
What is the best way to get the right status right away?
UPDATE 1:
I updated my code and tried to use completion handler, but why when I use this method it will print multiple YES?
connectedCompletionBlock({ connected in
if connected {
print("YES")
} else {
print("NO")
}
})
class func connectedCompletionBlock(_ completion: #escaping (_ connected: Bool) -> Void) {
AFNetworkReachabilityManager.shared().startMonitoring()
AFNetworkReachabilityManager.shared().setReachabilityStatusChange({ status in
var isConnected = false
let wifi = AFNetworkReachabilityStatus.reachableViaWiFi
let wwan = AFNetworkReachabilityStatus.reachableViaWWAN
if ( status == wifi || status == wwan) {
con = true
}
AFNetworkReachabilityManager.shared().stopMonitoring()
completion(isConnected)
})
}
Ok since nobody didn't answer, I think it's good to share the solution with you. The issue was this: I was calling this method on didFinishLaunchingWithOptions, and since it takes sometime for the Alamofire to figure out the connection status it would return unknown! I called it on applicationDidBecomeActive and it works fine now.
I posted a heavy question recently surrounding this topic, but I need to cover the basics first before I can start doing other tasks. I would like to know how to get data from an ip address instead of a standard url that returns JSON data like so:
func simpleGetRequest()
{
let parameters = ["takepic":"true"]
Alamofire.request("http://XXX.XXX.X.XX", parameters:parameters).responseJSON { (response) in
if let status = response.response?.statusCode
{
switch(status)
{
case 200:
print("Successfully taken a pic")
case 500:
print("Failed to connect")
default:
print("Error with everything")
}
}
if let result = response.result.value
{
print(result)
}
}
}
I've heard some things about IPv6 and IPv4, but am not sure as to what they mean. I'll continue to do more research, however I can't find a single source that shows how to communicate with non-local web servers.