When there is no connection I get an error from the URL Session saying that the request timed out.
I’m using the Network protocol to check for connectivity before hand but apparently this is not working as when I am calling this inside viewDidLoad:
static func startUpdateProcess() {
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("Good! We are connected!")
Helper.createDownloadTask()
} else {
print("No connection. Local file not updated!")
}
}
let queue = DispatchQueue(label: "Monitor")
monitor.start(queue: queue)
}
...I get “Good! We are connected!”.
Shouldn’t the path not be satisfied if there is no connection and therefore trigger the else statement?
FYI the createDownloadTask() questions the API and downloads the required data.
Can you tell me what is wrong here and what could I do to get to the else statement if the path is not satisfied?
Thank you!
Credit to user May Rest in Peace for pointing me to the right direction.
Despite the Documentation being silent on the Network Protocol, it seems that the status property of the NWPath class, an enumeration of type NWPath.Status, returns .satisfied as long as the device is connected to a network, regardless of whether that network is working, transmitting data, or not.
The only way the else statement above could be triggered would have been by deactivating Wi-Fi and/or Cellular data or disconnecting from any network before launching the app.
All those properties are listed in the Documentation but none of them has a description or a discussion attached.
This article by user #twostraws allowed me to create the first part of that code.
Reference to the instance of NWPathMonitor (aka monitor in your scenario) needs to be retained.
You could make it a strong property by making monitor a class level property so that its lifecycle is the same as the place you are referring it from. It looks like the monitor object is being released effectively stopping the callbacks for network status monitoring.
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
This post was edited and submitted for review 1 year ago and failed to reopen the post:
Original close reason(s) were not resolved
Improve this question
My app supports iOS12 or later and i am using latest macOS and xcode. I did several research on this topic but couldn't found any proper solutions. Yes, i know how to check whether we are connected to the network or not and can get connection types too. But the problem is: is my connected network reachable? I am communicating with an IoT device, and i have to get some data from it, also send it to different server. That device always gives me the data because it is an intranet, as i am connected to it's hotspot. I have to check whether it can send data further on it's own to another remote server(sometimes network connection doesn't work) or if not i need to switch wifi connection to my local network. I have tried several ways but couldn't get satisfactory results.
What #Joakim Danielson said above is what you should be doing. No matter what source you use, it will always give you false information at some point (about being connected to internet or not).
Make the api call.
Check for errors - is it a temporary network error?
Make the call on whether you should retry or show something else to user.
How to check if it might be a temporary network error?
You can use this extension on NSError instance that was returned from api call.
public extension NSError {
var isConnectionAborted: Bool {
return (self.domain == NSPOSIXErrorDomain && self.code == 53)
}
var isTemporaryNetworkError: Bool {
let temporaryNetworkErrorCodes = [
NSURLErrorTimedOut,
NSURLErrorCannotFindHost,
NSURLErrorCannotConnectToHost,
NSURLErrorNetworkConnectionLost,
NSURLErrorDNSLookupFailed,
NSURLErrorHTTPTooManyRedirects,
NSURLErrorResourceUnavailable,
NSURLErrorNotConnectedToInternet,
NSURLErrorRedirectToNonExistentLocation,
NSURLErrorSecureConnectionFailed,
NSURLErrorCannotLoadFromNetwork,
NSURLErrorRequestBodyStreamExhausted,
]
return (self.isConnectionAborted || temporaryNetworkErrorCodes.contains(self.code))
}
var isCancelled: Bool {
return (self.domain == NSURLErrorDomain && self.code == NSURLErrorCancelled)
}
}
I tried an api call, it failed, now I want to query whether I'm connected to ineternet or not. How to know?
Somewhere in your API code, you can add an observer for possible network changes by using NWPathMonitor like this - requires iOS 12
import Network
let monitor = NWPathMonitor()
monitor.start(queue: DispatchQueue(label: "NetworkMonitor"))
monitor.pathUpdateHandler = { (path) in
if path.status == .satisfied {
print("Connected")
} else {
print("Not Connected")
}
}
This allows more granular control as well .cellular, .wifi & .wiredEthernet etc. -
let cellMonitor = NWPathMonitor(requiredInterfaceType: .cellular)
If you still support iOS 11, you can use a 3rd party library like - Reachability
Best practice is to just try: invoke the network request and check the result. What you do if you got an error depends on your use case, if there is a user involved or if the request was made in the background.
For example, you may just output an error and let the user proceed in the app. This may require to design your screen accordingly, i.e. provide a way where the user can retry it (in the screen where you show the content, show an empty content screen and provide a pull-to-refresh or a "retry" button, etc.).
Or, you may analyse the kind of error (URL error or response error) and may apply several strategies. For example, if the network request can be performed in the background you may apply a retry when you got a 5xx HTTP status. Or, if the connection was lost (no response and you get an URLError) you may start to observe the network state and retry when the network is available.
How to detect connection to internet?
I have a viewController that displays data fetched from a server. It is presented modally.
I display a message asking for an internet connection if a user is not connected to the internet. Otherwise, I show the data.
When a user connects to the internet without leaving the app, how to detect it? I assume it is possible to do it in Alamofire.
I have used the following code in viewDidLoad and viewWillAppear, but the listener is not called - If I connect & disconnect to WIFI, startListening is not called.
let network = NetworkReachabilityManager()
network!.startListening { status in
if status == .reachable(.cellular) || status == .reachable(.ethernetOrWiFi) {
self.fetchProducts()
} else {
self.noInternetConnnection()
}
}
FYI, you can unable both WIFI and Mobile Data in control center.
If you're declaring network inside a function it's probably being deallocated as soon as the scope ends.
Make sure you store it in an instance variable.
In short
We have a mobile app that streams fairly high volumes of data to and from a server through various bidirectional streams. The streams need to be closed on occasion (for example when the app is backgrounded). They are then reopened as needed. Sometimes when this happens, something goes wrong:
From what I can tell, the stream is up and running on the device's side (the status of both the GRPCProtocall and the GRXWriter involved is either started or paused)
The device sends data on the stream fine (the server receives the data)
The server seems to send data back to the device fine (the server's Stream.Send calls return as successful)
On the device, the result handler for data received on the stream is never called
More detail
Our code is heavily simplified below, but this should hopefully provide enough detail to indicate what we're doing. A bidirection stream is managed by a Switch class:
class Switch {
/** The protocall over which we send and receive data */
var protocall: GRPCProtoCall?
/** The writer object that writes data to the protocall. */
var writer: GRXBufferedPipe?
/** A static GRPCProtoService as per the .proto */
static let service = APPDataService(host: Settings.grpcHost)
/** A response handler. APPData is the datatype defined by the .proto. */
func rpcResponse(done: Bool, response: APPData?, error: Error?) {
NSLog("Response received")
// Handle response...
}
func start() {
// Create a (new) instance of the writer
// (A writer cannot be used on multiple protocalls)
self.writer = GRXBufferedPipe()
// Setup the protocall
self.protocall = Switch.service.rpcToStream(withRequestWriter: self.writer!, eventHandler: self.rpcRespose(done:response:error:))
// Start the stream
self.protocall.start()
}
func stop() {
// Stop the writer if it is started.
if self.writer.state == .started || self.writer.state == .paused {
self.writer.finishWithError(nil)
}
// Stop the proto call if it is started
if self.protocall?.state == .started || self.protocall?.state == .paused {
protocall?.cancel()
}
self.protocall = nil
}
private var needsRestart: Bool {
if let protocall = self.protocall {
if protocall.state == .notStarted || protocall.state == .finished {
// protocall exists, but isn't running.
return true
} else if writer.state == .notStarted || writer.state == .finished {
// writer isn't running
return true
} else {
// protocall and writer are running
return false
}
} else {
// protocall doesn't exist.
return true
}
}
func restartIfNeeded() {
guard self.needsRestart else { return }
self.stop()
self.start()
}
func write(data: APPData) {
self.writer.writeValue(data)
}
}
Like I said, heavily simplified, but it shows how we start, stop, and restart streams, and how we check whether a stream is healthy.
When the app is backgrounded, we call stop(). When it is foregrounded and we need the stream again, we call start(). And we periodically call restartIfNeeded(), eg. when screens that use the stream come into view.
As I mentioned above, what happens occasionally is that our response handler (rpcResponse) stops getting called when server writes data to the stream. The stream appears to be healthy (server receives the data we write to it, and protocall.state is neither .notStarted nor .finished). But not even the log on the first line of the response handler is executed.
First question: Are we managing the streams correctly, or is our way of stopping and restarting streams prone to errors? If so, what is the correct way of doing something like this?
Second question: How do we debug this? Everything we could think of that we can query for a status tells us that the stream is up and running, but it feels like the objc gRPC library keeps a lot of its mechanics hidden from us. Is there a way to see whether responses from server may do reach us, but fail to trigger our response handler?
Third question: As per the code above, we use the GRXBufferedPipe provided by the library. Its documentation advises against using it in production because it doesn't have a push-back mechanism. To our understanding, the writer is only used to feed data to the gRPC core in a synchronised, one-at-a-time fashion, and since server receives data from us fine, we don't think this is an issue. Are we wrong though? Is the writer also involved in feeding data received from server to our response handler? I.e. if the writer broke due to overload, could that manifest as a problem reading data from the stream, rather than writing to it?
UPDATE: Over a year after asking this, we have finally found a deadlock bug in our server-side code that was causing this behaviour on client-side. The streams appeared to hang because no communication sent by the client was handled by server, and vice-versa, but the streams were actually alive and well. The accepted answer provides good advice for how to manage these bi-directional streams, which I believe is still valuable (it helped us a lot!). But the issue was actually due to a programming error.
Also, for anyone running into this type of issue, it might be worth investigating whether you're experiencing this known issue where a channel gets silently dropped when iOS changes its network. This readme provides instructions for using Apple's CFStream API rather than TCP sockets as a possible fix for that issue.
First question: Are we managing the streams correctly, or is our way of stopping and restarting streams prone to errors? If so, what is the correct way of doing something like this?
From what I can tell by looking at your code, the start() function seems to be right. In the stop() function, you do not need to call cancel() of self.protocall; the call will be finished with the previous self.writer.finishWithError(nil).
needsrestart() is where it gets a bit messy. First, you are not supposed to poll/set the state of protocall yourself. That state is altered by itself. Second, setting those state does not close your stream. It only pause a writer, and if app is in background, pausing a writer is like a no-op. If you want to close a stream, you should use finishWithError to terminate this call, and maybe start a new call later when needed.
Second question: How do we debug this?
One way is to turn on gRPC log (GRPC_TRACE and GRPC_VERBOSITY). Another way is to set breakpoint at here where gRPC objc library receives a gRPC message from the server.
Third question: Is the writer also involved in feeding data received from server to our response handler?
No. If you create a buffered pipe and feed that as request of your call, it only feed data to be sent to server. The receiving path is handled by another writer (which is in fact your protocall object).
I don't see where the usage of GRXBufferedPipe in production is discouraged. The known drawback about this utility is that if you pause the writer but keep writing data to it with writeWithValue, you end up buffering a lot of data without being able to flush them, which may cause memory issue.
I am using Reachability class from Apple to detect network events that have impact in the functionality of my app. It is a voip app that uses setKeepAliveTimeout, so every ~10 minutes wakes up reads the network status and decides if the connection should be refreshed.
BOOL res = [app setKeepAliveTimeout:600 handler:^{
[[WIFI instance] isWifiConnected];
[[AClass an_instance] refresh];
}
}];
So every 10 minutes the isWifiConnected is called and the app reads the network status again.
- (BOOL) isWifiConnected {
self.wifiReach = [Reachability reachabilityForLocalWiFi];
NetworkStatus wifiStatus = [self.wifiReach currentReachabilityStatus];
switch (wifiStatus) {
case NotReachable: {
m_wifiConnected = NO;
LOG(#"NetStatus:NotReachable");
break;
}
case ReachableViaWiFi: {
m_wifiConnected = YES;
m_wwanConnected = NO;
LOG(#"NetStatus:ReachableViaWiFi");
break;
}
}
return m_wifiConnected;
}
Although I have WiFi in the device the call returns false, ie no WiFi, and moreover NotReachable for the net status.
However after a very short time interval the reachability callback is called again and the the wifi seems connected. However i have already fired an event due to the error value and the app closes the connection with the server believing that there is no wi-fi.
Doing some research it I found this in the Readme file of the Reachability.m file (provided from Apple)
By default, the application uses www.apple.com for its remote host. You can change the host it uses in APLViewController.m by modifying the value of the remoteHostName variable in -viewDidLoad.
IMPORTANT: Reachability must use DNS to resolve the host name before
it can determine the Reachability of that host, and this may take time
on certain network connections. Because of this, the API will return
NotReachable until name resolution has completed. This delay may be
visible in the interface on some networks
.
Could it this be the problem? The delay in the dns lookup? Or do I need to enhance my code as well?
When I initialize the app I call this
self.hostReach = [Reachability reachabilityWithHostName: #"www.apple.com"];
If I use an IP address like this is correct?
self.hostReach = [Reachability reachabilityWithHostName: #"1.2.3.4"];
Is it safe to use a public IP? eg "17.178.96.59" is the result of an nslookup for apple.com
There is a method in the Reachability class that seems to be used from the Apple's demo.
- (BOOL)connectionRequired
{
NSAssert(_reachabilityRef != NULL, #"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
Why the connectionRequired is needed? Can be used to resolved the problem?
To preserve battery charge, iOS will close down the networking hardware when it's not being actively used. This means turning off the WiFi and cellular radios. In this situation, Reachability isn't going to be able to report a complete result, because it can't check the situation without turning everything back on again. That is what kSCNetworkReachabilityFlagsConnectionRequired is for -- to tell you that you need to make a connection to wake the hardware back up.
What you are seeing is probably something waking up when the phone is unlocked (your app or some other app with background permissions) and so everything wakes up, you see an error immediately, but then WiFi connects and you are connected again.
You need to handle Reachability as if it can tell you "definitely reachable" or "definitely not reachable" but also "situation unknown". You need to decide what you're going to do in the unknown situation. If you make a network connection immediately then you will drain the battery faster than normal. An alternative might be just to wait for the network to be woken up for some other reason. That's really up to you.
Reachability should be created with a host name, not an explicit address. The whole point of the DNSsystem is that addresses change for hosts sometimes. Linking directly to a name server should provide some security in this regard, but that isn't how it's supposed to work.
Reachability is often a best guess, not a hard and fast. The only way to be sure is to actually try. Connection required is related to this, because it's the device saying 'everything looks ok, I just haven't tried to connect for real'.
So, arguably you should fire the keep alive without checking the reachability status and use the result of the request to decide if there's an error or not. If you need to be sure, send an actual request and review the actual result.
I am trying to make my iPhone app more robust, by ensuring that it doesn't crash when there is no network connection. Right now the app attempts to make a connection on startup immediately, through the app delegate. It has no issues if wifi or cellular is available, but it will crash if there is no network connection visible. I have looked around on this site and haven't found anything that quite fits my problem. I have the feeling it should be just a simple line of code, like an objective-c equivalent of a pseudo- 'isConnection', or something similar:
if (isConnection) {
- sendSynchronousRequest for json data I'm using
- manipulate the data, etc., and continue with normal operations
} else {
- send an output message to a view controller,
letting the user know what's wrong.
}
I can't seem to isolate the (admittedly abstract) "isConnection" condition that I'm looking for, specifically. Does anyone have experience or advice with this topic?
The reachability class is very easy to use. Download the class files here https://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html
You also need to add the SystemConfiguration.framework
Here's the code you need:
-(BOOL)isConnection {
Reachability *reach = [Reachability reachabilityWithHostName:#"www.google.com"];
//replace www.google.com with your own host you're checking for
NetworkStatus hostStatus = [reach currentReachabilityStatus];
if (hostStatus != NotReachable) {
//There are also other status enums like
//ReachableViaWiFi
//ReachableViaWWAN (3G/LTE)
//if you need to detect if user is on cellular, act accordingly
return YES;
}
return NO;
}
Then you can call your method:
if ([self isConnection]) {
//do something
} else {
//no connection, inform user
}
You can use the Reachability class that Apple provides in the Reachability sample application. Not only does it tell you if you're connected, but how you're connected (WiFi, cellular or no connection at all). You can even register for a notification when the connection status changes.
Even though you can use that to check the connection status before initiating the code you describe above, I still think you should investigate why the app crashes when you have no connection, and attempt to resolve it. You could always lose connectivity in the middle of a request attempt, and you don't want that to crash the app either.