Posting notification halts the further execution of code - ios

I have a singleton object for observing Network status, it's based around NWPathMonitor class.
In the pathUpdateHandler callback I want to post a custom notification with current interface type of the path and then the strangest thing happens: I have place breakpoints before and after the .post method and the second breakpoint is never reached and the notification does is not posted. What could be the issue? It's the first time I've encountered such situation
class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
enum InterfaceType: String {
case wifi
case cellular
case other
case none
}
private init() {
monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
print("Before notification")
NotificationCenter.default.post(name: NetworkMonitor.connectionChangedNotification, object: nil, userInfo: ["interface": path.interfaceType])
print("Notification posted") // This line is not being printed
}
}
public func start() {
let queue = DispatchQueue.global(qos: .utility)
monitor.start(queue: queue)
}
}
fileprivate extension NWPath {
var interfaceType: NetworkMonitor.InterfaceType {
guard status == .satisfied else {
return .none
}
if usesInterfaceType(.wifi) {
return .wifi
} else if usesInterfaceType(.cellular) {
return .cellular
} else {
return .other
}
}
}
EDIT:
I have subscribed to this notification in two places, A and B. I noticed that the callback in object B gets called twice and in A not one time. I have no idea what is going on here, for now I have commented out the code in B and everything is working properly, but I mean, what the heck.
In both objects I have
var connectionObserver: Any?
connectionObserver = NotificationCenter.default.addObserver(forName:
NetworkMonitor.connectionChangedNotification, object: nil, queue: nil) {
...
}
If I don't use the connectionObserver variable the bug also exists
Changing the rawValue of notification name does not change anything

Related

using NWPathMonitor with BehaviorSubject to monitor network connectivity

I need an observer to keep my app updated on the connectivity status of the device.
NWPathMonitor seems to be the standard approach. So I go like this:
class NetworkService {
let monitor = NWPathMonitor()
let connected = BehaviorSubject(value: true)
private init() {
monitor.pathUpdateHandler = { path in
let value = path.status == .satisfied
self.connected.onNext(value)
}
let queue = DispatchQueue(label: "NetworkMonitor")
monitor.start(queue: queue)
}
}
And this is where I subscribe to connected
NetworkService.shared.connected.subscribe(onNext: { connected in
print("network connected: \(connected)")
}).disposed(by: disposeBag)
As soon as the app starts, onNext starts firing like crazy, flooding the console with network connected: true until the app crashes.
I tried adding a local cache variable so the onNext part fires only if there's been a change on the value.
if (value != self.previousValue) {
self.previousValue = value
self.connected.onNext(value)
}
Same happens still. So I guessed maybe the monitor is updating too frequently to allow the cache variable to get assigned, and I tried adding a semaphore ...
self.semaphore.wait()
if (value != self.previousValue) {
self.previousValue = value
self.connected.onNext(value)
}
self.semaphore.signal()
And event that didn't help. Still getting a flood of print messages and the app crashes.
BTW, if you were wondering this is how I declare the semaphore in my class:
let semaphore = DispatchSemaphore( value: 1)
I'm not seeing the same behavior from the class as you, but a simple solution is to use .distinctUntilChanged() which will stop an event from propagating unless it is different than the previous event.
If the above doesn't stop the flood of events, then the problem isn't with the code you have presented, but with something else you haven't told us about.
Also, I would have written it like this:
extension NWPathMonitor {
var rx_path: Observable<NWPath> {
Observable.create { [self] observer in
self.pathUpdateHandler = { path in
observer.onNext(path)
}
let queue = DispatchQueue(label: "NetworkMonitor")
self.start(queue: queue)
return Disposables.create {
self.cancel()
}
}
}
}
With the above, it's easy to access by doing:
let disposable = NWPathMonitor().rx_path
.map { $0.status == .satisfied }
.debug()
.subscribe()
The subscription will keep the NWPathMonitor object alive for the duration of the subscription. Calling dispose() on the disposable will shut down the subscription and release the NWPathMonitor object.

Why is UIAccessibility.post(notification: .announcement, argument: "arg") not announced in voice over?

