diff --git a/.gitmodules b/.gitmodules index b623bd0e..e1d20022 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/LlamaKit/LlamaKit.git [submodule "Carthage/Checkouts/Assertions"] path = Carthage/Checkouts/Assertions - url = https://github.com/ikesyo/Assertions.git + url = https://github.com/antitypical/Assertions.git [submodule "Carthage/Checkouts/OHHTTPStubs"] path = Carthage/Checkouts/OHHTTPStubs url = https://github.com/ishkawa/OHHTTPStubs.git diff --git a/APIKit.podspec b/APIKit.podspec index 179f80b0..da4091a3 100644 --- a/APIKit.podspec +++ b/APIKit.podspec @@ -27,5 +27,5 @@ Pod::Spec.new do |s| LICENSE } - s.dependency "LlamaKit", "~> 0.5.0" + s.dependency "LlamaKit", "~> 0.6.0" end diff --git a/APIKit/API.swift b/APIKit/API.swift index 517c5bcf..bf58c97e 100644 --- a/APIKit/API.swift +++ b/APIKit/API.swift @@ -6,24 +6,17 @@ import LlamaKit public let APIKitErrorDomain = "APIKitErrorDomain" -// use private, global scope variable until we can use stored class var in Swift 1.2 -private let internalDefaultURLSession = NSURLSession( - configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), - delegate: URLSessionDelegate(), - delegateQueue: nil -) - public class API { // configurations - public class func baseURL() -> NSURL { - fatalError("API.baseURL() must be overrided in subclasses.") + public class var baseURL: NSURL { + fatalError("API.baseURL must be overrided in subclasses.") } - public class func requestBodyBuilder() -> RequestBodyBuilder { + public class var requestBodyBuilder: RequestBodyBuilder { return .JSON(writingOptions: nil) } - public class func responseBodyParser() -> ResponseBodyParser { + public class var responseBodyParser: ResponseBodyParser { return .JSON(readingOptions: nil) } @@ -35,9 +28,15 @@ public class API { return [Int](200..<300) } + private static let internalDefaultURLSession = NSURLSession( + configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), + delegate: URLSessionDelegate(), + delegateQueue: nil + ) + // build NSURLRequest public class func URLRequest(method: Method, _ path: String, _ parameters: [String: AnyObject] = [:]) -> NSURLRequest? { - if let components = NSURLComponents(URL: baseURL(), resolvingAgainstBaseURL: true) { + if let components = NSURLComponents(URL: baseURL, resolvingAgainstBaseURL: true) { let request = NSMutableURLRequest() switch method { @@ -45,7 +44,7 @@ public class API { components.query = URLEncodedSerialization.stringFromObject(parameters, encoding: NSUTF8StringEncoding) default: - switch requestBodyBuilder().buildBodyFromObject(parameters) { + switch requestBodyBuilder.buildBodyFromObject(parameters) { case .Success(let box): request.HTTPBody = box.unbox @@ -57,8 +56,8 @@ public class API { components.path = (components.path ?? "").stringByAppendingPathComponent(path) request.URL = components.URL request.HTTPMethod = method.rawValue - request.setValue(requestBodyBuilder().contentTypeHeader, forHTTPHeaderField: "Content-Type") - request.setValue(responseBodyParser().acceptHeader, forHTTPHeaderField: "Accept") + request.setValue(requestBodyBuilder.contentTypeHeader, forHTTPHeaderField: "Content-Type") + request.setValue(responseBodyParser.acceptHeader, forHTTPHeaderField: "Accept") return request } else { @@ -66,18 +65,8 @@ public class API { } } - // In Swift 1.1, we could not omit `URLSession` argument of `func send(request:URLSession(=default):handler(=default):)` - // with trailing closure, so we provide following 2 methods - // - `func sendRequest(request:handler(=default):)` - // - `func sendRequest(request:URLSession:handler(=default):)`. - // In Swift 1.2, we can omit default arguments with trailing closure, so they should be replaced with - // - `func sendRequest(request:URLSession(=default):handler(=default):)` - public class func sendRequest(request: T, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { - return sendRequest(request, URLSession: defaultURLSession, handler: handler) - } - // send request and build response object - public class func sendRequest(request: T, URLSession: NSURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { + public class func sendRequest(request: T, URLSession: NSURLSession = defaultURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { let mainQueue = dispatch_get_main_queue() if let URLRequest = request.URLRequest { @@ -93,7 +82,7 @@ public class API { let statusCode = (URLResponse as? NSHTTPURLResponse)?.statusCode ?? 0 if !contains(self.acceptableStatusCodes, statusCode) { let error: NSError = { - switch self.responseBodyParser().parseData(data) { + switch self.responseBodyParser.parseData(data) { case .Success(let box): return self.responseErrorFromObject(box.unbox) case .Failure(let box): return box.unbox } @@ -103,7 +92,7 @@ public class API { return } - let mappedResponse: Result = self.responseBodyParser().parseData(data).flatMap { rawResponse in + let mappedResponse: Result = self.responseBodyParser.parseData(data).flatMap { rawResponse in if let response = T.responseFromObject(rawResponse) { return success(response) } else { @@ -192,7 +181,7 @@ private var dataTaskResponseBufferKey = 0 private var dataTaskCompletionHandlerKey = 0 private extension NSURLSessionDataTask { - // `var request: Request?` is not available in both of Swift 1.1 and 1.2 + // `var request: Request?` is not available in Swift 1.2 // ("protocol can only be used as a generic constraint") private var request: Box? { get { @@ -248,4 +237,3 @@ extension NSURLSessionDownloadTask { } } } - diff --git a/APIKit/Request.swift b/APIKit/Request.swift index 5a906cea..3043bf93 100644 --- a/APIKit/Request.swift +++ b/APIKit/Request.swift @@ -5,5 +5,5 @@ public protocol Request { var URLRequest: NSURLRequest? { get } - class func responseFromObject(object: AnyObject) -> Response? + static func responseFromObject(object: AnyObject) -> Response? } diff --git a/APIKit/RequestBodyBuilder.swift b/APIKit/RequestBodyBuilder.swift index 385741b3..d751b454 100644 --- a/APIKit/RequestBodyBuilder.swift +++ b/APIKit/RequestBodyBuilder.swift @@ -33,14 +33,14 @@ public enum RequestBodyBuilder { return failure(error) } - return try { error in + return try({ error in return NSJSONSerialization.dataWithJSONObject(object, options: writingOptions, error: error) - } + }) case .URL(let encoding): - return try { error in + return try({ error in return URLEncodedSerialization.dataFromObject(object, encoding: encoding, error: error) - } + }) case .Custom(let (_, buildBodyFromObject)): return buildBodyFromObject(object) diff --git a/APIKit/ResponseBodyParser.swift b/APIKit/ResponseBodyParser.swift index 7876b68e..a6b69f30 100644 --- a/APIKit/ResponseBodyParser.swift +++ b/APIKit/ResponseBodyParser.swift @@ -25,14 +25,14 @@ public enum ResponseBodyParser { public func parseData(data: NSData) -> Result { switch self { case .JSON(let readingOptions): - return try { error in + return try({ error in return NSJSONSerialization.JSONObjectWithData(data, options: readingOptions, error: error) - } + }) case .URL(let encoding): - return try { error in + return try({ error in return URLEncodedSerialization.objectFromData(data, encoding: encoding, error: error) - } + }) case .Custom(let (accept, parseData)): return parseData(data) diff --git a/APIKit/URLEncodedSerialization.swift b/APIKit/URLEncodedSerialization.swift index 4be61177..d6c50cbc 100644 --- a/APIKit/URLEncodedSerialization.swift +++ b/APIKit/URLEncodedSerialization.swift @@ -1,11 +1,11 @@ import Foundation private func escape(string: String) -> String { - return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, "!*'();:@&=+$,/?%#[]", CFStringBuiltInEncodings.UTF8.rawValue) + return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, "!*'();:@&=+$,/?%#[]", CFStringBuiltInEncodings.UTF8.rawValue) as String } private func unescape(string: String) -> String { - return CFURLCreateStringByReplacingPercentEscapes(nil, string, nil) + return CFURLCreateStringByReplacingPercentEscapes(nil, string, nil) as String } public class URLEncodedSerialization { @@ -15,8 +15,8 @@ public class URLEncodedSerialization { if let string = NSString(data: data, encoding: encoding) as? String { dictionary = [String: AnyObject]() - for pair in split(string, { $0 == "&" }) { - let contents = split(pair, { $0 == "=" }) + for pair in string.componentsSeparatedByString("&") { + let contents = pair.componentsSeparatedByString("=") if contents.count == 2 { dictionary?[contents[0]] = unescape(contents[1]) diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index 2e866a15..a29bbd48 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -6,15 +6,15 @@ import OHHTTPStubs class APITests: XCTestCase { class MockAPI: API { - override class func baseURL() -> NSURL { + override class var baseURL: NSURL { return NSURL(string: "https://api.github.com")! } - override class func requestBodyBuilder() -> RequestBodyBuilder { + override class var requestBodyBuilder: RequestBodyBuilder { return .JSON(writingOptions: nil) } - override class func responseBodyParser() -> ResponseBodyParser { + override class var responseBodyParser: ResponseBodyParser { return .JSON(readingOptions: nil) } diff --git a/APIKitTests/RequestBodyBuilderTests.swift b/APIKitTests/RequestBodyBuilderTests.swift index f86c8835..afb30cab 100644 --- a/APIKitTests/RequestBodyBuilderTests.swift +++ b/APIKitTests/RequestBodyBuilderTests.swift @@ -16,10 +16,10 @@ class RequestBodyBuilderTests: XCTestCase { switch builder.buildBodyFromObject(object) { case .Success(let box): - let dictionary = NSJSONSerialization.JSONObjectWithData(box.unbox, options: nil, error: nil) as [String: Int] - assertEqual(dictionary["foo"], 1) - assertEqual(dictionary["bar"], 2) - assertEqual(dictionary["baz"], 3) + let dictionary = NSJSONSerialization.JSONObjectWithData(box.unbox, options: nil, error: nil) as? [String: Int] + assertEqual(dictionary?["foo"], 1) + assertEqual(dictionary?["bar"], 2) + assertEqual(dictionary?["baz"], 3) case .Failure: XCTFail() @@ -52,10 +52,10 @@ class RequestBodyBuilderTests: XCTestCase { switch builder.buildBodyFromObject(object) { case .Success(let box): - let dictionary = URLEncodedSerialization.objectFromData(box.unbox, encoding: NSUTF8StringEncoding, error: nil) as [String: String] - assertEqual(dictionary["foo"], "1") - assertEqual(dictionary["bar"], "2") - assertEqual(dictionary["baz"], "3") + let dictionary = URLEncodedSerialization.objectFromData(box.unbox, encoding: NSUTF8StringEncoding, error: nil) as? [String: String] + assertEqual(dictionary?["foo"], "1") + assertEqual(dictionary?["bar"], "2") + assertEqual(dictionary?["baz"], "3") case .Failure: XCTFail() @@ -63,7 +63,7 @@ class RequestBodyBuilderTests: XCTestCase { } func testCustomHeader() { - let builder = RequestBodyBuilder.Custom(contentTypeHeader: "foo", buildBodyFromObject: { o in success(o as NSData) }) + let builder = RequestBodyBuilder.Custom(contentTypeHeader: "foo", buildBodyFromObject: { o in success(o as! NSData) }) assertEqual(builder.contentTypeHeader, "foo") } diff --git a/APIKitTests/ResponseBodyParserTests.swift b/APIKitTests/ResponseBodyParserTests.swift index 8f16d2a8..431af8bf 100644 --- a/APIKitTests/ResponseBodyParserTests.swift +++ b/APIKitTests/ResponseBodyParserTests.swift @@ -17,10 +17,10 @@ class ResponseBodyParserTests: XCTestCase { switch parser.parseData(data) { case .Success(let box): - let dictionary = box.unbox as [String: Int] - assertEqual(dictionary["foo"], 1) - assertEqual(dictionary["bar"], 2) - assertEqual(dictionary["baz"], 3) + let dictionary = box.unbox as? [String: Int] + assertEqual(dictionary?["foo"], 1) + assertEqual(dictionary?["bar"], 2) + assertEqual(dictionary?["baz"], 3) case .Failure: XCTFail() @@ -55,10 +55,10 @@ class ResponseBodyParserTests: XCTestCase { switch parser.parseData(data) { case .Success(let box): - let dictionary = box.unbox as [String: String] - assertEqual(dictionary["foo"], "1") - assertEqual(dictionary["bar"], "2") - assertEqual(dictionary["baz"], "3") + let dictionary = box.unbox as? [String: String] + assertEqual(dictionary?["foo"], "1") + assertEqual(dictionary?["bar"], "2") + assertEqual(dictionary?["baz"], "3") case .Failure: XCTFail() @@ -79,7 +79,7 @@ class ResponseBodyParserTests: XCTestCase { switch parser.parseData(data) { case .Success(let box): - let dictionary = box.unbox as [String: Int] + let dictionary = box.unbox as? [String: Int] assertEqual(dictionary, expectedDictionary) case .Failure: diff --git a/Cartfile b/Cartfile index 46680ecd..f2ad3f1e 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "LlamaKit/LlamaKit" ~> 0.5.0 +github "LlamaKit/LlamaKit" ~> 0.6.0 diff --git a/Cartfile.private b/Cartfile.private index 05269001..aaa392e9 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,2 +1,2 @@ -github "ikesyo/Assertions" "swift-1.1" +github "antitypical/Assertions" "master" github "ishkawa/OHHTTPStubs" "master" diff --git a/Cartfile.resolved b/Cartfile.resolved index 2fcbaab4..8ac70784 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ -github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" -github "LlamaKit/LlamaKit" "v0.5.0" +github "antitypical/Assertions" "19bac03828dcb2f9b9ecb7f829e09bb3900886e5" +github "LlamaKit/LlamaKit" "v0.6.0" github "ishkawa/OHHTTPStubs" "825806534aa2bdc6ebba5e26cd304de2ced67395" diff --git a/Carthage/Checkouts/Assertions b/Carthage/Checkouts/Assertions index fec437ce..19bac038 160000 --- a/Carthage/Checkouts/Assertions +++ b/Carthage/Checkouts/Assertions @@ -1 +1 @@ -Subproject commit fec437ce6857259ac8bda3eb255c5782aa798734 +Subproject commit 19bac03828dcb2f9b9ecb7f829e09bb3900886e5 diff --git a/Carthage/Checkouts/LlamaKit b/Carthage/Checkouts/LlamaKit index e37b9669..e28d7f6e 160000 --- a/Carthage/Checkouts/LlamaKit +++ b/Carthage/Checkouts/LlamaKit @@ -1 +1 @@ -Subproject commit e37b966998df6ca05445c0b5d9c6c9560f1e7b61 +Subproject commit e28d7f6e82fbd5dcd5388b36e2acf4eedb44b4e8 diff --git a/DemoApp/GitHub.swift b/DemoApp/GitHub.swift index 8bdebda6..8430f3ca 100644 --- a/DemoApp/GitHub.swift +++ b/DemoApp/GitHub.swift @@ -3,19 +3,19 @@ import APIKit import LlamaKit class GitHub: API { - override class func baseURL() -> NSURL { + override class var baseURL: NSURL { return NSURL(string: "https://api.github.com")! } - override class func requestBodyBuilder() -> RequestBodyBuilder { + override class var requestBodyBuilder: RequestBodyBuilder { return .JSON(writingOptions: nil) } - override class func responseBodyParser() -> ResponseBodyParser { + override class var responseBodyParser: ResponseBodyParser { return .JSON(readingOptions: nil) } - class Request { + class Endpoint { // https://developer.github.com/v3/search/#search-repositories class SearchRepositories: APIKit.Request { enum Sort: String { diff --git a/DemoApp/Models.swift b/DemoApp/Models.swift index b5b4bcf6..6881fe5d 100644 --- a/DemoApp/Models.swift +++ b/DemoApp/Models.swift @@ -1,38 +1,40 @@ import Foundation -class Repository { - let id: Int! - let name: String! - let owner: User! +struct Repository { + let id: Int + let name: String + let owner: User init?(dictionary: NSDictionary) { - id = dictionary["id"] as? Int - name = dictionary["name"] as? String - - if let userDictionary = dictionary["owner"] as? NSDictionary { - owner = User(dictionary: userDictionary) - } - - if id == nil || name == nil || owner == nil { + if + let id = dictionary["id"] as? Int, + let userDictionary = dictionary["owner"] as? NSDictionary, + let name = dictionary["name"] as? String, + let user = User(dictionary: userDictionary) { + self.id = id + self.name = name + self.owner = user + } else { return nil } } } -class User { - let id: Int! - let login: String! - let avatarURL: NSURL! +struct User { + let id: Int + let login: String + let avatarURL: NSURL init?(dictionary: NSDictionary) { - id = dictionary["id"] as? Int - login = dictionary["login"] as? String - - if let string = dictionary["avatar_url"] as? String { - avatarURL = NSURL(string: string) - } - - if id == nil || login == nil || avatarURL == nil { + if + let id = dictionary["id"] as? Int, + let login = dictionary["login"] as? String, + let string = dictionary["avatar_url"] as? String, + let avatarURL = NSURL(string: string) { + self.id = id + self.login = login + self.avatarURL = avatarURL + } else { return nil } } diff --git a/DemoApp/ViewController.swift b/DemoApp/ViewController.swift index 996e040a..ec5d531c 100644 --- a/DemoApp/ViewController.swift +++ b/DemoApp/ViewController.swift @@ -10,7 +10,7 @@ class ViewController: UITableViewController { override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) - let request = GitHub.Request.SearchRepositories(query: "APIKit") + let request = GitHub.Endpoint.SearchRepositories(query: "APIKit") GitHub.sendRequest(request) { response in switch response { @@ -32,7 +32,7 @@ class ViewController: UITableViewController { } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell + let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell let repository = repositories[indexPath.row] cell.textLabel?.text = "\(repository.owner.login)/\(repository.name)" diff --git a/README.md b/README.md index 001943fa..c83537bb 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ GitHub.sendRequest(request) { response in // type of response is inferred from type of request self.repositories = box.unbox self.tableView?.reloadData() - + case .Failure(let box): // if request fails, value in box is a NSError println(box.unbox) @@ -42,11 +42,11 @@ GitHub.sendRequest(request) { response in ## Requirements -- Swift 1.1 -- iOS 7.0 or later +- Swift 1.2 +- iOS 8.0 or later (if you use Carthage), iOS 7.0 if you copy sources - Mac OS 10.9 or later -If you want to use APIKit with Swift 1.2, try `swift-1.2` branch. +If you want to use APIKit with Swift 1.1, try [0.6.0](https://github.com/ishkawa/APIKit/releases/tag/0.6.0). ## Installation @@ -72,24 +72,24 @@ You have 3 choices. If your app supports iOS 7.0, you can only choose copying so ## Usage 1. Create subclass of `API` that represents target web API. -2. Set base URL by overriding `baseURL()`. -3. Set encoding of request body by overriding `requestBodyBuilder()`. -4. Set encoding of response body by overriding `responseBodyParser()`. +2. Set base URL by overriding `baseURL`. +3. Set encoding of request body by overriding `requestBodyBuilder`. +4. Set encoding of response body by overriding `responseBodyParser`. 5. Define request classes that conforms to `Request` for each endpoints. ### Example ```swift class GitHub: API { - override class func baseURL() -> NSURL { + override class var baseURL: NSURL { return NSURL(string: "https://api.github.com")! } - override class func requestBodyBuilder() -> RequestBodyBuilder { + override class var requestBodyBuilder: RequestBodyBuilder { return .JSON(writingOptions: nil) } - override class func responseBodyParser() -> ResponseBodyParser { + override class var responseBodyParser: ResponseBodyParser { return .JSON(readingOptions: nil) } diff --git a/circle.yml b/circle.yml index b75d50cb..7648d666 100644 --- a/circle.yml +++ b/circle.yml @@ -4,17 +4,19 @@ machine: dependencies: override: - - ./script/import-certificates - - sudo gem install cocoapods - - sudo gem install xcpretty - - brew install carthage - - carthage bootstrap + #- ./script/import-certificates + #- sudo gem install cocoapods + #- sudo gem install xcpretty + #- brew install carthage + #- carthage bootstrap + - echo "skip installing dependencies until Circle CI supports Xcode 6.3 (with Swift 1.2)." test: override: - - ./script/check-carthage-compatibility - - pod lib lint - - set -o pipefail && xcodebuild test -scheme APIKit-iOS | xcpretty -c -r junit -o $CIRCLE_TEST_REPORTS/test-report-ios.xml - - set -o pipefail && xcodebuild test -scheme APIKit-Mac | xcpretty -c -r junit -o $CIRCLE_TEST_REPORTS/test-report-mac.xml - - set -o pipefail && xcodebuild build -scheme DemoApp -sdk iphonesimulator | xcpretty -c + #- ./script/check-carthage-compatibility + #- pod lib lint + #- set -o pipefail && xcodebuild test -scheme APIKit-iOS | xcpretty -c -r junit -o $CIRCLE_TEST_REPORTS/test-report-ios.xml + #- set -o pipefail && xcodebuild test -scheme APIKit-Mac | xcpretty -c -r junit -o $CIRCLE_TEST_REPORTS/test-report-mac.xml + #- set -o pipefail && xcodebuild build -scheme DemoApp -sdk iphonesimulator | xcpretty -c + - echo "skip testing until Circle CI supports Xcode 6.3 (with Swift 1.2)."