Browse Source

Merge pull request #1053 from Alamofire/feature/network_reachability_manager

Feature - Network Reachability Manager
Christian Noon 10 years ago
parent
commit
d409ef6e89

+ 0 - 2
Alamofire.podspec

@@ -14,6 +14,4 @@ Pod::Spec.new do |s|
   s.watchos.deployment_target = '2.0'
 
   s.source_files = 'Source/*.swift'
-
-  s.requires_arc = true
 end

+ 28 - 10
Alamofire.xcodeproj/project.pbxproj

@@ -30,6 +30,12 @@
 		4C33A1441B52089C00873DFF /* ServerTrustPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */; };
 		4C341BBA1B1A865A00C1B34D /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C341BB91B1A865A00C1B34D /* CacheTests.swift */; };
 		4C341BBB1B1A865A00C1B34D /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C341BB91B1A865A00C1B34D /* CacheTests.swift */; };
+		4C3D00541C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */; };
+		4C3D00551C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */; };
+		4C3D00561C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */; };
+		4C3D00581C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */; };
+		4C3D00591C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */; };
+		4C3D005A1C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */; };
 		4C4CBE7B1BAF700C0024D659 /* String+AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4CBE7A1BAF700C0024D659 /* String+AlamofireTests.swift */; };
 		4C4CBE7C1BAF700C0024D659 /* String+AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4CBE7A1BAF700C0024D659 /* String+AlamofireTests.swift */; };
 		4C574E6A1C67D207000B3128 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C574E691C67D207000B3128 /* Timeline.swift */; };
@@ -113,10 +119,10 @@
 		4C83F41D1B749E0E00203445 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C83F41A1B749E0E00203445 /* Stream.swift */; };
 		4CA028C51B7466C500C84163 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA028C41B7466C500C84163 /* ResultTests.swift */; };
 		4CA028C61B7466C500C84163 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA028C41B7466C500C84163 /* ResultTests.swift */; };
-		4CB928291C66BFBC00CE5F08 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */; };
-		4CB9282A1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */; };
-		4CB9282B1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */; };
-		4CB9282C1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */; };
+		4CB928291C66BFBC00CE5F08 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* Notifications.swift */; };
+		4CB9282A1C66BFBC00CE5F08 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* Notifications.swift */; };
+		4CB9282B1C66BFBC00CE5F08 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* Notifications.swift */; };
+		4CB9282C1C66BFBC00CE5F08 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB928281C66BFBC00CE5F08 /* Notifications.swift */; };
 		4CCFA79A1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
 		4CCFA79B1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
 		4CDE2C371AF8932A00BABAE5 /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDE2C361AF8932A00BABAE5 /* Manager.swift */; };
@@ -136,6 +142,7 @@
 		4CEC605A1B745C9100E684F4 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1DC8531B68908E00476DE3 /* Error.swift */; };
 		4CEC605B1B745C9100E684F4 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0E5BF71B673D3400816CCC /* Result.swift */; };
 		4CEC605C1B745C9B00E684F4 /* Alamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = F8111E3819A95C8B0040E7D1 /* Alamofire.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		4CEE82AD1C6813CF00E9C9F0 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */; };
 		4CF626F91BA7CB3E0011A099 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CF626EF1BA7CB3E0011A099 /* Alamofire.framework */; };
 		4CF627061BA7CBE30011A099 /* Alamofire.h in Headers */ = {isa = PBXBuildFile; fileRef = F8111E3819A95C8B0040E7D1 /* Alamofire.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		4CF627071BA7CBF60011A099 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897FF4019AA800700AB5182 /* Alamofire.swift */; };