When using Voice Over in iOS, calling UIAccessibility.post(notification:argument:) to announce a field error doesn't actually announce the error.
I have a submit button and, when focusing the button, voice over reads the button title as you would expect. When pressing the button, voice over reads the title again. When the submit button is pressed, I am doing some validation and, when there is a field error, I am trying to announce it by calling:
if UIAccessibility.isVoiceOverRunning {
UIAccessibility.post(notification: .announcement, argument: "my field error")
}
Interestingly enough, if I stop on a breakpoint in the debugger the announcement happens. When I don't stop on a breakpoint, the announcement doesn't happen.
The notification is posting on the main thread and, if is like NotificationCenter.default, I assume that it is handled on the same thread it was posted on. I have tried to dispatch the call to the main queue, even though it is already on the main thread, and that doesn't seem to work either.
The only thing that I can think is that the notification is posted and observed before voice over is finished reading the submit button title and the announcement notification won't interrupt the current voice over.
I would really appreciate any help on this.
This is an admittedly hacky solution, but I was able to prevent the system announcement from pre-empting my own by dispatching to the main thread with a slight delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
UIAccessibility.post(notification: .announcement, argument: "<Your text>")
}
Another work around is to use .screenChanged instead and pass the error label, as:
UIAccessibility.post(notification: .screenChanged, argument: errorLabel)
Your problem may happen because the system needs to take over during the field error appears and, in this case, any customed VoiceOver notification is cancelled.🤯
I wrote an answer about problems with queueing multiple VoiceOver notifications that may help you to understand your current situation.🤓
Your notification works with a breakpoint because you're delaying it and the system works during this time : there's no overlap between your notification and the system work.
A simple solution may be to implement a short delay before sending your notification but the delay depends on the speech rate that's why this is only a temporary workaround. 🙄
Your retry mechanism is smart and could be improved inside a loop of few retries in case of many system takeovers. 👍
I was facing the same issue, so I picked up #brandenesmith idea of the notification queue and wrote a little helper class.
class AccessibilityAnnouncementQueue {
static let shard = AccessibilityAnnouncementQueue()
private var queue: [String] = []
private init() {
NotificationCenter.default.addObserver(self,
selector: #selector(announcementFinished(_:)),
name: UIAccessibility.announcementDidFinishNotification,
object: nil)
}
func post(announcement: String) {
guard UIAccessibility.isVoiceOverRunning else { return }
queue.append(announcement)
postNotification(announcement)
}
private func postNotification(_ message: String) {
let attrMessage: NSAttributedString = NSAttributedString(string: message, attributes: [.accessibilitySpeechQueueAnnouncement: true])
UIAccessibility.post(notification: .announcement, argument: attrMessage)
}
#objc private func announcementFinished(_ sender: Notification) {
guard
let userInfo = sender.userInfo,
let firstQueueItem = queue.first,
let announcement = userInfo[UIAccessibility.announcementStringValueUserInfoKey] as? String,
let success = userInfo[UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool,
firstQueueItem == announcement
else { return }
if success {
queue.removeFirst()
} else {
postNotification(firstQueueItem)
}
}
}
I am able to get this to work using a retry mechanism where I register as an observer of the UIAccessibility.announcementDidFinishNotification and then pull the announcement and success status out of the userInfo dictionary.
If the success status is false and the announcement is the same as the one I just sent, I post the notification again. This happens on repeat until the announcement was successful.
There are obviously multiple problems with this approach including having to de-register, what happens if another object manages to post the same announcement (this shouldn't ever happen in practice but in theory it could), having to keep track of the last announcement sent, etc.
The code would look like:
private var _errors: [String] = []
private var _lastAnnouncement: String = ""
init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(announcementFinished(_:)),
name: UIAccessibility.announcementDidFinishNotification,
object: nil
)
}
func showErrors() {
if !_errors.isEmpty {
view.errorLabel.text = _errors.first!
view.errorLabel.isHidden = false
if UIAccessibility.isVoiceOverRunning {
_lastAnnouncement = _errors.first!
UIAccessibility.post(notification: .announcement, argument: _errors.first!)
}
} else {
view.errorLabel.text = ""
view.errorLabel.isHidden = true
}
}
#objc func announcementFinished(_ sender: Notification) {
guard let announcement = sender.userInfo![UIAccessibility.announcementStringValueUserInfoKey] as? String else { return }
guard let success = sender.userInfo![UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool else { return }
if !success && announcement == _lastAnnouncement {
_lastAnnouncement = _errors.first!
UIAccessibility.post(notification: .announcement, argument: _errors.first!)
}
}
The problem is that this retry mechanism will always be used because the first call to UIAccessibility.post(notification: .announcement, argument: _errors.first!) always (unless I am stopped on a breakpoint). I still don't know why the first post always fails.
If somebody uses RxSwift, probably following solution will be more suitable:
extension UIAccessibility {
static func announce(_ message: String) -> Completable {
guard !message.isEmpty else { return .empty() }
return Completable.create { subscriber in
let postAnnouncement = {
DispatchQueue.main.async {
UIAccessibility.post(notification: .announcement, argument: message)
}
}
postAnnouncement()
let observable = NotificationCenter.default.rx.notification(UIAccessibility.announcementDidFinishNotification)
return observable.subscribe(onNext: { notification in
guard let userInfo = notification.userInfo,
let announcement = userInfo[UIAccessibility.announcementStringValueUserInfoKey] as? String,
announcement == message,
let success = userInfo[UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool else { return }
success ? subscriber(.completed) : postAnnouncement()
})
}
}
}

How to save webservice call and use it when internet connection available in iOS?

I m trying to implement webservice call, when user takes screenshot, and I have successfully implemented it with the bellow method.
//this method get called when screenshot captured
func detectScreenShot() {
let mainQueue = OperationQueue.main
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationUserDidTakeScreenshot,
object: nil,
queue: mainQueue,
using: { notification in
WEBSERVICE_INTERFACE.webServiceWithPostJSONParameters(param: nil, methodName: Constants.URLs.screenshot, headers: Constants.Headers.urlEncoded, showProgress: false, completion: { (response) in
if let response = response{
let response = BaseResponse(JSONString : response)
if let message = response?.message{
LIMITUtils.showAlertMessage(message: message)
}
}
})
})
}
But now I have the scenario, suppose user takes the screenshot by making internet connection off, then no webservice call will happen and user will get the benefits out of it. Now I wanted to have some sort of solution where I can save the webservice call if no connection is available and make the same call when internet connection becomes available. Can anyone please suggest me how I can proceed for this?
Use Reachability for check Internet available or not write bellow code to check internet
//MARK:- check internet connection
func checkInternetStatus() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(reachabilityChanged(_:)), name:ReachabilityChangedNotification, object: nil)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
//Check for Internet Status
do {
self.reachability = try Reachability.reachabilityForInternetConnection()
try self.reachability?.startNotifier()
} catch {
jprint("Errore")
return
}
}
}
func reachabilityChanged(note:NSNotification) {
let Status = reachability?.currentReachabilityStatus //note.object as! Reachability
if Status != .NotReachable {
jprint("isNetworkAvailable = true")
isNetworkAvailable = true
} else {
jprint("isNetworkAvailable = false")
ShowFlinntAlert("No internet", description: "Your internet connection seems to be down")
isNetworkAvailable = false
}
}
Before API call just check
guard isInternetAvailable() else {
return
}

