Use NSPathMonitor instead of Reachability to monitor network connectivity

This commit is contained in:
Ali 2021-01-29 01:46:57 +05:00
parent c6270f78f1
commit fb05647d97
6 changed files with 243 additions and 135 deletions

View File

@ -1,20 +1,14 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
objc_library( swift_library(
name = "Reachability", name = "Reachability",
enable_modules = True,
module_name = "Reachability", module_name = "Reachability",
srcs = glob([ srcs = glob([
"Sources/*.m", "Sources/**/*.swift",
]), ]),
hdrs = glob([ deps = [
"PublicHeaders/**/*.h", "//submodules/Reachability/LegacyReachability:LegacyReachability",
]), "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
"SystemConfiguration",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,22 @@
objc_library(
name = "LegacyReachability",
enable_modules = True,
module_name = "LegacyReachability",
srcs = glob([
"Sources/*.m",
]),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
sdk_frameworks = [
"Foundation",
"SystemConfiguration",
],
visibility = [
"//visibility:public",
],
)

View File

@ -24,7 +24,7 @@ typedef enum : NSInteger {
extern NSString *kReachabilityChangedNotification; extern NSString *kReachabilityChangedNotification;
@interface Reachability : NSObject @interface LegacyReachability : NSObject
@property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status); @property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status);

View File

@ -14,7 +14,7 @@
#import <CoreFoundation/CoreFoundation.h> #import <CoreFoundation/CoreFoundation.h>
#import <Reachability/Reachability.h> #import <LegacyReachability/LegacyReachability.h>
#import <pthread.h> #import <pthread.h>
#import <libkern/OSAtomic.h> #import <libkern/OSAtomic.h>
@ -126,14 +126,14 @@ static ReachabilityAtomic *contexts() {
return instance; return instance;
} }
static void withContext(int32_t key, void (^f)(Reachability *)) { static void withContext(int32_t key, void (^f)(LegacyReachability *)) {
Reachability *reachability = [contexts() with:^id(NSDictionary *dict) { LegacyReachability *reachability = [contexts() with:^id(NSDictionary *dict) {
return dict[@(key)]; return dict[@(key)];
}]; }];
f(reachability); f(reachability);
} }
static int32_t addContext(Reachability *context) { static int32_t addContext(LegacyReachability *context) {
int32_t key = OSAtomicIncrement32(&nextKey); int32_t key = OSAtomicIncrement32(&nextKey);
[contexts() modify:^id(NSMutableDictionary *dict) { [contexts() modify:^id(NSMutableDictionary *dict) {
NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict]; NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
@ -155,19 +155,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
{ {
#pragma unused (target, flags) #pragma unused (target, flags)
//NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); //NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
//NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); //NSCAssert([(__bridge NSObject*) info isKindOfClass: [LegacyReachability class]], @"info was wrong class in ReachabilityCallback");
int32_t key = (int32_t)((intptr_t)info); int32_t key = (int32_t)((intptr_t)info);
withContext(key, ^(Reachability *context) { withContext(key, ^(LegacyReachability *context) {
if ([context isKindOfClass:[Reachability class]] && context.reachabilityChanged != nil) if ([context isKindOfClass:[LegacyReachability class]] && context.reachabilityChanged != nil)
context.reachabilityChanged(context.currentReachabilityStatus); context.reachabilityChanged(context.currentReachabilityStatus);
}); });
} }
#pragma mark - Reachability implementation #pragma mark - LegacyReachability implementation
@implementation Reachability @implementation LegacyReachability
{ {
int32_t _key; int32_t _key;
SCNetworkReachabilityRef _reachabilityRef; SCNetworkReachabilityRef _reachabilityRef;
@ -175,7 +175,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
+ (instancetype)reachabilityWithHostName:(NSString *)hostName + (instancetype)reachabilityWithHostName:(NSString *)hostName
{ {
Reachability* returnValue = NULL; LegacyReachability* returnValue = NULL;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
if (reachability != NULL) if (reachability != NULL)
{ {
@ -199,7 +199,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
{ {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress);
Reachability* returnValue = NULL; LegacyReachability* returnValue = NULL;
if (reachability != NULL) if (reachability != NULL)
{ {

View File

@ -0,0 +1,180 @@
import Foundation
import SwiftSignalKit
import LegacyReachability
import Network
private final class WrappedLegacyReachability: NSObject {
@objc private static func threadImpl() {
while true {
RunLoop.current.run(until: .distantFuture)
}
}
private static let thread: Thread = {
let thread = Thread(target: Reachability.self, selector: #selector(WrappedLegacyReachability.threadImpl), object: nil)
thread.start()
return thread
}()
@objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) {
f()
}
private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) {
WrappedLegacyReachability.perform(#selector(WrappedLegacyReachability.dispatchOnThreadImpl(_:)), on: WrappedLegacyReachability.thread, with: f, waitUntilDone: false)
}
private let reachability: LegacyReachability
let value: ValuePromise<Reachability.NetworkType>
override init() {
assert(Thread.current === WrappedLegacyReachability.thread)
self.reachability = LegacyReachability.forInternetConnection()
let type: Reachability.NetworkType
switch self.reachability.currentReachabilityStatus() {
case NotReachable:
type = .none
case ReachableViaWiFi:
type = .wifi
case ReachableViaWWAN:
type = .cellular
default:
type = .none
}
self.value = ValuePromise<Reachability.NetworkType>(type)
super.init()
self.reachability.reachabilityChanged = { [weak self] status in
WrappedLegacyReachability.dispatchOnThread {
guard let strongSelf = self else {
return
}
let internalNetworkType: Reachability.NetworkType
switch status {
case NotReachable:
internalNetworkType = .none
case ReachableViaWiFi:
internalNetworkType = .wifi
case ReachableViaWWAN:
internalNetworkType = .cellular
default:
internalNetworkType = .none
}
strongSelf.value.set(internalNetworkType)
}
}
self.reachability.startNotifier()
}
private static var valueRef: Unmanaged<WrappedLegacyReachability>?
static func withInstance(_ f: @escaping (WrappedLegacyReachability) -> Void) {
WrappedLegacyReachability.dispatchOnThread {
if self.valueRef == nil {
self.valueRef = Unmanaged.passRetained(WrappedLegacyReachability())
}
if let valueRef = self.valueRef {
let value = valueRef.takeUnretainedValue()
f(value)
}
}
}
}
@available(iOSApplicationExtension 12.0, iOS 12.0, *)
private final class PathMonitor {
private let queue: Queue
private let monitor: NWPathMonitor
let networkType = Promise<Reachability.NetworkType>()
init(queue: Queue) {
self.queue = queue
self.monitor = NWPathMonitor()
self.monitor.pathUpdateHandler = { [weak self] path in
queue.async {
guard let strongSelf = self else {
return
}
let networkType: Reachability.NetworkType
if path.status == .satisfied {
if path.usesInterfaceType(.cellular) {
networkType = .cellular
} else {
networkType = .wifi
}
} else {
networkType = .none
}
strongSelf.networkType.set(.single(networkType))
}
}
self.monitor.start(queue: self.queue.queue)
let networkType: Reachability.NetworkType
let path = self.monitor.currentPath
if path.status == .satisfied {
if path.usesInterfaceType(.cellular) {
networkType = .cellular
} else {
networkType = .wifi
}
} else {
networkType = .none
}
self.networkType.set(.single(networkType))
}
}
@available(iOSApplicationExtension 12.0, iOS 12.0, *)
private final class SharedPathMonitor {
static let queue = Queue()
static let impl = QueueLocalObject<PathMonitor>(queue: queue, generate: {
return PathMonitor(queue: queue)
})
}
public enum Reachability {
public enum NetworkType: Equatable {
case none
case wifi
case cellular
}
public static var networkType: Signal<NetworkType, NoError> {
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
return Signal { subscriber in
let disposable = MetaDisposable()
SharedPathMonitor.impl.with { impl in
disposable.set(impl.networkType.get().start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
|> distinctUntilChanged
} else {
return Signal { subscriber in
let disposable = MetaDisposable()
WrappedLegacyReachability.withInstance({ impl in
disposable.set(impl.value.get().start(next: { next in
subscriber.putNext(next)
}))
})
return disposable
}
|> distinctUntilChanged
}
}
}

View File

@ -34,12 +34,6 @@ extension CellularNetworkType {
#endif #endif
enum InternalNetworkType: Equatable {
case none
case wifi
case cellular
}
public enum NetworkType: Equatable { public enum NetworkType: Equatable {
case none case none
case wifi case wifi
@ -50,7 +44,7 @@ public enum NetworkType: Equatable {
extension NetworkType { extension NetworkType {
#if os(iOS) #if os(iOS)
init(internalType: InternalNetworkType, cellularType: CellularNetworkType) { init(internalType: Reachability.NetworkType, cellularType: CellularNetworkType) {
switch internalType { switch internalType {
case .none: case .none:
self = .none self = .none
@ -61,7 +55,7 @@ extension NetworkType {
} }
} }
#else #else
init(internalType: InternalNetworkType) { init(internalType: Reachability.NetworkType) {
switch internalType { switch internalType {
case .none: case .none:
self = .none self = .none
@ -72,91 +66,11 @@ extension NetworkType {
#endif #endif
} }
private final class WrappedReachability: NSObject {
@objc private static func threadImpl() {
while true {
RunLoop.current.run(until: .distantFuture)
}
}
static let thread: Thread = {
let thread = Thread(target: WrappedReachability.self, selector: #selector(WrappedReachability.threadImpl), object: nil)
thread.start()
return thread
}()
@objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) {
f()
}
private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) {
WrappedReachability.perform(#selector(WrappedReachability.dispatchOnThreadImpl(_:)), on: WrappedReachability.thread, with: f, waitUntilDone: false)
}
private let reachability: Reachability
let value: ValuePromise<InternalNetworkType>
override init() {
assert(Thread.current === WrappedReachability.thread)
self.reachability = Reachability.forInternetConnection()
let type: InternalNetworkType
switch self.reachability.currentReachabilityStatus() {
case NotReachable:
type = .none
case ReachableViaWiFi:
type = .wifi
case ReachableViaWWAN:
type = .cellular
default:
type = .none
}
self.value = ValuePromise<InternalNetworkType>(type)
super.init()
self.reachability.reachabilityChanged = { [weak self] status in
WrappedReachability.dispatchOnThread {
guard let strongSelf = self else {
return
}
let internalNetworkType: InternalNetworkType
switch status {
case NotReachable:
internalNetworkType = .none
case ReachableViaWiFi:
internalNetworkType = .wifi
case ReachableViaWWAN:
internalNetworkType = .cellular
default:
internalNetworkType = .none
}
strongSelf.value.set(internalNetworkType)
}
}
self.reachability.startNotifier()
}
static var valueRef: Unmanaged<WrappedReachability>?
static func withInstance(_ f: @escaping (WrappedReachability) -> Void) {
WrappedReachability.dispatchOnThread {
if self.valueRef == nil {
self.valueRef = Unmanaged.passRetained(WrappedReachability())
}
if let valueRef = self.valueRef {
let value = valueRef.takeUnretainedValue()
f(value)
}
}
}
}
private final class NetworkTypeManagerImpl { private final class NetworkTypeManagerImpl {
let queue: Queue let queue: Queue
let updated: (NetworkType) -> Void let updated: (NetworkType) -> Void
var networkTypeDisposable: Disposable? var networkTypeDisposable: Disposable?
var currentNetworkType: InternalNetworkType? var currentNetworkType: Reachability.NetworkType?
var networkType: NetworkType? var networkType: NetworkType?
#if os(iOS) #if os(iOS)
var currentCellularType: CellularNetworkType var currentCellularType: CellularNetworkType
@ -197,9 +111,8 @@ private final class NetworkTypeManagerImpl {
let networkTypeDisposable = MetaDisposable() let networkTypeDisposable = MetaDisposable()
self.networkTypeDisposable = networkTypeDisposable self.networkTypeDisposable = networkTypeDisposable
WrappedReachability.withInstance({ [weak self] impl in networkTypeDisposable.set((Reachability.networkType
networkTypeDisposable.set((impl.value.get() |> deliverOn(queue)).start(next: { [weak self] networkStatus in
|> deliverOn(queue)).start(next: { networkStatus in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -218,7 +131,6 @@ private final class NetworkTypeManagerImpl {
} }
} }
})) }))
})
} }
func stop() { func stop() {