Polling for Response from BLE Adapter in Swift - ios

Using a Wi-Fi socket based adapter, I can successfully poll for a response like so:
func writeMessageWithResponse(message: String) -> [String] {
self.waitingForResponse = true
let runLoop = NSRunLoop.currentRunLoop()
if self.response != nil {
self.response?.removeAll()
}
writeMessage(message) // this will set self.waitingForResponse to false when a response is received
while self.waitingForResponse && runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()) {
// waiting for flag (self.waitingForResponse) to be set
}
return self.response!
}
When I use this same code with a CBCentralManager BLE connection, the main thread is blocked and does not receive the response from the connection. I've tried changing the CBCentralManager to a different queue, but I get the same results.
Does anybody have an idea how to wait in a loop and still be able to receive a BLE response? I know a response is being sent, but the thread is blocked and not reading it.
Using an async function with a completionHandler won't work for this use case because I need a reusable function that can issue a chain of commands that each depend on the result of the last response.

The CBCentralManager and CBPeripheral APIs are not really designed for this. You should be using the methods provided by CBPeripheralDelegate and CBCentralManagerDelegate protocols. Initialize your central manager with a dedicated queue so that you can listen for responses to your peripherals and act on that data as appropriate.

Related

Socket.io swift client disconnects automatically

I am using socket.io swift client in my app (version 13.1.0) Things work fine, but sometimes this scenario occurs:
Socket gets disconnected (3 out of 10 times). After it gets disconnected it tries and gets connected successfully, but after this it gets disconnected again and this forms an infinite loop of connected - disconnected state. I am searched but didn't get any possible solution.
My code is very simple. I have created a singleton class and its code is given as follows:
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
let manager = SocketManager(socketURL: URL(string: "URL")!, config: [.compress, .log(true), .reconnects(false)])
override init() {
super.init()
let socket = manager.defaultSocket
socket.on("connect") {data, ack in
NSLog("*************** SOCKET CONNECTED")
}
}
func establishConnection() {
let socket = manager.defaultSocket
socket.connect()
}
}
Other than this I am elegantly disconnecting socket when app goes in background and then connecting it again when app comes in foreground.
Things I have tried:
Tried the configs parameters: forceNew(true/false), reconnects(true/false), forceWebsockets(true/false)
In order to keep the connection active, I am sending an empty parameter to the server every 10 seconds, so that it knows that the app is in active state.
My observation:
I am using Alamofire in the same project and it send requests in background thread. Might be possible that at some point of time both libs start using the same background thread and socket connection breaks.
Is this possible? Correct me if I am wrong in my understanding.
What should I do to overcome this socket disconnection problem. I am a newbie to iOS so any help would be appreciated.

Pattern for retrying URLSession dataTask?

I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.

How can I wait for Socket.IO to have a socket connection established before connecting a user to the server?

I'm running SocketIO-Client-Swift in order to open WebSockets and communicate with a server. However the problem I'm having is that I want to connect to the socket and then connect to the server with an emit.
SocketIOManager.sharedInstance.connectToSocket()
SocketIOManager.sharedInstance.connectToServer(self.myUsername)
And those point to functions here:
func connectToSocket() {
addHandlers()
socket.joinNamespace("/chat")
socket.connect()
}
func addHandlers() {
self.socket.on("connectUser") { data in
print("socket connected")
}
}
func connectToServer(username: String) {
socket.emit("connectUser", username)
}
Even after registering the handle, the emit is being called before the socket is properly connected. The connect works fine after the connect has been established as I can properly send messages and get feedback after my Log shows a connection.
You need to wait for the client to receive the connect event. That will signal that it is now connected and you can safely send initial data on the connection from that event handler.

Socket.io does not close the socket on demand (and does not allow my swift app to reconnect again to the node.js server)