@@ -243,6 +250,8 @@
 		4C33A1241B5207DB00873DFF /* unicorn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = unicorn.png; sourceTree = "<group>"; };
 		4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustPolicyTests.swift; sourceTree = "<group>"; };
 		4C341BB91B1A865A00C1B34D /* CacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheTests.swift; sourceTree = "<group>"; };
+		4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManager.swift; sourceTree = "<group>"; };
+		4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManagerTests.swift; sourceTree = "<group>"; };
 		4C4CBE7A1BAF700C0024D659 /* String+AlamofireTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+AlamofireTests.swift"; sourceTree = "<group>"; };
 		4C574E691C67D207000B3128 /* Timeline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
 		4C7C8D211B9D0D9000948136 /* NSURLSessionConfiguration+AlamofireTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLSessionConfiguration+AlamofireTests.swift"; sourceTree = "<group>"; };
@@ -264,7 +273,7 @@
 		4C812C601B535F6D0017E0BF /* testssl-expire.disig.sk.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = "testssl-expire.disig.sk.cer"; path = "disig.sk/testssl-expire.disig.sk.cer"; sourceTree = "<group>"; };
 		4C83F41A1B749E0E00203445 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
 		4CA028C41B7466C500C84163 /* ResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = "<group>"; };
-		4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; };
+		4CB928281C66BFBC00CE5F08 /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
 		4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLProtocolTests.swift; sourceTree = "<group>"; };
 		4CDE2C361AF8932A00BABAE5 /* Manager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Manager.swift; sourceTree = "<group>"; };
 		4CDE2C391AF899EC00BABAE5 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
@@ -378,6 +387,7 @@
 				4C341BB91B1A865A00C1B34D /* CacheTests.swift */,
 				F8111E5B19A9674D0040E7D1 /* DownloadTests.swift */,
 				4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */,
+				4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */,
 				4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */,
 				4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */,
 				F86AEFE51AE6A282007D9C76 /* TLSEvaluationTests.swift */,
