@@ -41,3 +41,159 @@ protocol URLSession {
4141extension NSURLSession : URLSession {
4242}
4343
44+
45+ #if DEBUG
46+
47+ import CoreTelephony
48+ import SystemConfiguration
49+
50+ /// Wrapper around an `NSURLSession`, adding logging facilities.
51+ ///
52+ class URLSessionLogger: NSObject , URLSession {
53+ static var epoch : NSDate !
54+
55+ struct RequestStat {
56+ // TODO: Log network type.
57+ let startTime : NSDate
58+ let host : String
59+ var networkType : String ?
60+ var responseTime : NSTimeInterval ?
61+ var cancelled : Bool = false
62+ var dataSize : Int ?
63+ var statusCode : Int ?
64+
65+ init ( startTime: NSDate , host: String ) {
66+ self . startTime = startTime
67+ self . host = host
68+ }
69+
70+ var description: String {
71+ var description = " @ \( Int ( startTime. timeIntervalSinceDate ( URLSessionLogger . epoch) * 1000 ) ) ms; \( host) ; \( networkType != nil ? networkType! : " ? " ) "
72+ if let responseTime = responseTime, dataSize = dataSize, statusCode = statusCode {
73+ description += " ; \( Int ( responseTime * 1000 ) ) ms; \( dataSize) B; \( statusCode) "
74+ }
75+ return description
76+ }
77+ }
78+
79+ /// The wrapped session.
80+ let session: NSURLSession
81+
82+ /// Stats.
83+ private( set) var stats: [ RequestStat] = [ ]
84+
85+ /// Queue used to serialize concurrent accesses to this object.
86+ private let queue = dispatch_queue_create ( nil , DISPATCH_QUEUE_SERIAL)
87+
88+ /// Temporary stats under construction (ongoing requests).
89+ private var tmpStats: [ NSURLSessionTask: RequestStat] = [ : ]
90+
91+ /// Used to determine overall network type.
92+ private let defaultRouteReachability: SCNetworkReachability
93+
94+ /// Used to get the mobile data network type.
95+ private let networkInfo = CTTelephonyNetworkInfo ( )
96+
97+ init( session: NSURLSession) {
98+ self . session = session
99+ var zeroAddress = sockaddr_in ( )
100+ zeroAddress. sin_len = UInt8 ( sizeofValue ( zeroAddress) )
101+ zeroAddress. sin_family = sa_family_t ( AF_INET)
102+ defaultRouteReachability = withUnsafePointer ( & zeroAddress) {
103+ SCNetworkReachabilityCreateWithAddress ( nil , UnsafePointer ( $0) ) !
104+ }
105+
106+ // Reset the (global) epoch for logging.
107+ URLSessionLogger . epoch = NSDate ( )
108+ }
109+
110+ func dataTaskWithRequest( request: NSURLRequest, completionHandler: ( NSData? , NSURLResponse? , NSError? ) - > Void) - > NSURLSessionDataTask {
111+ var task : NSURLSessionDataTask !
112+ let startTime = NSDate ( )
113+ let networkType = getNetworkType ( )
114+ task = session. dataTaskWithRequest ( request, completionHandler: completionHandler)
115+ dispatch_sync ( self . queue) {
116+ self . tmpStats [ task] = RequestStat ( startTime: startTime, host: request. URL!. host!)
117+ self . tmpStats [ task] ? . networkType = networkType
118+ }
119+ task. addObserver ( self , forKeyPath: " state " , options: . New, context: nil )
120+ return task
121+ }
122+
123+ override func observeValueForKeyPath( keyPath: String? , ofObject object: AnyObject? , change: [ String : AnyObject] ? , context: UnsafeMutablePointer < Void > ) {
124+ if let task = object as? NSURLSessionTask {
125+ if keyPath == " state " {
126+ if task. state == . Canceling {
127+ dispatch_sync ( self . queue) {
128+ self . tmpStats [ task] !. cancelled = true
129+ }
130+ }
131+ if task. state == . Completed {
132+ let stopTime = NSDate ( )
133+ dispatch_sync ( self . queue) {
134+ var stat = self . tmpStats [ task] !
135+ stat. responseTime = stopTime. timeIntervalSinceDate ( stat. startTime)
136+ stat. dataSize = Int ( task. countOfBytesReceived)
137+ if let response = task. response as? NSHTTPURLResponse {
138+ stat. statusCode = response. statusCode
139+ } else if let error = task. error {
140+ stat. statusCode = error. code
141+ }
142+ self . stats. append ( stat)
143+ self . tmpStats. removeValueForKey ( task)
144+ }
145+ task. removeObserver ( self , forKeyPath: " state " )
146+ dump ( )
147+ }
148+ }
149+ }
150+ }
151+
152+ func dump( ) {
153+ dispatch_sync ( self . queue) {
154+ for stat in self . stats {
155+ print ( " [NET] \( stat. description) " )
156+ }
157+ self . stats. removeAll ( )
158+ }
159+ }
160+
161+ // MARK: Network status
162+
163+ /// Return the current network type as a human-friendly string.
164+ ///
165+ private func getNetworkType( ) - > String? {
166+ var flags = SCNetworkReachabilityFlags ( )
167+ if SCNetworkReachabilityGetFlags ( defaultRouteReachability, & flags) {
168+ if flags. contains ( . IsWWAN) {
169+ if let technology = networkInfo. currentRadioAccessTechnology {
170+ return URLSessionLogger . radioAccessTechnologyDescription ( technology)
171+ }
172+ } else {
173+ return " WIFI "
174+ }
175+ }
176+ return nil
177+ }
178+
179+ /// Convert one of the enum-like `CTRadioAccessTechnology*` constants into a human-friendly string.
180+ ///
181+ static func radioAccessTechnologyDescription( radioAccessTechnology: String) - > String {
182+ switch ( radioAccessTechnology) {
183+ case CTRadioAccessTechnologyGPRS: return " GPRS "
184+ case CTRadioAccessTechnologyEdge: return " EDGE "
185+ case CTRadioAccessTechnologyWCDMA: return " WCDMA "
186+ case CTRadioAccessTechnologyHSDPA: return " HSDPA "
187+ case CTRadioAccessTechnologyHSUPA: return " HSUPA "
188+ case CTRadioAccessTechnologyCDMA1x: return " CDMA(1x) "
189+ case CTRadioAccessTechnologyCDMAEVDORev0: return " CDMA(EVDORev0) "
190+ case CTRadioAccessTechnologyCDMAEVDORevA: return " CDMA(EVDORevA) "
191+ case CTRadioAccessTechnologyCDMAEVDORevB: return " CDMA(EVDORevB) "
192+ case CTRadioAccessTechnologyeHRPD: return " HRPD "
193+ case CTRadioAccessTechnologyLTE: return " LTE "
194+ default : return " ? "
195+ }
196+ }
197+ }
198+
199+ #endif // DBEUG
0 commit comments