I am playing around with socket.io library, I already implemented the server side (in node.js). I try to follow a very simple scenario:
1) user establishes connection
2) user connects to the room
3) user disconnects
4) user reconnects
I'm using ios app (written in swift) on a client side.
My server code is as follows:
io.on('connection', function(clientSocket){
console.log('a user connected here');
clientSocket.on('disconnect', function(){
console.log('user disconnected');
});
clientSocket.on('connectUser', function(username){
clientSocket.join(username);
console.log('user ' + username + ' connected to the room.');
});
Now in my client side I created a class responsible for handling all socket stuff:
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: serverURL)!, options: [.ForceNew(true)])
override init() {
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
func connectToServerWithNickname(nickname: String) {
print("CONNECTING TO SOCKET SERVER \(nickname)")
socket.emit("connectUser", nickname)
}
}
Now - further on - in my swift app (in applicationDidBecomeActive) I'm calling establishConnection() and then SocketIOManager.sharedInstance.connectToServerWithNickname(username).
In my server console I see:
a user connected here
user 571fc6818451630f5becda9c connected to the room.
In my appDelegate I implemented this function:
func applicationWillResignActive(application: UIApplication) {
SocketIOManager.sharedInstance.closeConnection()
print("closing sockets")
}
so now when I put my app in the background I see closing sockets and in my server console I see user disconnected.
And now to the main problem - I want to reconnect user when he comes back to the app. Therefore I implemented this method:
func applicationDidBecomeActive(application: UIApplication) {
print("application is back to life")
SocketIOManager.sharedInstance.establishConnection()
SocketIOManager.sharedInstance.connectToServerWithNickname(username)
}
But now when I wake up the app from the background, in my server console I only see a user connected here. So this message:
user 571fc6818451630f5becda9c connected to the room.
is missing and I assume it's because my app is not reconnecting to the socket.
Now from what I've read, socket.io is really buggy and people recommend switching to other libraries, but I would like to use it anyway. I found this issue on github https://github.com/socketio/socket.io-client/issues/251 and they recommend to use a specific parameter on client side. Following this plugin's page for swift https://github.com/nuclearace/Socket.IO-Client-Swift I found the specific flag:
case ForceNew(Bool) // Will a create a new engine for each connect. Useful if you find a bug in the engine related to reconnects
and that is why I'm constructing my socket on client side as:
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: serverURL)!, options: [.ForceNew(true)])
but yeah, even in the official code of the Socket.IO-Client-Swift library there's a comment above the disconnect method:
/// Disconnects the socket. Only reconnect the same socket if you know what you're doing.
/// Will turn off automatic reconnects.
public func disconnect() {
DefaultSocketLogger.Logger.log("Closing socket", type: logType)
reconnects = false
didDisconnect("Disconnect")
}
Does anyone know how could I handle that? I want to close the socket each time my app goes to the background and connect to it again when my app goes to the foreground. Thanks!
add your code in applicationWillEnterForeground
SocketIOManager.sharedInstance.establishConnection()
SocketIOManager.sharedInstance.connectToServerWithNickname(username)

Repeat an asynchronous request if it fails?

I am writing an ios app that relies on being able to tell when a user is connected to wifi and, when he or she connects or disconnects, send an asynchronous request using alamo fire.
The first time I connect, my asynchronous succeeds.
However, after I first connect, any toggling of the wifi results in 404s.
I suspect this is because I am sending the request as soon as the user connects/disconnects, meaning that for a brief moment he or she has no internet service.
My question is, can I repeat the request if it fails or is it possible to "cache" the requests I want to make and wait until the user has internet connection to make them?
There are many solutions to solve this. One is to call the download method recursively again and so implementing an automatic retry mechanism on errors:
func downloadSomething() {
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
if let error = error {
log(error)
self.downloadSomething() // recursive call to downloadSomething
} else {
// do something on success
}
}
}
You can extend this by:
showing the user also an altert view asking him if he want's to retry
the download or not before retrying the download. (depending on your
UI strategy on network errors)
a specified count of automatic re-trys and then ask the user.
checking the error status code and then depending on the code do
different network error handling strategies...
etc...
I think there is no needed to re-invented apple code like reachability or this swift reachability porting. You can able to check if a user is connected to the net or wifi very easily:
class func hasConnectivity() -> Bool {
let reachability: Reachability = Reachability.reachabilityForInternetConnection()
let networkStatus: Int = reachability.currentReachabilityStatus().rawValue
return networkStatus != 0
}
For a Wi-Fi connection:
(reachability.currentReachabilityStatus().value == ReachableViaWiFi.value)

Resources