@@ -501,7 +511,7 @@
 			children = (
 				4C1DC8531B68908E00476DE3 /* Error.swift */,
 				4CDE2C361AF8932A00BABAE5 /* Manager.swift */,
-				4CB928281C66BFBC00CE5F08 /* NotificationNames.swift */,
+				4CB928281C66BFBC00CE5F08 /* Notifications.swift */,
 				4CE2724E1AF88FB500F1D59A /* ParameterEncoding.swift */,
 				4CDE2C391AF899EC00BABAE5 /* Request.swift */,
 				4C0B62501BB1001C009302D3 /* Response.swift */,
@@ -515,6 +525,7 @@
 			children = (
 				4CDE2C3C1AF89D4900BABAE5 /* Download.swift */,
 				4C23EB421B327C5B0090E0BC /* MultipartFormData.swift */,
+				4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */,
 				4CDE2C451AF89FF300BABAE5 /* ResponseSerialization.swift */,
 				4C811F8C1B51856D00E0F59A /* ServerTrustPolicy.swift */,
 				4C83F41A1B749E0E00203445 /* Stream.swift */,
@@ -959,10 +970,11 @@
 				4CF627131BA7CBF60011A099 /* Validation.swift in Sources */,
 				4CF6270E1BA7CBF60011A099 /* MultipartFormData.swift in Sources */,
 				4C80F9F81BB730EF001B46D2 /* Response.swift in Sources */,
-				4CB9282B1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */,
+				4CB9282B1C66BFBC00CE5F08 /* Notifications.swift in Sources */,
 				4CF627091BA7CBF60011A099 /* Manager.swift in Sources */,
 				4CF6270F1BA7CBF60011A099 /* ResponseSerialization.swift in Sources */,
 				4CF6270B1BA7CBF60011A099 /* Request.swift in Sources */,
+				4C3D00561C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4CF6270A1BA7CBF60011A099 /* ParameterEncoding.swift in Sources */,
 				4CF627101BA7CBF60011A099 /* ServerTrustPolicy.swift in Sources */,
 				4CF6270D1BA7CBF60011A099 /* Download.swift in Sources */,
@@ -990,6 +1002,7 @@
 				4CF627161BA7CC240011A099 /* ManagerTests.swift in Sources */,
 				4CF6271A1BA7CC240011A099 /* ResultTests.swift in Sources */,
 				4CF6271B1BA7CC240011A099 /* NSURLSessionConfiguration+AlamofireTests.swift in Sources */,
+				4C3D005A1C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */,
 				4CF6271F1BA7CC240011A099 /* ResponseSerializationTests.swift in Sources */,
 				4CF6271D1BA7CC240011A099 /* DownloadTests.swift in Sources */,
 			);
@@ -1007,10 +1020,11 @@
 				4C1DC8551B68908E00476DE3 /* Error.swift in Sources */,
 				4CDE2C381AF8932A00BABAE5 /* Manager.swift in Sources */,
 				4C0B62521BB1001C009302D3 /* Response.swift in Sources */,
-				4CB9282A1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */,
+				4CB9282A1C66BFBC00CE5F08 /* Notifications.swift in Sources */,
 				4DD67C251A5C590000ED2280 /* Alamofire.swift in Sources */,
 				4C23EB441B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
 				4C811F8E1B51856D00E0F59A /* ServerTrustPolicy.swift in Sources */,
+				4C3D00551C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4C83F41C1B749E0E00203445 /* Stream.swift in Sources */,
 				4CDE2C3E1AF89D4900BABAE5 /* Download.swift in Sources */,
 				4CDE2C441AF89F0900BABAE5 /* Validation.swift in Sources */,
@@ -1023,6 +1037,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				4C574E6D1C67D207000B3128 /* Timeline.swift in Sources */,
+				4CEE82AD1C6813CF00E9C9F0 /* NetworkReachabilityManager.swift in Sources */,
 				E4202FCF1B667AA100C997FB /* Upload.swift in Sources */,
 				E4202FD01B667AA100C997FB /* ParameterEncoding.swift in Sources */,
 				E4202FD11B667AA100C997FB /* Request.swift in Sources */,
@@ -1030,7 +1045,7 @@
 				E4202FD21B667AA100C997FB /* ResponseSerialization.swift in Sources */,
 				E4202FD31B667AA100C997FB /* Manager.swift in Sources */,
 				4C0B62531BB1001C009302D3 /* Response.swift in Sources */,
-				4CB9282C1C66BFBC00CE5F08 /* NotificationNames.swift in Sources */,
+				4CB9282C1C66BFBC00CE5F08 /* Notifications.swift in Sources */,
 				4CEC605B1B745C9100E684F4 /* Result.swift in Sources */,
 				E4202FD41B667AA100C997FB /* Alamofire.swift in Sources */,
 				E4202FD51B667AA100C997FB /* MultipartFormData.swift in Sources */,
@@ -1056,10 +1071,11 @@
 				F897FF4119AA800700AB5182 /* Alamofire.swift in Sources */,
 				4C23EB431B327C5B0090E0BC /* MultipartFormData.swift in Sources */,
 				4C811F8D1B51856D00E0F59A /* ServerTrustPolicy.swift in Sources */,
+				4C3D00541C66A63000D1F709 /* NetworkReachabilityManager.swift in Sources */,
 				4C83F41B1B749E0E00203445 /* Stream.swift in Sources */,
 				4CDE2C3D1AF89D4900BABAE5 /* Download.swift in Sources */,
 				4CDE2C431AF89F0900BABAE5 /* Validation.swift in Sources */,
-				4CB928291C66BFBC00CE5F08 /* NotificationNames.swift in Sources */,
+				4CB928291C66BFBC00CE5F08 /* Notifications.swift in Sources */,
 				4C0E5BF81B673D3400816CCC /* Result.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -1084,6 +1100,7 @@
 				F8AE910219D28DCC0078C7B2 /* ValidationTests.swift in Sources */,
 				F8111E6119A9674D0040E7D1 /* ParameterEncodingTests.swift in Sources */,
 				F8111E6419A9674D0040E7D1 /* UploadTests.swift in Sources */,
+				4C3D00581C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */,
 				F8111E6019A9674D0040E7D1 /* DownloadTests.swift in Sources */,
 				4C7C8D221B9D0D9000948136 /* NSURLSessionConfiguration+AlamofireTests.swift in Sources */,
 			);
