Skip to content

Instantly share code, notes, and snippets.

@borland
Created February 14, 2016 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save borland/be26bee83d9d757da01a to your computer and use it in GitHub Desktop.
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
//
// 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