unexpected non void return value in void function

I've been pouring over stack overflow for ages trying to find a way out of this error:
unexpected non void return value in void function
that I am getting with returning a Bool within my function.
i just can't seem to dig my way out of this one. I'm sure its something to do with async but I'm not very familiar with these types of functions.
class CheckReachability {
class func setupReachability (hostName:String?, useClosures: Bool) -> Bool{
var reachability : Reachability!
var connected = false
let reachability2 = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
reachability = reachability2
try! reachability?.startNotifier()
if useClosures {
reachability2?.whenReachable = { reachability in
DispatchQueue.main.async {
connected = true
print("Reachable....")
}
}
reachability2?.whenUnreachable = { reachability in
DispatchQueue.main.async {
connected = false
print("Not Connected....")
}
}
} else {
NotificationCenter.default.addObserver(self, selector: Selector(("reachabilityChanged:")), name: ReachabilityChangedNotification, object: reachability2)
}
return connected
}
}
calling this from viewdidload on another vc doesn't allow enough time to get a true result
let connected = CheckReachability.setupReachability(hostName: nil, useClosures: true)
if connected {
Your question is confusing because the code you posted does not have the error you describe. However, you're trying to create a function that returns a result from an async function. That is not how async works.
Async functions start to do a task in the background, where that task won't be finished before it's time to return.
You need to adjust your thinking. Instead of trying to return a result from your function, you need to write your function to take a completion handler. You then call the completion handler once the long-running task has finished (which is after your function has returned.)
#bubuxu provided you with code showing how to modify your function as I described.
If you want to write a checking class to listen to the reachability, define it as a singleton and pass the completeBlock to it like this:
class CheckReachability {
static let shared = CheckReachability()
var reachability: Reachability?
func setupReachability(hostName:String?, completeBlock: ((Bool) -> Void)? = nil) {
reachability = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
try? reachability?.startNotifier()
if let block = completeBlock {
reachability?.whenReachable = { reachability in
DispatchQueue.main.async {
print("Reachable....")
block(true)
}
}
reachability?.whenUnreachable = { reachability in
DispatchQueue.main.async {
print("Not Connected....")
block(false)
}
}
} else {
// If we don't use block, there is no point to observe it.
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(_:)), name: .ReachabilityChangedNotification, object: nil)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
#objc func reachabilityChanged(_ notification: Notification) {
// ?? what should we do here?
}
}

access reachability with enough time to return true state

i have a reachability class which is checked from within my VC code as below:
func setupReachability (hostName:String?, useClosures: Bool) {
let reachability = hostName == nil ? Reachability() : Reachability(hostname: hostName!)
self.reachability = reachability
try! self.reachability?.startNotifier()
if useClosures {
reachability?.whenReachable = { reachability in
DispatchQueue.main.async {
self.connected = true
print("Reachable....")
}
}
reachability?.whenUnreachable = { reachability in
DispatchQueue.main.async {
self.connected = false
print("Not Connected....")
}
}
} else {
NotificationCenter.default.addObserver(self, selector: Selector(("reachabilityChanged:")), name: ReachabilityChangedNotification, object: reachability)
}
}
this is initialised from within viewDidLoad:
setupReachability(hostName: nil, useClosures: true)
print(self.connected)
the problem i am having is that it always returns false and i need this to load with enough time that it returns true (if it is really connected) at this point. I'm struggling to figure out where to place my setupReachability function and initialise it so that it can be accessed from anywhere in the app and return its connected state. Ive tried placing this in a separate class but can't seem to get it working or access the connected state. Any tips on how to achieve this?

Resources