@@ -1109,6 +1126,7 @@
 				F829C6C31A7A950600A2CD59 /* DownloadTests.swift in Sources */,
 				F829C6C41A7A950600A2CD59 /* AuthenticationTests.swift in Sources */,
 				F829C6C51A7A950600A2CD59 /* ValidationTests.swift in Sources */,
+				4C3D00591C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift in Sources */,
 				F86AEFE81AE6A315007D9C76 /* TLSEvaluationTests.swift in Sources */,
 				4C7C8D231B9D0D9000948136 /* NSURLSessionConfiguration+AlamofireTests.swift in Sources */,
 			);

+ 27 - 0
README.md

@@ -1133,6 +1133,33 @@ Whether you need to set the `NSExceptionRequiresForwardSecrecy` to `NO` depends
 
 > It is recommended to always use valid certificates in production environments.
 
+### Network Reachability
+
+The `NetworkReachabilityManager` listens for reachability changes of hosts and addresses for both WWAN and WiFi network interfaces.
+
+```swift
+let manager = NetworkReachabilityManager(host: "www.apple.com")
+
+manager?.listener = { status in
+    print("Network Status Changed: \(status)")
+}
+
+manager?.startListening()
+```
+
+> Make sure to remember to retain the `manager` in the above example, or no status changes will be reported.
+
+There are some important things to remember when using network reachability to determine what to do next.
+
+* **Do NOT** use Reachability to determine if a network request should be sent.
+  * You should **ALWAYS** send it.
+* When Reachability is restored, use the event to retry failed network requests.
+  * Even though the network requests may still fail, this is a good moment to retry them.
+* The network reachability status can be useful for determining why a network request may have failed.
+  * If a network request fails, it is more useful to tell the user that the network request failed due to being offline rather than a more technical errror, such as "request timed out."
+
+> It is recommended to check out [WWDC 2012 Session 706, "Networking Best Practices"](https://developer.apple.com/videos/play/wwdc2012-706/) for more info.
+
 ---
 
 ## Component Libraries

+ 1 - 1
Source/Manager.swift

@@ -452,7 +452,7 @@ public class Manager {
                 delegate.URLSession(session, task: task, didCompleteWithError: error)
             }
 
-            NSNotificationCenter.defaultCenter().postNotificationName(NotificationNames.TaskDidComplete, object: task)
+            NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidComplete, object: task)
 
             self[task] = nil
         }

+ 238 - 0
Source/NetworkReachabilityManager.swift

