Created
February 14, 2016 17:41
-
-
Save borland/be26bee83d9d757da01a to your computer and use it in GitHub Desktop.
Swift observable reachability framework which publishes values via an Observable instead of a wrapper class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Reachability.swift | |
// iOSSwiftScratchApp | |
// | |
// Created by Orion Edwards on 15/02/16. | |
// Copyright © 2016 Orion Edwards. All rights reserved. | |
// | |
import Foundation | |
import SystemConfiguration | |
struct ReachabilityResult { | |
let rawValue:SCNetworkReachabilityFlags | |
var networkStatus:NetworkStatus { | |
if !rawValue.contains(.Reachable) { | |
return .NotReachable | |
} | |
var result = NetworkStatus.NotReachable | |
if !rawValue.contains(.ConnectionRequired) { | |
// If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... | |
result = .ReachableViaWiFi | |
} | |
if rawValue.contains(.ConnectionOnDemand) || rawValue.contains(.ConnectionOnTraffic) { | |
// ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... | |
if !rawValue.contains(.InterventionRequired) { | |
// ... and no [user] intervention is needed... | |
result = .ReachableViaWiFi | |
} | |
} | |
if rawValue == .IsWWAN { | |
// ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. | |
result = .ReachableViaWWAN | |
} | |
return result | |
} | |
var wifiStatus:NetworkStatus { | |
return (rawValue.contains(.Reachable) && rawValue.contains(.IsDirect)) ? | |
.ReachableViaWiFi : | |
.NotReachable | |
} | |
} | |
enum NetworkStatus : Int { | |
case NotReachable = 0, ReachableViaWiFi, ReachableViaWWAN | |
} | |
// MARK: - Supporting functions | |
extension SCNetworkReachabilityFlags : CustomStringConvertible { | |
public var description: String { | |
var str = "" | |
str += (self.contains(.IsWWAN) ? "W" : "-") | |
str += (self.contains(.Reachable) ? "R" : "-") | |
str += (self.contains(.TransientConnection) ? "t" : "-") | |
str += (self.contains(.ConnectionRequired) ? "c" : "-") | |
str += (self.contains(.ConnectionOnTraffic) ? "C" : "-") | |
str += (self.contains(.InterventionRequired) ? "i" : "-") | |
str += (self.contains(.ConnectionOnDemand) ? "D" : "-") | |
str += (self.contains(.IsLocalAddress) ? "l" : "-") | |
str += (self.contains(.IsDirect) ? "d" : "-") | |
return str | |
} | |
} | |
private func reachabilityCallback(_:SCNetworkReachabilityRef, flags:SCNetworkReachabilityFlags, info:UnsafeMutablePointer<Void>) { | |
precondition(info != nil, "info was NULL in ReachabilityCallback") | |
let notifier = Unmanaged<ReachabilityNotifier>.fromOpaque(COpaquePointer(info)).takeUnretainedValue() | |
notifier.onNext(flags) | |
} | |
private class ReachabilityNotifier : Subject<SCNetworkReachabilityFlags> { | |
let target:SCNetworkReachabilityRef | |
init(target:SCNetworkReachabilityRef) { | |
self.target = target | |
} | |
func subscribe<O: ObserverType where O.ValueType == ValueType>(observer: O) -> DisposableType { | |
let info:UnsafeMutablePointer<Void> = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()) | |
var context = SCNetworkReachabilityContext(version: 0, info: info, retain: nil, release: nil, copyDescription: nil) | |
if SCNetworkReachabilitySetCallback(target, reachabilityCallback, &context) { | |
if SCNetworkReachabilityScheduleWithRunLoop(target, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode) { | |
// active = true | |
} // else failed. cleanup | |
} // else failed. cleanup | |
return Disposable.create { | |
let this = self // explicity capture self in the disposable as it's unretained in the callback | |
SCNetworkReachabilityUnscheduleFromRunLoop(this.target, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode) | |
} | |
} | |
} | |
// MARK: - Reachability implementation | |
func reachabilityForHostName(hostName:String) -> Observable<ReachabilityResult> { | |
let ptr = UnsafePointer<sockaddr>((hostName as NSString).UTF8String) | |
if let reach = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, ptr) { | |
return ReachabilityNotifier(target: reach) | |
.map{ ReachabilityResult(rawValue: $0) } | |
} | |
return Observable.error(NSError(domain: "x", code: Int(errno), userInfo: nil)) | |
} | |
func reachabilityWithAddress(hostAddress:sockaddr_in) -> Observable<ReachabilityResult> { | |
var ha = hostAddress | |
return withUnsafeMutablePointer(&ha){ umPtr in | |
let ptr = UnsafePointer<sockaddr>(umPtr) | |
if let reach = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, ptr) { | |
return ReachabilityNotifier(target: reach).map{ ReachabilityResult(rawValue: $0) } | |
} | |
return Observable.error(NSError(domain: "x", code: Int(errno), userInfo: nil)) | |
} | |
} | |
func reachabilityForInternetConnection() -> Observable<ReachabilityResult> { | |
var zeroAddress = sockaddr_in() | |
bzero(&zeroAddress, sizeof(sockaddr_in)) | |
zeroAddress.sin_len = UInt8(sizeof(sockaddr_in)) | |
zeroAddress.sin_family = sa_family_t(AF_INET) | |
return reachabilityWithAddress(zeroAddress) | |
} | |
func reachabilityForLocalWiFi() -> Observable<ReachabilityResult> { | |
var localWifiAddress = sockaddr_in() | |
bzero(&localWifiAddress, sizeof(sockaddr_in)) | |
localWifiAddress.sin_len = UInt8(sizeof(sockaddr_in)) | |
localWifiAddress.sin_family = sa_family_t(AF_INET) | |
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0 | |
let address: UInt32 = 0xA9FE0000 | |
localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian) | |
return reachabilityWithAddress(localWifiAddress) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment