How Can I set timeout interval when use bluetooth - ios

When I use bluetooth to write data, I hope get response. But when the peripheral goes wrong, it doesn't send notification. I need set a timeout interval to handle this bad interaction. Like we use urlrequest:
/// Creates and initializes a URLRequest with the given URL and cache policy.
/// - parameter: url The URL for the request.
/// - parameter: cachePolicy The cache policy for the request. Defaults to `.useProtocolCachePolicy`
/// - parameter: timeoutInterval The timeout interval for the request. See the commentary for the `timeoutInterval` for more information on timeout intervals. Defaults to 60.0
public init(url: URL, cachePolicy: CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60.0) {
_handle = _MutableHandle(adoptingReference: NSMutableURLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval))
}
How could I make it.

By "when the peripheral goes wrong" - if you mean that the peripheral crashes or stops working then you should get a BLE disconnection event to indicate the crash:
(centralManager:didDisconnectPeripheral:)
If this is not the case and you just stop receiving notifications after some time and the BLE connection is still alive, then there is no way to tell why the peripheral stopped sending notifications. The reason for this is that there is no specific "time" associated with notifications. Some peripherals send notifications every 1 second and some send notifications every 1 week. Some peripherals send notifications on a value change (e.g. if the temperature increases by 1 degree) and some send notifications on a user action (e.g. the user pressed a button).
The only workaround for this is if you add a timer in your central device, then every time you receive a notification using:
peripheral(_:didUpdateValueFor:error:)
you can reset that timer (if it is the exact notifications you're expecting to timeout). Then if the timer expires, you know that you did not receive your notification on time as expected and therefore you can flag an error or force a disconnection. This is just one example and there are a few variations of this that you can create (e.g. set a flag on peripheral(_:didUpdateValueFor:error:) that you check and reset every 30 seconds). You can find more information about timers in the links below:-
The ultimate guide to timer
Timer: Apple Developer Documentation
iOS Timer Tutorial
I hope this helps.

This is an interesting question.
After checking CoreBluetooth, i figured out that there's already an timeout Error in their list .
https://developer.apple.com/documentation/corebluetooth/cberror/2325746-connectiontimeout
But It's only works for the first time connect to peripheral.
So I think that, if you send a request, but you don't get any response, you have to make your own timer manager.
The concept for timeout is quite complicated. You need make a queue for timer, It's FIFO. You need an uniqueId for each request and you have to map it with your expected response.
Ex :
You call :
Call to get health info via BLE (any thread).
Call to get height info via BLE (any thread).
Call to get health info via BLE (any thread).
Your response :
height info.
health info.
After mapping your request with your response, you will notice that there's one expected response is missing. So how to know the request 1 or request 3 is missing response. It's your choice.
In conclusion, I think that you need a queue request and a queue expected response and a queue for timer. To manage these queue is not really a very big problem.

Related

How to end a twilio call prematurely

I want to use twilio to test our internal phone system, and make sure calls are routing as they should, since our provider is notoriously bad of notifying us to problems.
I'm can initiate a call from twilio, use the "gather" verb to record speech (to ensure we hit the right queue) and then hang up. Everything works fine. Except that the gather ends up taking over 2 minutes to listen to the whole message from our phone system, charging us for 8 15 second gather chunks. I only need the first 15 seconds, but can't figure out how to hangup sooner. Is there a simple way to limit calls to a specific time?
timeLimit, and timeout both don't apply here, since timeLimit only works inside of a dial verb, and timeout only works for pauses in speech during the gather.
Perhaps just set a timer in your code for 15 seconds or so and then use the POST endpoint at /2010-04-01/Accounts/{AccountSid}/Calls/{CallSid} to cancel the call (using the Status=Completed parameter in order to cancel calls even if they are in progress).
If you use their Ruby SDK, and you make a normal call (not a conference call) then you can use the update method:
client = Twilio::REST::Client.new(account_sid, auth_token)
# fetch all in-progress calls between the two numbers
client.calls.list(from: '+11231231234',
to: '+12312311234',
status: 'in-progress').each do |c| #it's supposed to be just one record, but you can play it safe
c.update(status: 'completed')
end
Updating the status to completed should hangup the call if in-progress.
Updating the status to canceled should hangup the call if ringing/queued.
If you know for sure that the call is in-progress and you know the call sid, then you can use:
client = Twilio::REST::Client.new(account_sid, auth_token)
in_progress_call = client.calls(call_sid).fetch
in_progress_call.update(status: 'completed') if in_progress_call.present?
There is some general information in the official docs. Also snippets are available for the other SDKs.
You can find the source code of the update method here for more details.

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 do you "resolve" a GKTurnBasedExchange?

I'm using a GKTurnBasedExchange to send data one way. It's a notification to the other players as certain triggers happen. However, the other players may not even be in the game at that time. The turns have 48 hour timeouts, so in theory if player1 sends said exchange, player3 might not pick it up for a couple of days. That's fine, player 1 doesn't require or expect any response.
But, when player1 tries to save the match data, end the turn or quit the match, I get an error:
Error Domain=GKErrorDomain Code=3 "The requested operation could not
be completed due to an error communicating with the server."
UserInfo=0x19317970 {GKServerStatusCode=5134,
NSUnderlyingError=0x16f15db0 "The operation couldn’t be completed.
status = 5134, Invalid operation for this session because the exchange
was not resolved. All exchanges must be resolved before the current
player can complete this operation.
OK, the bolded text seems pretty self-explanatory except for one little detail: I can't find any reference anywhere to what constitutes a "resolved" exchange. I don't expect a response back to this message. Even if I did, it could take days to receive it. The only option I can see is for the sender to cancel the exchange, which defeats the purpose of sending the exchange in the first place
So, how exactly does one finalize an exchange? What series of steps, besides canceling the exchange, will satisfy game center that the exchange has been "resolved?"
I'm just using:
[theMatch sendExchangeToParticipants:exchangeParticipants
data:exchangeData
localizableMessageKey:#"F1"
arguments:nil
timeout:600
completionHandler:^(GKTurnBasedExchange *exchange, NSError *error)
{
if (error)
{
VLOG(LOWLOG, #"%#", [error description]);
}
}
];
Followed by:
[theMatch saveCurrentTurnWithMatchData:dataCopy completionHandler:^(NSError *error)
{
if (error)
{
VLOG(LOWLOG, #"%#", [error description])
}
}];
The saveCurrentTurnWithMatchData call returns the aforementioned error.
Thanks!
I think I figured this out. Man oh man it's a slog making sense of Apple's documentation.
In the end I didn't make sense of it actually: this is the result of brute force trial and error.
So here's the deal: you can do a lot of things with exchanges, but to actually resolve them you have to call:
saveMergedMatch(matchData: Data, withResolvedExchanges: [GKTurnBasedExchange], completionHandler: ((Error?) -> Void))
There are several catches though.
Only the match's currentParticipant (the player who's turn it is) can call saveMergedMatch successflly.
It will only work for exchanges that are not in .active status.
There are only two ways, as far as I can tell, to programmatically get an exchange out of .active status.
The harder way: all recipients of the exchange have to act on the exchange--itself a murky process, can't help there. If all recipients do respond to it, Game Center itself will handle changing the status of the exchange, I think. Not sure though, because I don't do this. [The only help I can give here is that if you give an exchange a really short timeout when you send it, after the timeout passes Game Center will adjust the exchange's status automatically, and your recipients won't have to do anything.]
The not-much-easier way: the sender and only the sender of the exchange can cancel it, by calling the exchange's method cancel(withLocalizableMessageKey key: String, arguments: [String], completionHandler: ((Error?) -> Void)? = nil). The saving grace of this way is that it doesn't have to be the sender's turn for them to cancel it. This is one of the few times that the current turn-taker doesn't matter. Unfortunately this also has a catch: the exchange can't be cancelled if anyone has already responded to it.
So the upshot is that there is no way for any single player to be guaranteed to be able to resolve an exchange, for two reasons:
The process that moves active exchanges into completed exchanges is only programmatically accessable for the purpose of cancelling the exchange, and only by the player that originated it, and only if no one has responded yet.
The actual process that turns completed exchanges into resolved exchanges can only be triggered by the player in the match's currentParticipant property.
Personally, I can work with this, now that I understand it (I hope) , but without doubt it is quite a pain.
Well, turns out exchanges can't be used. Yet another limitation in Game Kit. For anyone that comes across this thread, I found WWDC 2013 session 506 says:
All participants have to respond for game center to mark the exchange as "completed"
You have to call:
[match saveMergedMatchData:dataCopy
withResolvedExchanges:match.completedExchanges
completionHandler:...];
So, you can't use exchanges for 1-way communications. There has to be a response (or wait for a timeout).
I guess I'll answer to the unasked question of: how to send 1-way communications With notifications.
I think the best API for that is setLocalizableMessageWithKey(key:arguments:) or sendReminderToParticipants(localizableMessageKey:arguments:completionHandler:) on your GKTurnBasedMatch instance.

AFNetworking - re-trying until API responds correctly

I'm using an API which returns a key "status" which can either be "completed" or "not completed". I have to to keep asking it until it is "completed". I want to be able to keep requesting the response until I get:
{
"status": "completed"
}
Is there a way to do this with AFNetworking? Where I can set a maximum number of requests, and a time interval between requests.
I don't believe so. You will need to maintain your own timer to trigger the checks and invalidate the timer once the success status you're waiting for is seen.

How return data from an HTTP request in Swift/Objective C

I'm trying to use Coinbase's API to get information about my online bitcoin wallet, and I'm trying to use Swift's NSURLSession object to do so. Perhaps I'm missing something obvious in the Apple docs, but after reading through the information about NSURLSession and NSURLSessionTask I still do not understand how to make an HTTP request and then return the body of the response so that the body can persist throughout the life of my app. As of now I only see the ability to use completion blocks which return void, or delegates which either return void themselves or use completion blocks which also return void. I want to use the data I get from the response later in the app, but because I'm using completion blocks I must handle the response data immediately after the response arrives.
To make it clear, I want to do something along the lines of the pseudocode function below:
func makeHTTPCall(urlString : String) -> String? {
create NSURLSession object
create request with proper headers and using the passed-in urlString
use the session object to send out the request
get the response object, extract the response body as a string, and return it
}
Then later, I could call something like this:
let myObject : MyObject = MyObject()
let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")
This data is returning a JSON Object string, which is the String I want to persist beyond the life of the response and its completion block. How can I do this in either Swift or Objective C, since I'll be able to use either in Xcode 6?
EDIT: Two answers have been posted, but they miss the fundamental point of this question. I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
In the edit to your question, you say:
I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
I understand the appeal of this strategy, because it feels so intuitively logical. The problem is that your networking requests should always run asynchronously (e.g. use that completion handler pattern to which you allude).
While there are techniques making a function "wait" for the asynchronous request to complete (i.e. to make the asynchronous NSURLSession method behave synchronously or use one of the old synchronous network request methods), this is a really bad idea for a number of reasons:
If you do this from the main thread, it results in a horrible user experience (the app will be unresponsive while the request is in progress and the user won't know if the app is busy doing something or whether it's frozen for some unknown reason).
Again, if you do this from the main thread, you also risk having the iOS "watch dog" process kill your app (because if you block the main queue for more than a few seconds at the wrong time, particularly as the app comes to foreground, the OS will unceremoniously terminate your app). See Technical Q&A #1693 for a discussion on the problems of doing synchronous network requests.
We generally prefer the asynchronous network techniques because they offer more features unavailable with synchronous techniques (e.g. making requests cancelable, offer progress updates when using delegate-based network requests, etc.).
You really should use the completion handler pattern that those other questions suggest, and manage the changing state of the app in those handlers. In those situations where you absolutely cannot let the user proceed until some network request is done (e.g. you can't let the user buy something until you confirm their bitcoin balance, and you can't do that until they log in), then change the UI to indicate that such a request is in progress. For example, dim the UI, disable the controls, pop up an activity indicator view (a.k.a., a "spinner"), etc. Only then would you initiate the request. And upon completion of the request, you would restore the UI. I know it seems like a lot, but it's the right way to do it when the user absolutely cannot proceed until the prior request is done.
I'd also think long and hard as to whether it's truly the case that you absolutely have to force the user to wait for the prior network request to complete. Sometimes there are situations where you can let the user do/review something else while the network request is in progress. Yes, sometimes that isn't possible, but if you can find those sorts of opportunities in your app, you'll end up with a more elegant UX.
I know that problem and use this code for synchronous requests:
func synchronousRequest() -> NSDictionary {
//creating the request
let url: NSURL! = NSURL(string: "exampledomain/...")
var request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
var response: NSURLResponse?
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
error = nil
let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
return resultDictionary
}
What you are asking for is a synchronous network request. There are many ways to do this, such as...
NSData's init(contentsOfURL aURL: NSURL!)
NSURLConnection's synchronous request method
...etc.
These methods will block the current thread until they complete - which can be a potentially long time. Network requests can have very high timeouts, it may be several minutes before the device gives up. NSData's init with contents of URL will return NSData, not void, and does not execute asynchronously. It will block until it is complete, which is why it's recommended to not do these types of requests from the main thread. The UI will be frozen until it completes.
In general the use of synchronous networking methods is discouraged. Asynchronous network requests are greatly preferred for a number of reasons. Using an asynchronous method that takes a completion block as a parameter will not prevent you from using the returned data elsewhere in your application. The block is executed when the network request has finished (wether it succeeds or fails) and it is passed the data, response metadata, and error. You are free to do what you want with that data - nothing prevents you from persisting it, passing it off to another object, etc. Based on your comments it sounds like you want to take the data that was the result of the network request and set it as the value of a property somewhere - that is entirely doable using an asynchronous method that uses a block as a completion handler.
In objective-C you can use __block and get the data when the operation finishes:
__block NSData *myData;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
myData = data;
}] resume];

Resources