@@ -0,0 +1,238 @@
+// NetworkReachabilityManager.swift
+//
+// Copyright (c) 2014–2016 Alamofire Software Foundation (http://alamofire.org/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#if !os(watchOS)
+
+import Foundation
+import SystemConfiguration
+
+/**
+    The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and
+    WiFi network interfaces.
+
+    Reachability can be used to determine background information about why a network operation failed, or to retry
+    network requests when a connection is established. It should not be used to prevent a user from initiating a network
+    request, as it's possible that an initial request may be required to establish reachability.
+*/
+public class NetworkReachabilityManager {
+    /**
+        Defines the various states of network reachability.
+
+        - Unknown:         It is unknown whether the network is reachable.
+        - NotReachable:    The network is not reachable.
+        - ReachableOnWWAN: The network is reachable over the WWAN connection.
+        - ReachableOnWiFi: The network is reachable over the WiFi connection.
+    */
+    public enum NetworkReachabilityStatus {
+        case Unknown
+        case NotReachable
+        case Reachable(ConnectionType)
+    }
+
+    /**
+        Defines the various connection types detected by reachability flags.
+
+        - EthernetOrWiFi: The connection type is either over Ethernet or WiFi.
+        - WWAN:           The connection type is a WWAN connection.
+    */
+    public enum ConnectionType {
+        case EthernetOrWiFi
+        case WWAN
+    }
+
+    /// A closure executed when the network reachability status changes. The closure takes a single argument: the 
+    /// network reachability status.
+    public typealias Listener = NetworkReachabilityStatus -> Void
+
+    // MARK: - Properties
+
+    /// Whether the network is currently reachable.
+    public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
+
+    /// Whether the network is currently reachable over the WWAN interface.
+    public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .Reachable(.WWAN) }
+
+    /// Whether the network is currently reachable over Ethernet or WiFi interface.
+    public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .Reachable(.EthernetOrWiFi) }
+
+    /// The current network reachability status.
+    public var networkReachabilityStatus: NetworkReachabilityStatus {
+        guard let flags = self.flags else { return .Unknown }
+        return networkReachabilityStatusForFlags(flags)
+    }
+
+    /// The dispatch queue to execute the `listener` closure on.
+    public var listenerQueue: dispatch_queue_t = dispatch_get_main_queue()
+
+    /// A closure executed when the network reachability status changes.
+    public var listener: Listener?
+
+    private var flags: SCNetworkReachabilityFlags? {
+        var flags = SCNetworkReachabilityFlags()
+
+        if SCNetworkReachabilityGetFlags(reachability, &flags) {
+            return flags
+        }
+
+        return nil
+    }
+
+    private let reachability: SCNetworkReachability
+    private var previousFlags: SCNetworkReachabilityFlags
+
+    // MARK: - Initialization
+
+    /**
+        Creates a `NetworkReachabilityManager` instance with the specified host.
+
+        - parameter host: The host used to evaluate network reachability.
+
+        - returns: The new `NetworkReachabilityManager` instance.
+    */
+    public convenience init?(host: String) {
+        guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
+        self.init(reachability: reachability)
+    }
+
+    /**
+        Creates a `NetworkReachabilityManager` instance with the default socket address (`sockaddr_in6`).
+
+        - returns: The new `NetworkReachabilityManager` instance.
+     */
+    public convenience init?() {
+        var address = sockaddr_in6()
+        address.sin6_len = UInt8(sizeofValue(address))
+        address.sin6_family = sa_family_t(AF_INET6)
+
+        guard let reachability = withUnsafePointer(&address, {
+            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
+        }) else { return nil }
+
+        self.init(reachability: reachability)
+    }
+
+    private init(reachability: SCNetworkReachability) {
+        self.reachability = reachability
+        self.previousFlags = SCNetworkReachabilityFlags()
+    }
+
+    deinit {
+        stopListening()
+    }
+
+    // MARK: - Listening
+
+    /**
+        Starts listening for changes in network reachability status.
+
+        - returns: `true` if listening was started successfully, `false` otherwise.
+    */
+    public func startListening() -> Bool {
+        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
+        context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())
+
+        let callbackEnabled = SCNetworkReachabilitySetCallback(
+            reachability,
+            { (_, flags, info) in
+                let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
+                reachability.notifyListener(flags)
+            },
+            &context
+        )
+
+        let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
+
+        dispatch_async(listenerQueue) {
+            self.notifyListener(self.flags ?? SCNetworkReachabilityFlags())
+        }
+
+        return callbackEnabled && queueEnabled
+    }
+
+    /**
+        Stops listening for changes in network reachability status.
+    */
+    public func stopListening() {
+        SCNetworkReachabilitySetCallback(reachability, nil, nil)
+        SCNetworkReachabilitySetDispatchQueue(reachability, nil)
+    }
+
+    // MARK: - Internal - Listener Notification
+
+    func notifyListener(flags: SCNetworkReachabilityFlags) {
+        guard previousFlags != flags else { return }
+        previousFlags = flags
+
+        listener?(networkReachabilityStatusForFlags(flags))
+    }
+
+    // MARK: - Internal - Network Reachability Status
+
+    func networkReachabilityStatusForFlags(flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
+        guard flags.contains(.Reachable) else { return .NotReachable }
+
+        var networkStatus: NetworkReachabilityStatus = .NotReachable
+
+        if !flags.contains(.ConnectionRequired) { networkStatus = .Reachable(.EthernetOrWiFi) }
+
+        if flags.contains(.ConnectionOnDemand) || flags.contains(.ConnectionOnTraffic) {
+            if !flags.contains(.InterventionRequired) { networkStatus = .Reachable(.EthernetOrWiFi) }
+        }
+
+        #if os(iOS)
+            if flags.contains(.IsWWAN) { networkStatus = .Reachable(.WWAN) }
+        #endif
+
+        return networkStatus
+    }
+}
+
+// MARK: -
+
+extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}
+
+/**
+    Returns whether the two network reachability status values are equal.
+
+    - parameter lhs: The left-hand side value to compare.
+    - parameter rhs: The right-hand side value to compare.
+
+    - returns: `true` if the two values are equal, `false` otherwise.
+*/
+public func ==(
+    lhs: NetworkReachabilityManager.NetworkReachabilityStatus,
+    rhs: NetworkReachabilityManager.NetworkReachabilityStatus)
+    -> Bool
+{
+    switch (lhs, rhs) {
+    case (.Unknown, .Unknown):
+        return true
+    case (.NotReachable, .NotReachable):
+        return true
+    case let (.Reachable(lhsConnectionType), .Reachable(rhsConnectionType)):
+        return lhsConnectionType == rhsConnectionType
+    default:
+        return false
+    }
+}
+
+#endif

