Skip to content

Commit b7ab97e

Browse files
author
Clément Le Provost
committed
Merge branch 'offline'
2 parents a3f5b99 + c936546 commit b7ab97e

File tree

9 files changed

+550
-88
lines changed

9 files changed

+550
-88
lines changed

Source/Client.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,15 @@ import Foundation
298298

299299
/// Perform an HTTP Query.
300300
func performHTTPQuery(path: String, method: HTTPMethod, body: [String: AnyObject]?, hostnames: [String], isSearchQuery: Bool = false, completionHandler: CompletionHandler? = nil) -> NSOperation {
301-
let request = newRequest(method, path: path, body: body, hostnames: hostnames, isSearchQuery: isSearchQuery) {
301+
var request: Request!
302+
request = newRequest(method, path: path, body: body, hostnames: hostnames, isSearchQuery: isSearchQuery) {
302303
(content: [String: AnyObject]?, error: NSError?) -> Void in
303304
if completionHandler != nil {
304305
dispatch_async(dispatch_get_main_queue()) {
305-
completionHandler!(content: content, error: error)
306+
// WARNING: Because of the asynchronous dispatch, the request could have been cancelled in the meantime.
307+
if !request.cancelled {
308+
completionHandler!(content: content, error: error)
309+
}
306310
}
307311
}
308312
}

Source/Index.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,12 +526,12 @@ import Foundation
526526
/// - returns: A cancellable operation.
527527
///
528528
@objc public func searchDisjunctiveFaceting(query: Query, disjunctiveFacets: [String], refinements: [String: [String]], completionHandler: CompletionHandler) -> NSOperation {
529-
var requests = [IndexQuery]()
529+
var queries = [Query]()
530530

531531
// Build the first, global query.
532532
let globalQuery = Query(copy: query)
533533
globalQuery.facetFilters = Index._buildFacetFilters(disjunctiveFacets, refinements: refinements, excludedFacet: nil)
534-
requests.append(IndexQuery(index: self, query: globalQuery))
534+
queries.append(globalQuery)
535535

536536
// Build the refined queries.
537537
for disjunctiveFacet in disjunctiveFacets {
@@ -545,11 +545,11 @@ import Foundation
545545
disjunctiveQuery.attributesToSnippet = []
546546
// Do not show this query in analytics, either.
547547
disjunctiveQuery.analytics = false
548-
requests.append(IndexQuery(index: self, query: disjunctiveQuery))
548+
queries.append(disjunctiveQuery)
549549
}
550550

551551
// Run all the queries.
552-
let operation = client.multipleQueries(requests, completionHandler: { (content, error) -> Void in
552+
let operation = self.multipleQueries(queries, completionHandler: { (content, error) -> Void in
553553
var finalContent: [String: AnyObject]? = nil
554554
var finalError: NSError? = error
555555
if error == nil {
@@ -565,6 +565,33 @@ import Foundation
565565
return operation
566566
}
567567

568+
/// Run multiple queries on this index.
569+
/// This method is a variant of `Client.multipleQueries()` where the targeted index is always the receiver.
570+
///
571+
/// - parameter queries: The queries to run.
572+
/// - parameter completionHandler: Completion handler to be notified of the request's outcome.
573+
/// - returns: A cancellable operation.
574+
///
575+
@objc public func multipleQueries(queries: [Query], strategy: String?, completionHandler: CompletionHandler) -> NSOperation {
576+
var requests = [IndexQuery]()
577+
for query in queries {
578+
requests.append(IndexQuery(index: self, query: query))
579+
}
580+
return client.multipleQueries(requests, strategy: strategy, completionHandler: completionHandler)
581+
}
582+
583+
/// Run multiple queries on this index.
584+
/// This method is a variant of `Client.multipleQueries()` where the targeted index is always the receiver.
585+
///
586+
/// - parameter queries: The queries to run.
587+
/// - param strategy: The strategy to use.
588+
/// - parameter completionHandler: Completion handler to be notified of the request's outcome.
589+
/// - returns: A cancellable operation.
590+
///
591+
public func multipleQueries(queries: [Query], strategy: Client.MultipleQueriesStrategy? = nil, completionHandler: CompletionHandler) -> NSOperation {
592+
return self.multipleQueries(queries, strategy: strategy?.rawValue, completionHandler: completionHandler)
593+
}
594+
568595
/// Aggregate disjunctive faceting search results.
569596
private class func _aggregateResults(disjunctiveFacets: [String], refinements: [String: [String]], content: [String: AnyObject]) throws -> [String: AnyObject] {
570597
guard let results = content["results"] as? [AnyObject] else {

Source/Network.swift

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,159 @@ protocol URLSession {
4141
extension 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

Comments
 (0)