+ 0 - 30
Source/NotificationNames.swift

@@ -1,30 +0,0 @@
-// NotificationNames.swift
-//
-// Copyright (c) 2014–2016 Alamofire Software Foundation (http://alamofire.org/)
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-import Foundation
-
-public struct NotificationNames {
-    public static let TaskDidResume = "com.alamofire.task.did.resume"
-    public static let TaskDidSuspend = "com.alamofire.task.did.suspend"
-    public static let TaskDidCancel = "com.alamofire.task.did.cancel"
-    public static let TaskDidComplete = "com.alamofire.task.did.complete"
-}

+ 45 - 0
Source/Notifications.swift

@@ -0,0 +1,45 @@
+// Notifications.swift
+//
+// Copyright (c) 2014–2016 Alamofire Software Foundation (http://alamofire.org/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+
+/// Contains all the `NSNotification` names posted by Alamofire with descriptions of each notification's payload.
+public struct Notifications {
+    /// Used as a namespace for all `NSURLSessionTask` related notifications.
+    public struct Task {
+        /// Notification posted when an `NSURLSessionTask` is resumed. The notification `object` contains the resumed
+        /// `NSURLSessionTask`.
+        public static let DidResume = "com.alamofire.notifications.task.didResume"
+
+        /// Notification posted when an `NSURLSessionTask` is suspended. The notification `object` contains the 
+        /// suspended `NSURLSessionTask`.
+        public static let DidSuspend = "com.alamofire.notifications.task.didSuspend"
+
+        /// Notification posted when an `NSURLSessionTask` is cancelled. The notification `object` contains the
+        /// cancelled `NSURLSessionTask`.
+        public static let DidCancel = "com.alamofire.notifications.task.didCancel"
+
+        /// Notification posted when an `NSURLSessionTask` is completed. The notification `object` contains the
+        /// completed `NSURLSessionTask`.
+        public static let DidComplete = "com.alamofire.notifications.task.didComplete"
+    }
+}

+ 3 - 3
Source/Request.swift

@@ -160,7 +160,7 @@ public class Request {
         if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
 
         task.resume()
-        NSNotificationCenter.defaultCenter().postNotificationName(NotificationNames.TaskDidResume, object: task)
+        NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidResume, object: task)
     }
 
     /**
@@ -168,7 +168,7 @@ public class Request {
     */
     public func suspend() {
         task.suspend()
-        NSNotificationCenter.defaultCenter().postNotificationName(NotificationNames.TaskDidSuspend, object: task)
+        NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidSuspend, object: task)
     }
 
     /**
@@ -186,7 +186,7 @@ public class Request {
             task.cancel()
         }
 
-        NSNotificationCenter.defaultCenter().postNotificationName(NotificationNames.TaskDidCancel, object: task)
+        NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidCancel, object: task)
     }
 
     // MARK: - TaskDelegate

+ 187 - 0
Tests/NetworkReachabilityManagerTests.swift

@@ -0,0 +1,187 @@
+// NetworkReachabilityManagerTests.swift
+//
+// Copyright (c) 2014–2016 Alamofire Software Foundation (http://alamofire.org/)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+@testable import Alamofire
+import Foundation
+import SystemConfiguration
+import XCTest
+
+class NetworkReachabilityManagerTestCase: BaseTestCase {
+
+    // MARK: - Tests - Initialization
+
+    func testThatManagerCanBeInitializedFromHost() {
+        // Given, When
+        let manager = NetworkReachabilityManager(host: "localhost")
+
+        // Then
+        XCTAssertNotNil(manager)
+    }
+
+    func testThatManagerCanBeInitializedFromAddress() {
+        // Given, When
+        let manager = NetworkReachabilityManager()
+
+        // Then
+        XCTAssertNotNil(manager)
+    }
+
+    func testThatHostManagerIsReachableOnWiFi() {
+        // Given, When
+        let manager = NetworkReachabilityManager(host: "localhost")
+
+        // Then
+        XCTAssertEqual(manager?.networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+        XCTAssertEqual(manager?.isReachable, true)
+        XCTAssertEqual(manager?.isReachableOnWWAN, false)
+        XCTAssertEqual(manager?.isReachableOnEthernetOrWiFi, true)
+    }
+
+    func testThatAddressManagerStartsWithUnknownStatus() {
+        // Given, When
+        let manager = NetworkReachabilityManager()
+
+        // Then
+        XCTAssertEqual(manager?.networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+        XCTAssertEqual(manager?.isReachable, true)
+        XCTAssertEqual(manager?.isReachableOnWWAN, false)
+        XCTAssertEqual(manager?.isReachableOnEthernetOrWiFi, true)
+    }
+
+    // MARK: - Tests - Listener
+
+    func testThatHostManagerIsNotifiedWhenStartListeningIsCalled() {
+        // Given
+        let manager = NetworkReachabilityManager(host: "localhost")
+        let expectation = expectationWithDescription("listener closure should be executed")
+
+        var networkReachabilityStatus: NetworkReachabilityManager.NetworkReachabilityStatus?
+
+        manager?.listener = { status in
+            networkReachabilityStatus = status
+            expectation.fulfill()
+        }
+
+        // When
+        manager?.startListening()
+        waitForExpectationsWithTimeout(timeout, handler: nil)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+    }
+
+    func testThatAddressManagerIsNotifiedWhenStartListeningIsCalled() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let expectation = expectationWithDescription("listener closure should be executed")
+
+        var networkReachabilityStatus: NetworkReachabilityManager.NetworkReachabilityStatus?
+
+        manager?.listener = { status in
+            networkReachabilityStatus = status
+            expectation.fulfill()
+        }
+
+        // When
+        manager?.startListening()
+        waitForExpectationsWithTimeout(timeout, handler: nil)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+    }
+
+    // MARK: - Tests - Network Reachability Status
+
+    func testThatManagerReturnsNotReachableStatusWhenReachableFlagIsAbsent() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.ConnectionOnDemand]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .NotReachable)
+    }
+
+    func testThatManagerReturnsNotReachableStatusWhenInterventionIsRequired() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.Reachable, .ConnectionRequired, .ConnectionOnDemand, .InterventionRequired]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .NotReachable)
+    }
+
+    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsNotRequired() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.Reachable]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+    }
+
+    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsOnDemand() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.Reachable, .ConnectionRequired, .ConnectionOnDemand]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+    }
+
+    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsOnTraffic() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.Reachable, .ConnectionRequired, .ConnectionOnTraffic]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.EthernetOrWiFi))
+    }
+
+#if os(iOS)
+    func testThatManagerReturnsReachableOnWWANStatusWhenIsWWAN() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let flags: SCNetworkReachabilityFlags = [.Reachable, .IsWWAN]
+
+        // When
+        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+
+        // Then
+        XCTAssertEqual(networkReachabilityStatus, .Reachable(.WWAN))
+    }
+#endif
+}