├── .github └── FUNDING.yml ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CODE_OF_CONDUCT.md ├── Cartfile ├── Cartfile.resolved ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── ws │ ├── Info.plist │ ├── WS+Requests.swift │ ├── WS+TypedCalls.swift │ ├── WS.swift │ ├── WSError.swift │ ├── WSHTTPVerb.swift │ ├── WSLogger.swift │ ├── WSModelJSONParser.swift │ ├── WSNetworkIndicator.swift │ ├── WSRequest.swift │ └── ws.h └── wsTests │ ├── 1px.jpg │ ├── Info.plist │ ├── Mapping.swift │ ├── mappingTests.swift │ └── wsTests.swift ├── banner.png ├── ws.framework.zip ├── ws.podspec └── ws.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ ├── WorkspaceSettings.xcsettings │ └── ws.xcscmblueprint └── xcshareddata └── xcschemes └── ws.xcscheme /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: freshos 2 | github: s4cha 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | Carthage/Checkouts 32 | Carthage/Build 33 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.1.3 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace # this would rewrite every single file :/ 3 | - large_tuple 4 | - type_name 5 | 6 | opt_in_rules: 7 | - empty_count 8 | - attributes 9 | - closure_spacing 10 | - conditional_returns_on_newline 11 | - overridden_super_call 12 | - private_outlet 13 | - private_unit_test 14 | - prohibited_super_call 15 | - redundant_nil_coalescing 16 | - switch_case_on_newline 17 | - sorted_imports 18 | - first_where 19 | - closure_end_indentation 20 | 21 | variable_name: 22 | min_length: 1 23 | 24 | excluded: 25 | - Carthage 26 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sachadso@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.9.1" 2 | github "freshOS/Arrow" "5.1.1" 3 | github "freshOS/Then" "5.1.2" 4 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Alamofire/Alamofire" "4.9.1" 2 | github "freshOS/Arrow" "5.1.1" 3 | github "freshOS/Then" "5.1.2" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Alamofire", 6 | "repositoryURL": "https://github.com/Alamofire/Alamofire", 7 | "state": { 8 | "branch": null, 9 | "revision": "747c8db8d57b68d5e35275f10c92d55f982adbd4", 10 | "version": "4.9.1" 11 | } 12 | }, 13 | { 14 | "package": "Arrow", 15 | "repositoryURL": "https://github.com/freshOS/Arrow", 16 | "state": { 17 | "branch": null, 18 | "revision": "ede0996c0b8f32a67bba80ae384f0898e9a8cb95", 19 | "version": "5.1.2" 20 | } 21 | }, 22 | { 23 | "package": "Then", 24 | "repositoryURL": "https://github.com/freshOS/Then", 25 | "state": { 26 | "branch": null, 27 | "revision": "7b20f33ca37c6ecc9420c4b699b641dbca504912", 28 | "version": "5.1.3" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ws", 7 | platforms: [.iOS(.v8)], 8 | products: [.library(name: "ws", targets: ["ws"])], 9 | dependencies: [ 10 | .package(url: "https://github.com/freshOS/Arrow", .exact("5.1.2")), 11 | .package(url: "https://github.com/freshOS/Then", .exact("5.1.3")), 12 | .package(url: "https://github.com/Alamofire/Alamofire", .exact("4.9.1")) 13 | ], 14 | targets: [ 15 | .target(name: "ws", dependencies:["Arrow", "Then", "Alamofire"]), 16 | .testTarget(name: "wsTests", dependencies: ["ws"]) 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ws](https://raw.githubusercontent.com/freshOS/ws/master/banner.png) 2 | 3 | # ⚠ Important Notice: Farewell ws... hello Networking ! 4 | [Networking](https://github.com/freshOS/Networking) is the next generation of the [ws](https://github.com/freshOS/ws) project. Think of it as ws 2.0 built for iOS13. 5 | It uses Combine native Apple's framework over [Then](https://github.com/freshOS/Then) Promise Library, removes [Arrow](https://github.com/freshOS/Arrow) dependency to favour Codable (Arrow can still be adapted easily though) and removes the [Alamofire](https://github.com/Alamofire/Alamofire) dependency in favour of a simpler purely native [URLSession](https://developer.apple.com/documentation/foundation/urlsession) implementation. In essence, less dependencies and more native stuff with an almost identical api. If your app supports iOS13 and up, it is strongly advised to migrate to Networking. WS will be "maintained" for backwards compatibility reasons but consider it deprected starting iOS13. 6 | 7 | # ws 8 | 9 | [![Language: Swift 5](https://img.shields.io/badge/language-swift5-f48041.svg?style=flat)](https://developer.apple.com/swift) 10 | ![Platform: iOS 8+](https://img.shields.io/badge/platform-iOS%208%2B-blue.svg?style=flat) 11 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 12 | [![Cocoapods compatible](https://img.shields.io/badge/Cocoapods-compatible-4BC51D.svg?style=flat)](https://cocoapods.org) 13 | [![License: MIT](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/freshOS/ws/blob/master/LICENSE) 14 | [![Build Status](https://app.bitrise.io/app/a6d157138f9ee86d/status.svg?token=W7-x9K5U976xiFrI8XqcJw&branch=master)](https://app.bitrise.io/app/a6d157138f9ee86d) 15 | [![codebeat badge](https://codebeat.co/badges/78d86c16-aa61-4a5e-8342-1aea8d437453)](https://codebeat.co/projects/github-com-freshos-ws) 16 | ![Release version](https://img.shields.io/github/release/freshos/ws.svg) 17 | 18 | [Reason](#why) - [Example](#usage) - [Installation](#installation) 19 | 20 | ```swift 21 | let ws = WS("http://jsonplaceholder.typicode.com") 22 | 23 | ws.get("/users").then { json in 24 | // Get back some json \o/ 25 | } 26 | ``` 27 | Because JSON apis are used in **99% of iOS Apps**, this should be **simple**. 28 | We developers should **focus on our app logic** rather than *boilerplate code* . 29 | *Less* code is *better* code 30 | ## Try it! 31 | 32 | ws is part of [freshOS](http://freshos.org) iOS toolset. Try it in an example App ! Download Starter Project 33 | 34 | 35 | ## How 36 | By providing a lightweight client that **automates boilerplate code everyone has to write**. 37 | By exposing a **delightfully simple** api to get the job done simply, clearly, quickly. 38 | Getting swift models from a JSON api is now *a problem of the past* 39 | 40 | ## What 41 | - [x] Build concise Apis 42 | - [x] Automatically maps your models 43 | - [x] Built-in network logger 44 | - [x] Stands on the shoulder of giants (Alamofire & Promises) 45 | - [x] Pure Swift, Simple & Lightweight 46 | 47 | ## Usage 48 | 49 | ### Bare JSON 50 | 51 | ```swift 52 | import ws // Import ws at the top of your file 53 | import Arrow // Import Arrow to get access to the JSON type 54 | 55 | class ViewController: UIViewController { 56 | 57 | // Set webservice base URL 58 | let ws = WS("http://jsonplaceholder.typicode.com") 59 | 60 | override func viewDidLoad() { 61 | super.viewDidLoad() 62 | 63 | // Get back some json instantly \o/ 64 | ws.get("/users").then { (json:JSON) in 65 | print(json) 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### Set up Model parsing 72 | Create a `User+JSON.swift` file and map the JSON keys to your model properties 73 | ```swift 74 | import Arrow 75 | 76 | extension User: ArrowParsable { 77 | 78 | mutating func deserialize(_ json: JSON) { 79 | identifier <-- json["id"] 80 | username <-- json["username"] 81 | email <-- json["email"] 82 | } 83 | } 84 | ``` 85 | *Note: `ws` uses `Arrow` for JSON Parsing 86 | https://github.com/freshOS/Arrow* 87 | 88 | ### Choose what you want back 89 | 90 | Here you are going to create a function that wraps your request. 91 | There are different ways of writing that function depending on what you want back. An empty block, the JSON, the model or the array of models. 92 | 93 | ```swift 94 | func voidCall() -> Promise { 95 | return ws.get("/users") 96 | } 97 | 98 | func jsonCall() -> Promise { 99 | return ws.get("/users") 100 | } 101 | 102 | func singleModelCall() -> Promise { 103 | return ws.get("/users/3") 104 | } 105 | 106 | func modelArrayCall() -> Promise<[User]> { 107 | return ws.get("/users") 108 | } 109 | ``` 110 | As you can notice, only by changing the return type, 111 | ws *automatically* knows what to do, for instance, try to parse the response into `User` models. 112 | 113 | This enables us to stay concise without having to write extra code. \o/ 114 | 115 | *Note: `ws` uses `then` for Promises 116 | https://github.com/freshOS/then* 117 | 118 | ### Get it! 119 | 120 | ```swift 121 | voidCall().then { 122 | print("done") 123 | } 124 | 125 | jsonCall().then { json in 126 | print(json) 127 | } 128 | 129 | singleModelCall().then { user in 130 | print(user) // Strongly typed User \o/ 131 | } 132 | 133 | modelArrayCall().then { users in 134 | print(users) // Strongly typed [User] \o/ 135 | } 136 | ``` 137 | 138 | ## Settings 139 | 140 | Want to log all network calls and responses? 141 | ```swift 142 | ws.logLevels = .debug 143 | ``` 144 | 145 | Want to hide network activity indicator? 146 | 147 | ```swift 148 | ws.showsNetworkActivityIndicator = false 149 | ``` 150 | 151 | Want to override the default session manager to customize trust policies? 152 | 153 | ```swift 154 | import Alamofire 155 | 156 | ws.sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager( 157 | policies: ["myspecialhostname.com" : .disableEvaluation] 158 | )) 159 | ``` 160 | 161 | ## Api Example 162 | Here is a Typical CRUD example for Articles : 163 | 164 | ```swift 165 | extension Article { 166 | 167 | static func list() -> Promise<[Article]> { 168 | return ws.get("/articles") 169 | } 170 | 171 | func save() -> Promise
{ 172 | return ws.post("/articles", params: ["name":name]) 173 | } 174 | 175 | func fetch() -> Promise
{ 176 | return ws.get("/articles/\(id)") 177 | } 178 | 179 | func update() -> Promise { 180 | return ws.put("/articles/\(id)", params: ["name":name]) 181 | } 182 | 183 | func delete() -> Promise { 184 | return ws.delete("/articles/\(id)") 185 | } 186 | 187 | } 188 | ``` 189 | 190 | Here is how we use it in code : 191 | ```swift 192 | // List Articles 193 | Article.list().then { articles in 194 | 195 | } 196 | 197 | // Create Article 198 | var newArticle = Article(name:"Cool story") 199 | newArticle.save().then { createdArticle in 200 | 201 | } 202 | 203 | // Fetch Article 204 | var existingArticle = Article(id:42) 205 | existingArticle.fetch().then { fetchedArticle in 206 | 207 | } 208 | 209 | // Edit Article 210 | existingArticle.name = "My new name" 211 | existingArticle.update().then { 212 | 213 | } 214 | 215 | // Delete Article 216 | existingArticle.delete().then { 217 | 218 | } 219 | ``` 220 | 221 | 222 | ### HTTP Status code 223 | 224 | When a request fails, we often want to know the reason thanks to the HTTP status code. 225 | Here is how to get it : 226 | 227 | ```swift 228 | ws.get("/users").then { 229 | // Do something 230 | }.onError { e in 231 | if let wsError = e as? WSError { 232 | print(wsError.status) 233 | print(wsError.status.rawValue) // RawValue for Int status 234 | } 235 | } 236 | ``` 237 | You can find the full `WSError` enum here -> https://github.com/freshOS/ws/blob/master/ws/WSError.swift 238 | 239 | ## Bonus - Load More pattern 240 | 241 | Very often we deal we lists and the ability to `load more` items. 242 | Here we are going to see an example implementation of this pattern using `ws`. 243 | This is not included because the logic itself depends on your backend implementation. 244 | This will give you an example for you to roll out your own version. 245 | 246 | ### Implementation 247 | 248 | 249 | ```swift 250 | import ws 251 | import then 252 | import Arrow 253 | 254 | 255 | class LoadMoreRequest { 256 | 257 | var limit = 12 258 | 259 | private var params = [String:Any]() 260 | private var offset = 0 261 | private var call: WSRequest! 262 | private var canLoadMore = true 263 | private var aCallback:((_ ts: [T]) -> [T])? = nil 264 | 265 | init(_ aCall: WSRequest) { 266 | call = aCall 267 | } 268 | 269 | func resetOffset() { 270 | offset = 0 271 | canLoadMore = true 272 | } 273 | 274 | func hasMoreItemsToload() -> Bool { 275 | return canLoadMore 276 | } 277 | 278 | func fetchNext() -> Promise<[T]> { 279 | params = call.params 280 | params["limit"] = limit 281 | params["offset"] = offset 282 | call.params = params 283 | offset += limit 284 | return call.fetch() 285 | .registerThen(parseModels) 286 | .resolveOnMainThread() 287 | } 288 | 289 | private func parseModels(_ json: JSON) -> [T] { 290 | let mapper = WSModelJSONParser() 291 | let models = mapper.toModels(json) 292 | if models.count < limit { 293 | canLoadMore = false 294 | } 295 | return models 296 | } 297 | } 298 | ``` 299 | As you can see, we have a strongly typed request. 300 | The limit is adjustable. 301 | It encapsulates a WSRequest. 302 | It handles the offset logic and also wether or not there are more items to load. 303 | 304 | And that's all we need! 305 | 306 | Now, this is how we build a `LoadMoreRequest` 307 | 308 | ```swift 309 | func loadMoreUsersRequest() -> LoadMoreRequest { 310 | return LoadMoreRequest(ws.getRequest("/users")) 311 | } 312 | ``` 313 | 314 | ### Usage 315 | And here is how we use it in our controllers : 316 | 317 | ```swift 318 | class ViewController: UIViewController { 319 | 320 | // Get a request 321 | let request = api.loadMoreUsersRequest() 322 | 323 | override func viewDidLoad() { 324 | super.viewDidLoad() 325 | request.limit = 5 // Set a limit if needed 326 | } 327 | 328 | func refresh() { 329 | // Resets the request, usually plugged with 330 | // the pull to refresh feature of a tableview 331 | request.resetOffset() 332 | } 333 | 334 | func loadMore() { 335 | // Get the next round of users 336 | request.fetchNext().then { users in 337 | print(users) 338 | } 339 | } 340 | 341 | func shouldDisplayLoadMoreSpinner() -> Bool { 342 | // This asks the requests if there are more items to come 343 | // This is useful to know if we show the "load more" spinner 344 | return request.hasMoreItemsToload() 345 | } 346 | } 347 | ``` 348 | 349 | Here you go you now have a simple way to deal with load more requests in your App 🎉 350 | 351 | 352 | ## Bonus - Simplifying restful routes usage 353 | 354 | When working with a `RESTFUL` api, we can have fun and go a little further. 355 | 356 | By introducing a `RestResource` protocol 357 | ```swift 358 | public protocol RestResource { 359 | static func restName() -> String 360 | func restId() -> String 361 | } 362 | ``` 363 | We can have a function that builds our `REST` URL 364 | ```swift 365 | public func restURL(_ r:T) -> String { 366 | return "/\(T.restName())/\(r.restId())" 367 | } 368 | ``` 369 | 370 | We conform our `User` Model to the protocol 371 | ```swift 372 | extension User:RestResource { 373 | static func restName() -> String { return "users" } 374 | func restId() -> String { return "\(identifier)" } 375 | } 376 | ``` 377 | 378 | 379 | And we can implement a version of `get` that takes our a `RestResource` 380 | 381 | ```swift 382 | public func get(_ restResource:T, params:[String:Any] = [String:Any]()) -> Promise { 383 | return get(restURL(restResource), params: params) 384 | } 385 | ``` 386 | then 387 | 388 | ```swift 389 | ws.get("/users/\(user.identifier)") 390 | ``` 391 | Can be written like : 392 | ```swift 393 | ws.get(user) 394 | ``` 395 | Of course, the same logic can be applied to the all the other ws functions (`post`, `put` `delete` etc) ! 🎉 396 | ## Installation 397 | 398 | ### Swift Package Manager (SPM) 399 | Due to the challenge of supporting all package manager at once, SPM support is availlable on a separate branch `spm-only`. 400 | 401 | ### Carthage 402 | In your Cartfile 403 | ``` 404 | github "freshOS/ws" 405 | ``` 406 | - Run `carthage update` 407 | - Drag and drop `ws.framework` from `Carthage/Build/iOS` to `Linked Frameworks and Libraries` (“General” settings tab) 408 | - Go to `Project` > `Target` > `Build Phases` + `New run Script Phase` 409 | 410 | `/usr/local/bin/carthage copy-frameworks` 411 | 412 | Add input files 413 | ``` 414 | $(SRCROOT)/Carthage/Build/iOS/ws.framework 415 | $(SRCROOT)/Carthage/Build/iOS/Alamofire.framework 416 | $(SRCROOT)/Carthage/Build/iOS/Arrow.framework 417 | $(SRCROOT)/Carthage/Build/iOS/then.framework 418 | ``` 419 | 420 | This links ws and its dependencies. 421 | 422 | ### Manually 423 | 424 | Carthage is pretty useful since it takes care of pulling dependencies such as Arrow, then and Alamofire. 425 | What's cool is that it really is transparent. What I mean is that you could just use carthage on the side to pull and build dependencies and manually link frameworks to your Xcode project. 426 | 427 | Without Carthage, I'd see 2 solutions : 428 | 1 - Copy paste all the source code : ws / then / Arrow / Alamofire which doesn't sound like a lot of fun ;) 429 | 2 - Manually link the frameworks (ws + dependencies) by A grabbing .frameworks them on each repo, or B use Carthage to build them 430 | 431 | ### Cocoapods 432 | 433 | ``` 434 | target 'MyApp' 435 | pod 'ws' 436 | use_frameworks! 437 | ``` 438 | 439 | ## Swift Version 440 | Swift 2 -> version [**1.3.0**](https://github.com/freshOS/ws/releases/tag/1.3.0) 441 | Swift 3 -> version [**2.0.4**](https://github.com/freshOS/ws/releases/tag/2.0.4) 442 | Swift 4 -> version [**3.0.0**](https://github.com/freshOS/ws/releases/tag/3.0.0) 443 | Swift 4.1 -> version [**3.1.0**](https://github.com/freshOS/ws/releases/tag/3.1.0) 444 | Swift 4.2 -> version [**3.2.0**](https://github.com/freshOS/ws/releases/tag/3.2.0) 445 | Swift 5.0 -> version [**5.0.0**](https://github.com/freshOS/ws/releases/tag/5.0.0) 446 | Swift 5.1 -> version [**5.1.0**](https://github.com/freshOS/ws/releases/tag/5.1.0) 447 | Swift 5.1.3 -> version [**5.1.1**](https://github.com/freshOS/ws/releases/tag/5.1.1) 448 | 449 | 450 | ### Backers 451 | Like the project? Offer coffee or support us with a monthly donation and help us continue our activities :) 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | ### Sponsors 485 | Become a sponsor and get your logo on our README on Github with a link to your site :) 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | -------------------------------------------------------------------------------- /Sources/ws/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/ws/WS+Requests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WS+Requests.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias Params = [String: Any] 12 | 13 | extension WS { 14 | 15 | public func getRequest(_ url: String, params: Params = Params()) -> WSRequest { 16 | return call(url, verb: .get, params: params) 17 | } 18 | 19 | public func putRequest(_ url: String, params: Params = Params()) -> WSRequest { 20 | return call(url, verb: .put, params: params) 21 | } 22 | 23 | public func postRequest(_ url: String, params: Params = Params()) -> WSRequest { 24 | return call(url, verb: .post, params: params) 25 | } 26 | 27 | public func deleteRequest(_ url: String, params: Params = Params()) -> WSRequest { 28 | return call(url, verb: .delete, params: params) 29 | } 30 | 31 | public func postMultipartRequest(_ url: String, 32 | params: Params = Params(), 33 | name: String, 34 | data: Data, 35 | fileName: String, 36 | mimeType: String) -> WSRequest { 37 | let multiPart = WSMultiPartData( 38 | multipartData: data, 39 | multipartName: name, 40 | multipartFileName: fileName, 41 | multipartMimeType: mimeType 42 | ) 43 | return postMultipartRequest(url, params: params, multiParts: [multiPart]) 44 | } 45 | 46 | public func postMultipartRequest(_ url: String, 47 | params: Params = Params(), 48 | multiParts: [WSMultiPartData], 49 | verb: WSHTTPVerb = .post) -> WSRequest { 50 | let c = call(url, verb: verb, params: params) 51 | c.isMultipart = true 52 | c.multiPartData = multiParts 53 | return c 54 | } 55 | 56 | public func putMultipartRequest(_ url: String, 57 | params: Params = Params(), 58 | name: String, 59 | data: Data, 60 | fileName: String, 61 | mimeType: String) -> WSRequest { 62 | let multiPart = WSMultiPartData( 63 | multipartData: data, 64 | multipartName: name, 65 | multipartFileName: fileName, 66 | multipartMimeType: mimeType 67 | ) 68 | return postMultipartRequest(url, params: params, multiParts: [multiPart], verb: .put) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Sources/ws/WS+TypedCalls.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WS+TypedCalls.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Arrow 10 | import Foundation 11 | import Then 12 | 13 | extension WS { 14 | 15 | public func get(_ url: String, 16 | params: Params = Params(), 17 | keypath: String? = nil) -> Promise<[T]> { 18 | let keypath = keypath ?? defaultCollectionParsingKeyPath 19 | return getRequest(url, params: params).fetch() 20 | .registerThen { (json: JSON) -> [T] in 21 | WSModelJSONParser().toModels(json, keypath: keypath) 22 | }.resolveOnMainThread() 23 | } 24 | 25 | public func get(_ url: String, 26 | params: Params = Params(), 27 | keypath: String? = nil) -> Promise { 28 | return resourceCall(.get, url: url, params: params, keypath: keypath) 29 | } 30 | 31 | public func post(_ url: String, 32 | params: Params = Params(), 33 | keypath: String? = nil) -> Promise { 34 | return resourceCall(.post, url: url, params: params, keypath: keypath) 35 | } 36 | 37 | public func put(_ url: String, 38 | params: Params = Params(), 39 | keypath: String? = nil) -> Promise { 40 | return resourceCall(.put, url: url, params: params, keypath: keypath) 41 | } 42 | 43 | public func delete(_ url: String, 44 | params: Params = Params(), 45 | keypath: String? = nil) -> Promise { 46 | return resourceCall(.delete, url: url, params: params, keypath: keypath) 47 | } 48 | 49 | private func resourceCall(_ verb: WSHTTPVerb, 50 | url: String, 51 | params: Params = Params(), 52 | keypath: String? = nil) -> Promise { 53 | let c = defaultCall() 54 | c.httpVerb = verb 55 | c.URL = url 56 | c.params = params 57 | 58 | // Apply corresponding JSON mapper 59 | return c.fetch().registerThen { (json: JSON) -> T in 60 | return WSModelJSONParser().toModel(json, keypath: keypath ?? self.defaultObjectParsingKeyPath) 61 | }.resolveOnMainThread() 62 | } 63 | 64 | } 65 | 66 | extension WS { 67 | 68 | public func get(_ url: String, 69 | params: Params = Params(), 70 | keypath: String? = nil) -> Promise<[T]> { 71 | let keypath = keypath ?? defaultCollectionParsingKeyPath 72 | return getRequest(url, params: params) 73 | .fetch() 74 | .registerThen { (json: JSON) in 75 | Promise<[T]> { (resolve, reject) in 76 | if let t: [T] = WSModelJSONParser().toModels(json, keypath: keypath) { 77 | resolve(t) 78 | } else { 79 | reject(WSError.unableToParseResponse) 80 | } 81 | } 82 | } 83 | .resolveOnMainThread() 84 | } 85 | 86 | public func get(_ url: String, 87 | params: Params = Params(), 88 | keypath: String? = nil) -> Promise { 89 | return typeCall(.get, url: url, params: params, keypath: keypath) 90 | } 91 | 92 | public func post(_ url: String, 93 | params: Params = Params(), 94 | keypath: String? = nil) -> Promise { 95 | return typeCall(.post, url: url, params: params, keypath: keypath) 96 | } 97 | 98 | public func put(_ url: String, 99 | params: Params = Params(), 100 | keypath: String? = nil) -> Promise { 101 | return typeCall(.put, url: url, params: params, keypath: keypath) 102 | } 103 | 104 | public func delete(_ url: String, 105 | params: Params = Params(), 106 | keypath: String? = nil) -> Promise { 107 | return typeCall(.delete, url: url, params: params, keypath: keypath) 108 | } 109 | 110 | private func typeCall(_ verb: WSHTTPVerb, 111 | url: String, params: Params = Params(), 112 | keypath: String? = nil) -> Promise { 113 | let c = defaultCall() 114 | c.httpVerb = verb 115 | c.URL = url 116 | c.params = params 117 | 118 | // Apply corresponding JSON mapper 119 | return c.fetch() 120 | .registerThen { (json: JSON) in 121 | Promise { (resolve, reject) in 122 | if let t: T = WSModelJSONParser().toModel(json, keypath: keypath 123 | ?? self.defaultObjectParsingKeyPath) { 124 | resolve(t) 125 | } else { 126 | reject(WSError.unableToParseResponse) 127 | } 128 | } 129 | } 130 | .resolveOnMainThread() 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /Sources/ws/WS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WS.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/11/15. 6 | // Copyright © 2015 s4cha. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import Arrow 11 | import Foundation 12 | import Then 13 | 14 | open class WS { 15 | 16 | /** 17 | Instead of using the same keypath for every call eg: "collection", 18 | this enables to use a default keypath for parsing collections. 19 | This is overidden by the per-request keypath if present. 20 | 21 | */ 22 | open var defaultCollectionParsingKeyPath: String? 23 | 24 | // Same but for ArrowInitializable objects 25 | open var defaultObjectParsingKeyPath: String? 26 | 27 | @available(*, unavailable, renamed:"defaultCollectionParsingKeyPath") 28 | open var jsonParsingColletionKey: String? 29 | 30 | /** 31 | Prints network calls to the console. 32 | Values Available are .None, Calls and CallsAndResponses. 33 | Default is None 34 | */ 35 | open var logLevels = WSLogLevel.off 36 | open var postParameterEncoding: ParameterEncoding = URLEncoding() 37 | 38 | /** 39 | Displays network activity indicator at the top left hand corner of the iPhone's screen in the status bar. 40 | Is shown by dafeult, set it to false to hide it. 41 | */ 42 | open var showsNetworkActivityIndicator = true 43 | 44 | /** 45 | Custom error handler block, to parse error returned in response body. 46 | For example: `{ error: { code: 1, message: "Server error" } }` 47 | */ 48 | open var errorHandler: ((JSON) -> Error?)? 49 | 50 | open var baseURL = "" 51 | open var headers = [String: String]() 52 | open var requestAdapter: RequestAdapter? 53 | open var requestRetrier: RequestRetrier? 54 | open var sessionManager: SessionManager? 55 | open var mandatoryQueryParams = Params() 56 | 57 | /** 58 | Create a webservice instance. 59 | @param Pass the base url of your webservice, E.g : "http://jsonplaceholder.typicode.com" 60 | 61 | */ 62 | public init(_ aBaseURL: String) { 63 | baseURL = aBaseURL 64 | } 65 | 66 | // MARK: - Calls 67 | 68 | internal func call(_ url: String, verb: WSHTTPVerb = .get, params: Params = Params()) -> WSRequest { 69 | let c = defaultCall() 70 | c.httpVerb = verb 71 | c.URL = url 72 | if mandatoryQueryParams.isEmpty { 73 | c.params = params 74 | } else { 75 | c.params = params.merging(mandatoryQueryParams) { (current, _) in current } 76 | } 77 | return c 78 | } 79 | 80 | open func defaultCall() -> WSRequest { 81 | let r = WSRequest() 82 | r.baseURL = baseURL 83 | r.logLevels = logLevels 84 | r.postParameterEncoding = postParameterEncoding 85 | r.showsNetworkActivityIndicator = showsNetworkActivityIndicator 86 | r.headers = headers 87 | r.requestAdapter = requestAdapter 88 | r.requestRetrier = requestRetrier 89 | r.sessionManager = sessionManager 90 | r.errorHandler = errorHandler 91 | return r 92 | } 93 | 94 | // MARK: JSON calls 95 | 96 | open func get(_ url: String, params: Params = Params()) -> Promise { 97 | return getRequest(url, params: params).fetch().resolveOnMainThread() 98 | } 99 | 100 | open func post(_ url: String, params: Params = Params()) -> Promise { 101 | return postRequest(url, params: params).fetch().resolveOnMainThread() 102 | } 103 | 104 | open func put(_ url: String, params: Params = Params()) -> Promise { 105 | return putRequest(url, params: params).fetch().resolveOnMainThread() 106 | } 107 | 108 | open func delete(_ url: String, params: Params = Params()) -> Promise { 109 | return deleteRequest(url, params: params).fetch().resolveOnMainThread() 110 | } 111 | 112 | // MARK: Void calls 113 | 114 | open func get(_ url: String, params: Params = Params()) -> Promise { 115 | let r = getRequest(url, params: params) 116 | r.returnsJSON = false 117 | return r.fetch().registerThen { (_: JSON) -> Void in }.resolveOnMainThread() 118 | } 119 | 120 | open func post(_ url: String, params: Params = Params()) -> Promise { 121 | let r = postRequest(url, params: params) 122 | r.returnsJSON = false 123 | return r.fetch().registerThen { (_:JSON) -> Void in }.resolveOnMainThread() 124 | } 125 | 126 | open func put(_ url: String, params: Params = Params()) -> Promise { 127 | let r = putRequest(url, params: params) 128 | r.returnsJSON = false 129 | return r.fetch().registerThen { (_:JSON) -> Void in }.resolveOnMainThread() 130 | } 131 | 132 | open func delete(_ url: String, params: Params = Params()) -> Promise { 133 | let r = deleteRequest(url, params: params) 134 | r.returnsJSON = false 135 | return r.fetch().registerThen { (_: JSON) -> Void in }.resolveOnMainThread() 136 | } 137 | 138 | // MARK: - Multipart 139 | 140 | open func postMultipart(_ url: String, 141 | params: Params = Params(), 142 | name: String, 143 | data: Data, 144 | fileName: String, 145 | mimeType: String) -> Promise { 146 | let r = postMultipartRequest(url, 147 | params: params, 148 | name: name, 149 | data: data, 150 | fileName: fileName, 151 | mimeType: mimeType) 152 | return r.fetch().resolveOnMainThread() 153 | } 154 | 155 | open func postMultipart(_ url: String, 156 | params: Params = Params(), 157 | multiParts: [WSMultiPartData]) -> Promise { 158 | let r = postMultipartRequest(url, 159 | params: params, 160 | multiParts: multiParts) 161 | return r.fetch().resolveOnMainThread() 162 | } 163 | 164 | open func putMultipart(_ url: String, 165 | params: Params = Params(), 166 | name: String, 167 | data: Data, 168 | fileName: String, 169 | mimeType: String) -> Promise { 170 | let r = putMultipartRequest(url, params: params, name: name, data: data, fileName: fileName, mimeType: mimeType) 171 | return r.fetch().resolveOnMainThread() 172 | } 173 | 174 | open func putMultipart(_ url: String, 175 | params: Params = Params(), 176 | multiParts: [WSMultiPartData]) -> Promise { 177 | let r = postMultipartRequest(url, params: params, multiParts: multiParts, verb: .put) 178 | return r.fetch().resolveOnMainThread() 179 | } 180 | 181 | open func patchMultipart(_ url: String, params: Params = Params(), multiParts: [WSMultiPartData]) -> Promise { 182 | let r = postMultipartRequest(url, params: params, multiParts: multiParts, verb: .patch) 183 | return r.fetch().resolveOnMainThread() 184 | } 185 | 186 | open func addMandatoryQueryParameter(key: String, value: Any) -> WS { 187 | mandatoryQueryParams[key] = value 188 | return self 189 | } 190 | 191 | open func addMandatoryQueryParameter(params: Params) -> WS { 192 | mandatoryQueryParams.merge(params) { (current, _) in current } 193 | return self 194 | } 195 | } 196 | 197 | public extension Promise { 198 | 199 | func resolveOnMainThread() -> Promise { 200 | return Promise { resolve, reject, progress in 201 | self.progress { p in 202 | DispatchQueue.main.async { 203 | progress(p) 204 | } 205 | } 206 | self.registerThen { t in 207 | DispatchQueue.main.async { 208 | resolve(t) 209 | } 210 | } 211 | self.onError { e in 212 | DispatchQueue.main.async { 213 | reject(e) 214 | } 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Sources/ws/WSError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSError.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Arrow 10 | import Foundation 11 | 12 | public struct WSError: Error { 13 | 14 | public enum Status: Int { 15 | case unknown = -1 16 | case networkUnreachable = 0 17 | 18 | case unableToParseResponse = 1 19 | 20 | // 4xx Client Error 21 | case badRequest = 400 22 | case unauthorized = 401 23 | case paymentRequired = 402 24 | case forbidden = 403 25 | case notFound = 404 26 | case methodNotAllowed = 405 27 | case notAcceptable = 406 28 | case proxyAuthenticationRequired = 407 29 | case requestTimeout = 408 30 | case conflict = 409 31 | case gone = 410 32 | case lengthRequired = 411 33 | case preconditionFailed = 412 34 | case payloadTooLarge = 413 35 | case uriTooLong = 414 36 | case unsupportedMediaType = 415 37 | case rangeNotSatisfiable = 416 38 | case expectationFailed = 417 39 | case teapot = 418 40 | case misdirectedRequest = 421 41 | case unprocessableEntity = 422 42 | case locked = 423 43 | case failedDependency = 424 44 | case upgradeRequired = 426 45 | case preconditionRequired = 428 46 | case tooManyRequests = 429 47 | case requestHeaderFieldsTooLarge = 431 48 | case unavailableForLegalReasons = 451 49 | 50 | // 4xx nginx 51 | case noResponse = 444 52 | case sslCertificateError = 495 53 | case sslCertificateRequired = 496 54 | case httpRequestSentToHTTPSPort = 497 55 | case clientClosedRequest = 499 56 | 57 | // 5xx Server Error 58 | case internalServerError = 500 59 | case notImplemented = 501 60 | case badGateway = 502 61 | case serviceUnavailable = 503 62 | case gatewayTimeout = 504 63 | case httpVersionNotSupported = 505 64 | case variantAlsoNegotiates = 506 65 | case insufficientStorage = 507 66 | case loopDetected = 508 67 | case notExtended = 510 68 | case networkAuthenticationRequired = 511 69 | } 70 | 71 | public var status: Status 72 | public var code: Int { return status.rawValue } 73 | 74 | public var jsonPayload: JSON? 75 | 76 | public var responseData: Data? 77 | 78 | public init(httpStatusCode: Int) { 79 | self.status = Status(rawValue: httpStatusCode) ?? .unknown 80 | } 81 | 82 | } 83 | 84 | extension WSError: CustomStringConvertible { 85 | 86 | public var description: String { 87 | return String(describing: self.status) 88 | .replacingOccurrences(of: "(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", 89 | with: " ", 90 | options: [.regularExpression]) 91 | .capitalized 92 | } 93 | 94 | } 95 | 96 | extension WSError { 97 | 98 | public static var unableToParseResponse: WSError { 99 | return WSError(httpStatusCode: Status.unableToParseResponse.rawValue) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Sources/ws/WSHTTPVerb.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSHTTPVerb.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum WSHTTPVerb: String { 12 | case get = "GET" 13 | case put = "PUT" 14 | case post = "POST" 15 | case patch = "PATCH" 16 | case delete = "DELETE" 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ws/WSLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSLogger.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/11/2016. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import Foundation 11 | 12 | public enum WSLogLevel { 13 | 14 | @available(*, unavailable, renamed: "off") 15 | case none 16 | @available(*, unavailable, renamed: "info") 17 | case calls 18 | @available(*, unavailable, renamed: "debug") 19 | case callsAndResponses 20 | 21 | case off 22 | case info 23 | case debug 24 | } 25 | 26 | class WSLogger { 27 | 28 | var logLevels = WSLogLevel.off 29 | 30 | func logMultipartRequest(_ request: WSRequest) { 31 | guard logLevels != .off else { 32 | return 33 | } 34 | print("\(request.httpVerb.rawValue.uppercased()) '\(request.URL)'") 35 | print(" params : \(request.params)") 36 | 37 | for (k, v) in request.headers { 38 | print(" \(k) : \(v)") 39 | } 40 | request.multiPartData.forEach { 41 | print(" name : \($0.multipartName)," 42 | + "mimeType: \($0.multipartMimeType), filename: \($0.multipartFileName)") 43 | } 44 | 45 | if logLevels == .debug { 46 | print() 47 | } 48 | } 49 | 50 | func logRequest(_ request: DataRequest) { 51 | guard logLevels != .off else { 52 | return 53 | } 54 | if let urlRequest = request.request, 55 | let verb = urlRequest.httpMethod, 56 | let url = urlRequest.url { 57 | print("\(verb) '\(url.absoluteString)'") 58 | logHeaders(urlRequest) 59 | logBody(urlRequest) 60 | if logLevels == .debug { 61 | print() 62 | } 63 | } 64 | } 65 | 66 | func logResponse(_ response: DefaultDataResponse) { 67 | guard logLevels != .off else { 68 | return 69 | } 70 | logStatusCodeAndURL(response.response) 71 | if logLevels == .debug { 72 | print() 73 | } 74 | } 75 | 76 | func logResponse(_ response: DataResponse) { 77 | guard logLevels != .off else { 78 | return 79 | } 80 | logStatusCodeAndURL(response.response) 81 | if logLevels == .debug { 82 | switch response.result { 83 | case .success(let value): 84 | print(value) 85 | case .failure(let error): 86 | print(error) 87 | } 88 | } 89 | if logLevels == .debug { 90 | print() 91 | } 92 | } 93 | 94 | private func logHeaders(_ urlRequest: URLRequest) { 95 | if let allHTTPHeaderFields = urlRequest.allHTTPHeaderFields { 96 | for (k, v) in allHTTPHeaderFields { 97 | print(" \(k) : \(v)") 98 | } 99 | } 100 | } 101 | 102 | private func logBody(_ urlRequest: URLRequest) { 103 | if let body = urlRequest.httpBody, 104 | let str = String(data: body, encoding: .utf8) { 105 | print(" HttpBody : \(str)") 106 | } 107 | } 108 | 109 | private func logStatusCodeAndURL(_ urlResponse: HTTPURLResponse?) { 110 | if let urlResponse = urlResponse, let url = urlResponse.url { 111 | print("\(urlResponse.statusCode) '\(url.absoluteString)'") 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/ws/WSModelJSONParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSModelJSONParser.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Arrow 10 | import Foundation 11 | 12 | open class WSModelJSONParser { 13 | 14 | public init() { } 15 | 16 | fileprivate func resourceData(from json: JSON, keypath: String?) -> JSON { 17 | if let k = keypath, !k.isEmpty, let j = json[k] { 18 | return j 19 | } 20 | return json 21 | } 22 | 23 | } 24 | 25 | extension WSModelJSONParser where T: ArrowInitializable { 26 | 27 | open func toModel(_ json: JSON, keypath: String? = nil) -> T? { 28 | return T.init(resourceData(from: json, keypath: keypath)) 29 | } 30 | 31 | open func toModels(_ json: JSON, keypath: String? = nil) -> [T]? { 32 | return [T].init(resourceData(from: json, keypath: keypath)) 33 | } 34 | 35 | } 36 | 37 | extension WSModelJSONParser where T: ArrowParsable { 38 | 39 | open func toModel(_ json: JSON, keypath: String? = nil) -> T { 40 | let data = resourceData(from: json, keypath: keypath) 41 | return resource(from: data) 42 | } 43 | 44 | open func toModels(_ json: JSON, keypath: String? = nil) -> [T] { 45 | guard let array = resourceData(from: json, keypath: keypath).collection else { 46 | return [T]() 47 | } 48 | return array.map { resource(from: $0) } 49 | } 50 | 51 | private func resource(from json: JSON) -> T { 52 | var t = T() 53 | t.deserialize(json) 54 | return t 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/ws/WSNetworkIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSNetworkIndicator.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 12/11/2016. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /** 13 | Abstracts network activity indicator management. 14 | - This only shows activity indicator for requests longer than 1 second, the loader is not shown for quick requests. 15 | - This also waits for 0.2 seconds before hiding the indicator in case other simultaneous requests 16 | occur in order to avoid flickering. 17 | 18 | */ 19 | class WSNetworkIndicator { 20 | 21 | static let shared = WSNetworkIndicator() 22 | private var runningRequests = 0 23 | 24 | func startRequest() { 25 | runningRequests += 1 26 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in 27 | self?.tick() 28 | } 29 | } 30 | 31 | func stopRequest() { 32 | runningRequests -= 1 33 | DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak self] in 34 | self?.tick() 35 | } 36 | } 37 | 38 | func tick() { 39 | let previousValue = UIApplication.shared.isNetworkActivityIndicatorVisible 40 | let newValue = (runningRequests > 0) 41 | if newValue != previousValue { 42 | UIApplication.shared.isNetworkActivityIndicatorVisible = newValue 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/ws/WSRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WSRequest.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 06/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import Arrow 11 | import Foundation 12 | import Then 13 | 14 | public struct WSMultiPartData { 15 | public var multipartData = Data() 16 | public var multipartName = "" 17 | public var multipartFileName = "photo.jpg" 18 | public var multipartMimeType = "image/*" 19 | 20 | public init() { 21 | } 22 | public init(multipartData: Data, 23 | multipartName: String, 24 | multipartFileName: String? = nil, 25 | multipartMimeType: String) { 26 | self.multipartData = multipartData 27 | self.multipartName = multipartName 28 | self.multipartFileName = multipartFileName ?? self.multipartFileName 29 | self.multipartMimeType = multipartMimeType 30 | } 31 | } 32 | 33 | open class WSRequest { 34 | 35 | var isMultipart = false 36 | var multiPartData = [WSMultiPartData]() 37 | 38 | open var baseURL = "" 39 | open var URL = "" 40 | open var httpVerb = WSHTTPVerb.get 41 | open var params = [String: Any]() 42 | open var returnsJSON = true 43 | open var headers = [String: String]() 44 | open var fullURL: String { return baseURL + URL } 45 | open var timeout: TimeInterval? 46 | open var logLevels: WSLogLevel { 47 | get { return logger.logLevels } 48 | set { logger.logLevels = newValue } 49 | } 50 | open var postParameterEncoding: ParameterEncoding = URLEncoding() 51 | open var showsNetworkActivityIndicator = true 52 | open var errorHandler: ((JSON) -> Error?)? 53 | open var requestAdapter: RequestAdapter? 54 | open var requestRetrier: RequestRetrier? 55 | open var sessionManager: SessionManager? 56 | 57 | private let logger = WSLogger() 58 | 59 | fileprivate var req: DataRequest?//Alamofire.Request? 60 | 61 | public init() {} 62 | 63 | open func cancel() { 64 | req?.cancel() 65 | } 66 | 67 | func buildRequest() -> URLRequest { 68 | let url = Foundation.URL(string: fullURL)! 69 | var r = URLRequest(url: url) 70 | r.httpMethod = httpVerb.rawValue 71 | for (key, value) in headers { 72 | r.setValue(value, forHTTPHeaderField: key) 73 | } 74 | if let t = timeout { 75 | r.timeoutInterval = t 76 | } 77 | 78 | var request: URLRequest? 79 | if httpVerb == .post || httpVerb == .put || httpVerb == .patch { 80 | request = try? postParameterEncoding.encode(r, with: params) 81 | } else { 82 | request = try? URLEncoding.default.encode(r, with: params) 83 | } 84 | return request ?? r 85 | } 86 | 87 | func wsSessionManager() -> SessionManager { 88 | let activeSessionManager = sessionManager ?? Alamofire.SessionManager.default 89 | if let adapter = requestAdapter { 90 | activeSessionManager.adapter = adapter 91 | } 92 | if let retrier = requestRetrier { 93 | activeSessionManager.retrier = retrier 94 | } 95 | return activeSessionManager 96 | } 97 | 98 | func wsRequest(_ urlRequest: URLRequestConvertible) -> DataRequest { 99 | return wsSessionManager().request(urlRequest) 100 | } 101 | 102 | func wsUpload( 103 | multipartFormData: @escaping (MultipartFormData) -> Void, 104 | usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold, 105 | with urlRequest: URLRequestConvertible, 106 | encodingCompletion: ((SessionManager.MultipartFormDataEncodingResult) -> Void)?) { 107 | return wsSessionManager().upload( 108 | multipartFormData: multipartFormData, 109 | usingThreshold: encodingMemoryThreshold, 110 | with: urlRequest, 111 | encodingCompletion: encodingCompletion 112 | ) 113 | } 114 | 115 | /// Returns Promise containing JSON 116 | open func fetch() -> Promise { 117 | return fetch().registerThen { (_, _, json) in json } 118 | } 119 | 120 | /// Returns Promise containing response status code, headers and parsed JSON 121 | open func fetch() -> Promise<(Int, [AnyHashable: Any], JSON)> { 122 | return Promise<(Int, [AnyHashable: Any], JSON)> { resolve, reject, progress in 123 | DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { 124 | if self.showsNetworkActivityIndicator { 125 | WSNetworkIndicator.shared.startRequest() 126 | } 127 | if self.isMultipart { 128 | self.sendMultipartRequest(resolve, reject: reject, progress: progress) 129 | } else if !self.returnsJSON { 130 | self.sendRequest(resolve, reject: reject) 131 | } else { 132 | self.sendJSONRequest(resolve, reject: reject) 133 | } 134 | } 135 | } 136 | } 137 | 138 | func sendMultipartRequest(_ resolve: @escaping (_ result: (Int, [AnyHashable: Any], JSON)) -> Void, 139 | reject: @escaping (_ error: Error) -> Void, 140 | progress:@escaping (Float) -> Void) { 141 | wsUpload(multipartFormData: { formData in 142 | for (key, value) in self.params { 143 | let str: String 144 | switch value { 145 | case let opt as Any?: 146 | if let v = opt { 147 | str = "\(v)" 148 | } else { 149 | continue 150 | } 151 | default: 152 | str = "\(value)" 153 | } 154 | if let data = str.data(using: .utf8) { 155 | formData.append(data, withName: key) 156 | } 157 | } 158 | self.multiPartData.forEach { data in 159 | formData.append(data.multipartData, 160 | withName: data.multipartName, 161 | fileName: data.multipartFileName, 162 | mimeType: data.multipartMimeType) 163 | } 164 | }, with: self.buildRequest(), 165 | encodingCompletion: { encodingResult in 166 | switch encodingResult { 167 | case .success(let upload, _, _): 168 | upload.uploadProgress { p in 169 | progress(Float(p.fractionCompleted)) 170 | }.validate().responseJSON { r in 171 | self.handleJSONResponse(r, resolve: resolve, reject: reject) 172 | } 173 | case .failure: () 174 | } 175 | }) 176 | logger.logMultipartRequest(self) 177 | } 178 | 179 | func sendRequest(_ resolve:@escaping (_ result: (Int, [AnyHashable: Any], JSON)) -> Void, 180 | reject: @escaping (_ error: Error) -> Void) { 181 | self.req = wsRequest(self.buildRequest()) 182 | logger.logRequest(self.req!) 183 | let bgQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default) 184 | req?.validate().response(queue: bgQueue) { response in 185 | WSNetworkIndicator.shared.stopRequest() 186 | self.logger.logResponse(response) 187 | if response.error == nil { 188 | resolve((response.response?.statusCode ?? 0, 189 | response.response?.allHeaderFields ?? [:], JSON(1 as AnyObject)!)) 190 | } else { 191 | self.rejectCallWithMatchingError(response.response, data: response.data, reject: reject) 192 | } 193 | } 194 | } 195 | 196 | func sendJSONRequest(_ resolve: @escaping (_ result: (Int, [AnyHashable: Any], JSON)) -> Void, 197 | reject: @escaping (_ error: Error) -> Void) { 198 | self.req = wsRequest(self.buildRequest()) 199 | logger.logRequest(self.req!) 200 | let bgQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default) 201 | req?.validate().responseJSON(queue: bgQueue) { r in 202 | self.handleJSONResponse(r, resolve: resolve, reject: reject) 203 | } 204 | } 205 | 206 | func handleJSONResponse(_ response: DataResponse, 207 | resolve: (_ result: (Int, [AnyHashable: Any], JSON)) -> Void, 208 | reject: (_ error: Error) -> Void) { 209 | WSNetworkIndicator.shared.stopRequest() 210 | logger.logResponse(response) 211 | switch response.result { 212 | case .success(let value): 213 | if let json: JSON = JSON(value as AnyObject?) { 214 | if let error = errorHandler?(json) { 215 | reject(error) 216 | return 217 | } 218 | resolve((response.response?.statusCode ?? 0, response.response?.allHeaderFields ?? [:], json)) 219 | } else { 220 | rejectCallWithMatchingError(response.response, data: response.data, reject: reject) 221 | } 222 | case .failure: 223 | rejectCallWithMatchingError(response.response, data: response.data, reject: reject) 224 | } 225 | } 226 | 227 | func rejectCallWithMatchingError(_ response: HTTPURLResponse?, 228 | data: Data? = nil, 229 | reject: (_ error: Error) -> Void) { 230 | var error = WSError(httpStatusCode: response?.statusCode ?? 0) 231 | error.responseData = data 232 | if let d = data, 233 | let json = try? JSONSerialization.jsonObject(with: d, 234 | options: JSONSerialization.ReadingOptions.allowFragments), 235 | let j = JSON(json as AnyObject?) { 236 | error.jsonPayload = j 237 | } 238 | reject(error as Error) 239 | } 240 | 241 | func methodForHTTPVerb(_ verb: WSHTTPVerb) -> HTTPMethod { 242 | switch verb { 243 | case .get: 244 | return .get 245 | case .post: 246 | return .post 247 | case .put: 248 | return .put 249 | case .patch: 250 | return .patch 251 | case .delete: 252 | return .delete 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /Sources/ws/ws.h: -------------------------------------------------------------------------------- 1 | // 2 | // ws.h 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/11/15. 6 | // Copyright © 2015 s4cha. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ws. 12 | FOUNDATION_EXPORT double wsVersionNumber; 13 | 14 | //! Project version string for ws. 15 | FOUNDATION_EXPORT const unsigned char wsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/wsTests/1px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freshOS/ws-deprecated/a44e643df48e1f1692e17075608b07d43a5dc407/Sources/wsTests/1px.jpg -------------------------------------------------------------------------------- /Sources/wsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/wsTests/Mapping.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mapping.swift 3 | // ws 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/04/16. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Arrow 10 | 11 | extension User: ArrowParsable { 12 | mutating func deserialize(_ json: JSON) { 13 | identifier <-- json["id"] 14 | username <-- json["username"] 15 | email <-- json["email"] 16 | name <-- json["name"] 17 | phone <-- json["phone"] 18 | website <-- json["website"] 19 | company <-- json["company"] 20 | address <-- json["address"] 21 | 22 | } 23 | } 24 | 25 | extension Company: ArrowParsable { 26 | mutating func deserialize(_ json: JSON) { 27 | bs <-- json["bs"] 28 | catchPhrase <-- json["catchPhrase"] 29 | name <-- json["name"] 30 | } 31 | } 32 | 33 | extension Address: ArrowParsable { 34 | mutating func deserialize(_ json: JSON) { 35 | city <-- json["city"] 36 | street <-- json["street"] 37 | zipcode <-- json["zipcode"] 38 | suite <-- json["suite"] 39 | geo <-- json["geo"] 40 | } 41 | } 42 | 43 | extension Geo: ArrowParsable { 44 | mutating func deserialize(_ json: JSON) { 45 | lat <-- json["lat"] 46 | lng <-- json["lng"] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/wsTests/mappingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mappingTests.swift 3 | // ws 4 | // 5 | // Created by Max Konovalov on 04/11/2016. 6 | // Copyright © 2016 s4cha. All rights reserved. 7 | // 8 | 9 | import Arrow 10 | import Then 11 | @testable import ws 12 | import XCTest 13 | 14 | struct Article { 15 | var id: Int = 0 16 | var name: String = "" 17 | } 18 | 19 | extension Article: ArrowParsable { 20 | mutating func deserialize(_ json: JSON) { 21 | id <-- json["id"] 22 | name <-- json["name"] 23 | } 24 | } 25 | 26 | enum FooBar: String { 27 | case foo = "Foo" 28 | case bar = "Bar" 29 | } 30 | 31 | extension FooBar: ArrowInitializable {} 32 | 33 | /** 34 | TEST JSON: 35 | { 36 | "count": 2, 37 | "articles": [ 38 | { 39 | "id": 1, 40 | "name": "Foo" 41 | }, 42 | { 43 | "id": 2, 44 | "name": "Bar" 45 | } 46 | ], 47 | "error": 48 | { 49 | "code": 0, 50 | "message": "No error" 51 | } 52 | } 53 | */ 54 | 55 | class MappingTests: XCTestCase { 56 | 57 | var ws: WS! 58 | 59 | private let path = "581c82711000003c24ea7806" 60 | 61 | override func setUp() { 62 | super.setUp() 63 | 64 | ws = WS("http://www.mocky.io/v2/") 65 | ws.logLevels = .debug 66 | 67 | ws.errorHandler = { json in 68 | if let errorPayload = json["error"] { 69 | var code = 0 70 | var message = "" 71 | code <-- errorPayload["code"] 72 | message <-- errorPayload["message"] 73 | if code != 0 { 74 | return NSError(domain: "WS", code: code, userInfo: [NSLocalizedDescriptionKey: message]) 75 | } 76 | } 77 | return nil 78 | } 79 | } 80 | 81 | func testMapping() { 82 | let e = expectation(description: "") 83 | 84 | getArticles() 85 | .then({ articles in 86 | XCTAssertEqual(articles.count, 2) 87 | e.fulfill() 88 | }) 89 | .onError({ error in 90 | print("ERROR: \(error)") 91 | XCTFail("Mapping fails") 92 | e.fulfill() 93 | }) 94 | 95 | waitForExpectations(timeout: 10, handler: nil) 96 | } 97 | 98 | func testTypeMapping() { 99 | let e = expectation(description: "") 100 | 101 | getArticlesCount() 102 | .then({ count in 103 | XCTAssertEqual(count, 2) 104 | e.fulfill() 105 | }) 106 | .onError({ error in 107 | print("ERROR: \(error)") 108 | XCTFail("Type Mapping Fails") 109 | e.fulfill() 110 | }) 111 | 112 | waitForExpectations(timeout: 10, handler: nil) 113 | } 114 | 115 | func testRawTypeMapping() { 116 | let e = expectation(description: "") 117 | 118 | getFooBar() 119 | .then({ foobar in 120 | XCTAssertEqual(foobar, FooBar.foo) 121 | e.fulfill() 122 | }) 123 | .onError({ error in 124 | print("ERROR: \(error)") 125 | XCTFail("Raw type mapping fails") 126 | e.fulfill() 127 | }) 128 | 129 | waitForExpectations(timeout: 10, handler: nil) 130 | } 131 | 132 | func getArticles() -> Promise<[Article]> { 133 | return ws.get(path, keypath: "articles") 134 | } 135 | 136 | func getArticlesCount() -> Promise { 137 | return ws.get(path, keypath: "count") 138 | } 139 | 140 | func getFooBar() -> Promise { 141 | return ws.get(path, keypath: "articles.0.name") 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /Sources/wsTests/wsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // wsTests.swift 3 | // wsTests 4 | // 5 | // Created by Sacha Durand Saint Omer on 13/11/15. 6 | // Copyright © 2015 s4cha. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import Arrow 11 | import Then 12 | @testable import ws 13 | import XCTest 14 | 15 | // MARK: - Models 16 | 17 | struct User { 18 | var identifier = 0 19 | var username = "" 20 | var email = "" 21 | var name = "" 22 | var phone = "" 23 | var website: NSURL? 24 | var company = Company() 25 | var address = Address() 26 | } 27 | 28 | //todo Does not work when useing forced ! 29 | 30 | struct Company { 31 | var bs = "" 32 | var catchPhrase = "" 33 | var name = "" 34 | } 35 | 36 | struct Address { 37 | var city = "" 38 | var street = "" 39 | var suite = "" 40 | var zipcode = "" 41 | var geo = Geo() 42 | } 43 | 44 | struct Geo { 45 | var lat = "" 46 | var lng = "" 47 | } 48 | 49 | // MARK: - Usage 50 | 51 | class WSTests: XCTestCase { 52 | 53 | var ws: WS! 54 | 55 | override func setUp() { 56 | super.setUp() 57 | // Create webservice with base URL 58 | ws = WS("http://jsonplaceholder.typicode.com") 59 | ws.logLevels = .debug 60 | ws.postParameterEncoding = JSONEncoding.default 61 | ws.showsNetworkActivityIndicator = false 62 | } 63 | 64 | func testJSON() { 65 | let exp = expectation(description: "") 66 | 67 | // use "call" to get back a json 68 | ws.get("/users").then { (_: JSON) in 69 | exp.fulfill() 70 | } 71 | waitForExpectations(timeout: 10, handler: nil) 72 | } 73 | 74 | func testModels() { 75 | let exp = expectation(description: "") 76 | latestUsers().then { users in 77 | XCTAssertEqual(users.count, 10) 78 | 79 | let u = users[0] 80 | XCTAssertEqual(u.identifier, 1) 81 | exp.fulfill() 82 | 83 | print(users) 84 | } 85 | waitForExpectations(timeout: 10, handler: nil) 86 | } 87 | 88 | func testResponse() { 89 | let exp = expectation(description: "") 90 | ws.getRequest("/users").fetch().then { (statusCode, _, _) in 91 | XCTAssertEqual(statusCode, 200) 92 | exp.fulfill() 93 | } 94 | waitForExpectations(timeout: 10, handler: nil) 95 | } 96 | 97 | // SPM doesn't support resources so this test fails under SPM 98 | // func testMultipart() { 99 | // let exp = expectation(description: "") 100 | // let wsFileIO = WS("https://file.io") 101 | // wsFileIO.logLevels = .debug 102 | // wsFileIO.postParameterEncoding = JSONEncoding.default 103 | // wsFileIO.showsNetworkActivityIndicator = false 104 | // 105 | // let imgPath = Bundle(for: type(of: self)).path(forResource: "1px", ofType: "jpg") 106 | // let img = UIImage(contentsOfFile: imgPath!) 107 | // let data = img!.jpegData(compressionQuality: 1.0)! 108 | // 109 | // wsFileIO.postMultipart("", name: "file", data: data, fileName: "file", mimeType: "image/jpeg").then { _ in 110 | // exp.fulfill() 111 | // }.onError { _ in 112 | // XCTFail("Posting multipart Fails") 113 | // } 114 | // waitForExpectations(timeout: 10, handler: nil) 115 | // } 116 | 117 | // Here is typically how you would define an api endpoint. 118 | // aka latestUsers is a GET on /users and I should get back User objects 119 | func latestUsers() -> Promise<[User]> { 120 | return ws.get("/users") 121 | } 122 | 123 | func testResolveOnMainThreadWorks() { 124 | let thenExp = expectation(description: "test") 125 | let finallyExp = expectation(description: "test") 126 | 127 | func fetch() -> Promise { 128 | return Promise { resolve, _ in 129 | resolve("Hello") 130 | } 131 | } 132 | 133 | fetch() 134 | .resolveOnMainThread() 135 | .then { data in 136 | print(data) 137 | thenExp.fulfill() 138 | }.onError { error in 139 | print(error) 140 | }.finally { 141 | finallyExp.fulfill() 142 | } 143 | 144 | waitForExpectations(timeout: 1, handler: nil) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freshOS/ws-deprecated/a44e643df48e1f1692e17075608b07d43a5dc407/banner.png -------------------------------------------------------------------------------- /ws.framework.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freshOS/ws-deprecated/a44e643df48e1f1692e17075608b07d43a5dc407/ws.framework.zip -------------------------------------------------------------------------------- /ws.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ws" 3 | s.version = "5.1.3" 4 | s.summary = "Elegant JSON WebService for Swift ☁️" 5 | s.homepage = "https://github.com/freshOS/ws" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = "S4cha" 8 | s.source = { :git => "https://github.com/freshOS/ws.git", :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/sachadso' 10 | s.ios.deployment_target = "9.0" 11 | s.source_files = "Sources/ws/*.{h,m,swift}" 12 | s.frameworks = "Foundation" 13 | s.dependency 'Arrow', '~> 5.1.1' 14 | s.dependency 'thenPromise', '~> 5.1.2' 15 | s.dependency 'Alamofire', '~> 4.9.1' 16 | s.description = "Elegant JSON WebService for Swift - Stop writing boilerplate JSON webservice code and focus on your awesome App instead" 17 | s.swift_versions = ['2', '3', '4', '4.1', '4.2', '5.0', '5.1', '5.1.3'] 18 | end 19 | -------------------------------------------------------------------------------- /ws.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 994E914523B1284C002F6D2B /* WSNetworkIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913A23B1284B002F6D2B /* WSNetworkIndicator.swift */; platformFilter = ios; }; 11 | 994E914623B1284C002F6D2B /* WS+TypedCalls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913B23B1284B002F6D2B /* WS+TypedCalls.swift */; platformFilter = ios; }; 12 | 994E914723B1284C002F6D2B /* WS+Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913C23B1284B002F6D2B /* WS+Requests.swift */; platformFilter = ios; }; 13 | 994E914823B1284C002F6D2B /* WSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913D23B1284B002F6D2B /* WSError.swift */; platformFilter = ios; }; 14 | 994E914923B1284C002F6D2B /* WSModelJSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913E23B1284B002F6D2B /* WSModelJSONParser.swift */; platformFilter = ios; }; 15 | 994E914A23B1284C002F6D2B /* WSHTTPVerb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E913F23B1284B002F6D2B /* WSHTTPVerb.swift */; platformFilter = ios; }; 16 | 994E914B23B1284C002F6D2B /* WSRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E914023B1284B002F6D2B /* WSRequest.swift */; platformFilter = ios; }; 17 | 994E914C23B1284C002F6D2B /* ws.h in Headers */ = {isa = PBXBuildFile; fileRef = 994E914123B1284B002F6D2B /* ws.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 994E914D23B1284C002F6D2B /* WS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E914223B1284C002F6D2B /* WS.swift */; platformFilter = ios; }; 19 | 994E914F23B1284C002F6D2B /* WSLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E914423B1284C002F6D2B /* WSLogger.swift */; platformFilter = ios; }; 20 | 994E916223B12B0D002F6D2B /* wsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E915D23B12B0D002F6D2B /* wsTests.swift */; }; 21 | 994E916323B12B0D002F6D2B /* mappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E915F23B12B0D002F6D2B /* mappingTests.swift */; }; 22 | 994E916423B12B0D002F6D2B /* Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994E916123B12B0D002F6D2B /* Mapping.swift */; }; 23 | 994E916523B12B4B002F6D2B /* 1px.jpg in CopyFiles */ = {isa = PBXBuildFile; fileRef = 994E915E23B12B0D002F6D2B /* 1px.jpg */; }; 24 | 996CAB9B1BF67A7C00931EAD /* ws.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 996CAB901BF67A7C00931EAD /* ws.framework */; }; 25 | 99A3FC02248D47FA006260BE /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A3FC01248D47FA006260BE /* Alamofire.framework */; }; 26 | 99A3FC04248D47FD006260BE /* Arrow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A3FC03248D47FD006260BE /* Arrow.framework */; }; 27 | 99A3FC06248D47FF006260BE /* Then.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99A3FC05248D47FF006260BE /* Then.framework */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 996CAB9C1BF67A7C00931EAD /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 996CAB871BF67A7C00931EAD /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 996CAB8F1BF67A7C00931EAD; 36 | remoteInfo = ws; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXCopyFilesBuildPhase section */ 41 | 99CB94E31CC3E4BD00F4E1FD /* CopyFiles */ = { 42 | isa = PBXCopyFilesBuildPhase; 43 | buildActionMask = 2147483647; 44 | dstPath = ""; 45 | dstSubfolderSpec = 7; 46 | files = ( 47 | 994E916523B12B4B002F6D2B /* 1px.jpg in CopyFiles */, 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXCopyFilesBuildPhase section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 994E913A23B1284B002F6D2B /* WSNetworkIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSNetworkIndicator.swift; path = Sources/ws/WSNetworkIndicator.swift; sourceTree = SOURCE_ROOT; }; 55 | 994E913B23B1284B002F6D2B /* WS+TypedCalls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "WS+TypedCalls.swift"; path = "Sources/ws/WS+TypedCalls.swift"; sourceTree = SOURCE_ROOT; }; 56 | 994E913C23B1284B002F6D2B /* WS+Requests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "WS+Requests.swift"; path = "Sources/ws/WS+Requests.swift"; sourceTree = SOURCE_ROOT; }; 57 | 994E913D23B1284B002F6D2B /* WSError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSError.swift; path = Sources/ws/WSError.swift; sourceTree = SOURCE_ROOT; }; 58 | 994E913E23B1284B002F6D2B /* WSModelJSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSModelJSONParser.swift; path = Sources/ws/WSModelJSONParser.swift; sourceTree = SOURCE_ROOT; }; 59 | 994E913F23B1284B002F6D2B /* WSHTTPVerb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSHTTPVerb.swift; path = Sources/ws/WSHTTPVerb.swift; sourceTree = SOURCE_ROOT; }; 60 | 994E914023B1284B002F6D2B /* WSRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSRequest.swift; path = Sources/ws/WSRequest.swift; sourceTree = SOURCE_ROOT; }; 61 | 994E914123B1284B002F6D2B /* ws.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ws.h; path = Sources/ws/ws.h; sourceTree = SOURCE_ROOT; }; 62 | 994E914223B1284C002F6D2B /* WS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WS.swift; path = Sources/ws/WS.swift; sourceTree = SOURCE_ROOT; }; 63 | 994E914323B1284C002F6D2B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Sources/ws/Info.plist; sourceTree = SOURCE_ROOT; }; 64 | 994E914423B1284C002F6D2B /* WSLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSLogger.swift; path = Sources/ws/WSLogger.swift; sourceTree = SOURCE_ROOT; }; 65 | 994E915D23B12B0D002F6D2B /* wsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = wsTests.swift; path = Sources/wsTests/wsTests.swift; sourceTree = SOURCE_ROOT; }; 66 | 994E915E23B12B0D002F6D2B /* 1px.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = 1px.jpg; path = Sources/wsTests/1px.jpg; sourceTree = SOURCE_ROOT; }; 67 | 994E915F23B12B0D002F6D2B /* mappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = mappingTests.swift; path = Sources/wsTests/mappingTests.swift; sourceTree = SOURCE_ROOT; }; 68 | 994E916023B12B0D002F6D2B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Sources/wsTests/Info.plist; sourceTree = SOURCE_ROOT; }; 69 | 994E916123B12B0D002F6D2B /* Mapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Mapping.swift; path = Sources/wsTests/Mapping.swift; sourceTree = SOURCE_ROOT; }; 70 | 996CAB901BF67A7C00931EAD /* ws.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ws.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 996CAB9A1BF67A7C00931EAD /* wsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = wsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 99A3FC01248D47FA006260BE /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = ""; }; 73 | 99A3FC03248D47FD006260BE /* Arrow.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Arrow.framework; path = Carthage/Build/iOS/Arrow.framework; sourceTree = ""; }; 74 | 99A3FC05248D47FF006260BE /* Then.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Then.framework; path = Carthage/Build/iOS/Then.framework; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 996CAB8C1BF67A7C00931EAD /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 99A3FC02248D47FA006260BE /* Alamofire.framework in Frameworks */, 83 | 99A3FC04248D47FD006260BE /* Arrow.framework in Frameworks */, 84 | 99A3FC06248D47FF006260BE /* Then.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 996CAB971BF67A7C00931EAD /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | 996CAB9B1BF67A7C00931EAD /* ws.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | 991774D223A399FC00FB36FE /* Frameworks */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 99A3FC05248D47FF006260BE /* Then.framework */, 103 | 99A3FC03248D47FD006260BE /* Arrow.framework */, 104 | 99A3FC01248D47FA006260BE /* Alamofire.framework */, 105 | ); 106 | name = Frameworks; 107 | sourceTree = ""; 108 | }; 109 | 996CAB861BF67A7C00931EAD = { 110 | isa = PBXGroup; 111 | children = ( 112 | 996CAB921BF67A7C00931EAD /* ws */, 113 | 996CAB9E1BF67A7C00931EAD /* wsTests */, 114 | 996CAB911BF67A7C00931EAD /* Products */, 115 | 991774D223A399FC00FB36FE /* Frameworks */, 116 | ); 117 | sourceTree = ""; 118 | }; 119 | 996CAB911BF67A7C00931EAD /* Products */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 996CAB901BF67A7C00931EAD /* ws.framework */, 123 | 996CAB9A1BF67A7C00931EAD /* wsTests.xctest */, 124 | ); 125 | name = Products; 126 | sourceTree = ""; 127 | }; 128 | 996CAB921BF67A7C00931EAD /* ws */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 994E914323B1284C002F6D2B /* Info.plist */, 132 | 994E914123B1284B002F6D2B /* ws.h */, 133 | 994E914223B1284C002F6D2B /* WS.swift */, 134 | 994E913C23B1284B002F6D2B /* WS+Requests.swift */, 135 | 994E913B23B1284B002F6D2B /* WS+TypedCalls.swift */, 136 | 994E913D23B1284B002F6D2B /* WSError.swift */, 137 | 994E913F23B1284B002F6D2B /* WSHTTPVerb.swift */, 138 | 994E914423B1284C002F6D2B /* WSLogger.swift */, 139 | 994E913E23B1284B002F6D2B /* WSModelJSONParser.swift */, 140 | 994E913A23B1284B002F6D2B /* WSNetworkIndicator.swift */, 141 | 994E914023B1284B002F6D2B /* WSRequest.swift */, 142 | ); 143 | path = ws; 144 | sourceTree = ""; 145 | }; 146 | 996CAB9E1BF67A7C00931EAD /* wsTests */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 994E915E23B12B0D002F6D2B /* 1px.jpg */, 150 | 994E916023B12B0D002F6D2B /* Info.plist */, 151 | 994E916123B12B0D002F6D2B /* Mapping.swift */, 152 | 994E915F23B12B0D002F6D2B /* mappingTests.swift */, 153 | 994E915D23B12B0D002F6D2B /* wsTests.swift */, 154 | ); 155 | path = wsTests; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXHeadersBuildPhase section */ 161 | 996CAB8D1BF67A7C00931EAD /* Headers */ = { 162 | isa = PBXHeadersBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | 994E914C23B1284C002F6D2B /* ws.h in Headers */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXHeadersBuildPhase section */ 170 | 171 | /* Begin PBXNativeTarget section */ 172 | 996CAB8F1BF67A7C00931EAD /* ws */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = 996CABA41BF67A7C00931EAD /* Build configuration list for PBXNativeTarget "ws" */; 175 | buildPhases = ( 176 | 9925B8D81E9FB07A002C081D /* ShellScript */, 177 | 996CAB8B1BF67A7C00931EAD /* Sources */, 178 | 996CAB8C1BF67A7C00931EAD /* Frameworks */, 179 | 996CAB8D1BF67A7C00931EAD /* Headers */, 180 | 996CAB8E1BF67A7C00931EAD /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = ws; 187 | productName = ws; 188 | productReference = 996CAB901BF67A7C00931EAD /* ws.framework */; 189 | productType = "com.apple.product-type.framework"; 190 | }; 191 | 996CAB991BF67A7C00931EAD /* wsTests */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = 996CABA71BF67A7C00931EAD /* Build configuration list for PBXNativeTarget "wsTests" */; 194 | buildPhases = ( 195 | 996CAB961BF67A7C00931EAD /* Sources */, 196 | 996CAB971BF67A7C00931EAD /* Frameworks */, 197 | 99CB94E31CC3E4BD00F4E1FD /* CopyFiles */, 198 | 991774E023A39BB000FB36FE /* ShellScript */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | 996CAB9D1BF67A7C00931EAD /* PBXTargetDependency */, 204 | ); 205 | name = wsTests; 206 | productName = wsTests; 207 | productReference = 996CAB9A1BF67A7C00931EAD /* wsTests.xctest */; 208 | productType = "com.apple.product-type.bundle.unit-test"; 209 | }; 210 | /* End PBXNativeTarget section */ 211 | 212 | /* Begin PBXProject section */ 213 | 996CAB871BF67A7C00931EAD /* Project object */ = { 214 | isa = PBXProject; 215 | attributes = { 216 | LastSwiftUpdateCheck = 0710; 217 | LastUpgradeCheck = 1020; 218 | ORGANIZATIONNAME = s4cha; 219 | TargetAttributes = { 220 | 996CAB8F1BF67A7C00931EAD = { 221 | CreatedOnToolsVersion = 7.1; 222 | LastSwiftMigration = 1130; 223 | }; 224 | 996CAB991BF67A7C00931EAD = { 225 | CreatedOnToolsVersion = 7.1; 226 | DevelopmentTeam = 4TATSTJ3J3; 227 | LastSwiftMigration = 1130; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 996CAB8A1BF67A7C00931EAD /* Build configuration list for PBXProject "ws" */; 232 | compatibilityVersion = "Xcode 3.2"; 233 | developmentRegion = en; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 996CAB861BF67A7C00931EAD; 240 | productRefGroup = 996CAB911BF67A7C00931EAD /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 996CAB8F1BF67A7C00931EAD /* ws */, 245 | 996CAB991BF67A7C00931EAD /* wsTests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 996CAB8E1BF67A7C00931EAD /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | /* End PBXResourcesBuildPhase section */ 259 | 260 | /* Begin PBXShellScriptBuildPhase section */ 261 | 991774E023A39BB000FB36FE /* ShellScript */ = { 262 | isa = PBXShellScriptBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | inputFileListPaths = ( 267 | ); 268 | inputPaths = ( 269 | "$(SRCROOT)/Carthage/Build/iOS/Arrow.framework", 270 | "$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework", 271 | "$(SRCROOT)/Carthage/Build/iOS/Then.framework", 272 | ); 273 | outputFileListPaths = ( 274 | ); 275 | outputPaths = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | shellPath = /bin/sh; 279 | shellScript = "/usr/local/bin/carthage copy-frameworks\n"; 280 | }; 281 | 9925B8D81E9FB07A002C081D /* ShellScript */ = { 282 | isa = PBXShellScriptBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | inputPaths = ( 287 | ); 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 996CAB8B1BF67A7C00931EAD /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 994E914B23B1284C002F6D2B /* WSRequest.swift in Sources */, 302 | 994E914823B1284C002F6D2B /* WSError.swift in Sources */, 303 | 994E914723B1284C002F6D2B /* WS+Requests.swift in Sources */, 304 | 994E914623B1284C002F6D2B /* WS+TypedCalls.swift in Sources */, 305 | 994E914923B1284C002F6D2B /* WSModelJSONParser.swift in Sources */, 306 | 994E914F23B1284C002F6D2B /* WSLogger.swift in Sources */, 307 | 994E914D23B1284C002F6D2B /* WS.swift in Sources */, 308 | 994E914A23B1284C002F6D2B /* WSHTTPVerb.swift in Sources */, 309 | 994E914523B1284C002F6D2B /* WSNetworkIndicator.swift in Sources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | 996CAB961BF67A7C00931EAD /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 994E916423B12B0D002F6D2B /* Mapping.swift in Sources */, 318 | 994E916323B12B0D002F6D2B /* mappingTests.swift in Sources */, 319 | 994E916223B12B0D002F6D2B /* wsTests.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXTargetDependency section */ 326 | 996CAB9D1BF67A7C00931EAD /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = 996CAB8F1BF67A7C00931EAD /* ws */; 329 | targetProxy = 996CAB9C1BF67A7C00931EAD /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | 996CABA21BF67A7C00931EAD /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_EMPTY_BODY = YES; 350 | CLANG_WARN_ENUM_CONVERSION = YES; 351 | CLANG_WARN_INFINITE_RECURSION = YES; 352 | CLANG_WARN_INT_CONVERSION = YES; 353 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 355 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 358 | CLANG_WARN_STRICT_PROTOTYPES = YES; 359 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 360 | CLANG_WARN_UNREACHABLE_CODE = YES; 361 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | COPY_PHASE_STRIP = NO; 364 | CURRENT_PROJECT_VERSION = 1; 365 | DEBUG_INFORMATION_FORMAT = dwarf; 366 | ENABLE_STRICT_OBJC_MSGSEND = YES; 367 | ENABLE_TESTABILITY = YES; 368 | GCC_C_LANGUAGE_STANDARD = gnu99; 369 | GCC_DYNAMIC_NO_PIC = NO; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_OPTIMIZATION_LEVEL = 0; 372 | GCC_PREPROCESSOR_DEFINITIONS = ( 373 | "DEBUG=1", 374 | "$(inherited)", 375 | ); 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 383 | MTL_ENABLE_DEBUG_INFO = YES; 384 | ONLY_ACTIVE_ARCH = YES; 385 | SDKROOT = iphoneos; 386 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | VERSIONING_SYSTEM = "apple-generic"; 390 | VERSION_INFO_PREFIX = ""; 391 | }; 392 | name = Debug; 393 | }; 394 | 996CABA31BF67A7C00931EAD /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_COMMA = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INFINITE_RECURSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNREACHABLE_CODE = YES; 421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 422 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 423 | COPY_PHASE_STRIP = NO; 424 | CURRENT_PROJECT_VERSION = 1; 425 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 426 | ENABLE_NS_ASSERTIONS = NO; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_NO_COMMON_BLOCKS = YES; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 437 | MTL_ENABLE_DEBUG_INFO = NO; 438 | SDKROOT = iphoneos; 439 | SWIFT_VERSION = 5.0; 440 | TARGETED_DEVICE_FAMILY = "1,2"; 441 | VALIDATE_PRODUCT = YES; 442 | VERSIONING_SYSTEM = "apple-generic"; 443 | VERSION_INFO_PREFIX = ""; 444 | }; 445 | name = Release; 446 | }; 447 | 996CABA51BF67A7C00931EAD /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | CLANG_ENABLE_MODULES = YES; 451 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 452 | DEFINES_MODULE = YES; 453 | DYLIB_COMPATIBILITY_VERSION = 1; 454 | DYLIB_CURRENT_VERSION = 1; 455 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 456 | FRAMEWORK_SEARCH_PATHS = ( 457 | "$(inherited)", 458 | "$(PROJECT_DIR)/Carthage/Build/iOS", 459 | ); 460 | INFOPLIST_FILE = Sources/ws/Info.plist; 461 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 462 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/Frameworks", 466 | "@loader_path/Frameworks", 467 | ); 468 | MARKETING_VERSION = 5.1.3; 469 | PRODUCT_BUNDLE_IDENTIFIER = com.freshOS.ws; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SKIP_INSTALL = YES; 472 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 473 | SWIFT_VERSION = 5.0; 474 | }; 475 | name = Debug; 476 | }; 477 | 996CABA61BF67A7C00931EAD /* Release */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | CLANG_ENABLE_MODULES = YES; 481 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 482 | DEFINES_MODULE = YES; 483 | DYLIB_COMPATIBILITY_VERSION = 1; 484 | DYLIB_CURRENT_VERSION = 1; 485 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 486 | FRAMEWORK_SEARCH_PATHS = ( 487 | "$(inherited)", 488 | "$(PROJECT_DIR)/Carthage/Build/iOS", 489 | ); 490 | INFOPLIST_FILE = Sources/ws/Info.plist; 491 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 492 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 493 | LD_RUNPATH_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "@executable_path/Frameworks", 496 | "@loader_path/Frameworks", 497 | ); 498 | MARKETING_VERSION = 5.1.3; 499 | PRODUCT_BUNDLE_IDENTIFIER = com.freshOS.ws; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SKIP_INSTALL = YES; 502 | SWIFT_COMPILATION_MODE = wholemodule; 503 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 504 | SWIFT_VERSION = 5.0; 505 | }; 506 | name = Release; 507 | }; 508 | 996CABA81BF67A7C00931EAD /* Debug */ = { 509 | isa = XCBuildConfiguration; 510 | buildSettings = { 511 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 512 | CLANG_ENABLE_MODULES = YES; 513 | DEVELOPMENT_TEAM = 4TATSTJ3J3; 514 | FRAMEWORK_SEARCH_PATHS = ( 515 | "$(inherited)", 516 | "$(PROJECT_DIR)/Carthage/Build/iOS", 517 | ); 518 | INFOPLIST_FILE = Sources/wsTests/Info.plist; 519 | LD_RUNPATH_SEARCH_PATHS = ( 520 | "$(inherited)", 521 | "@executable_path/Frameworks", 522 | "@loader_path/Frameworks", 523 | ); 524 | PRODUCT_BUNDLE_IDENTIFIER = com.s4cha.ws.wsTests; 525 | PRODUCT_NAME = "$(TARGET_NAME)"; 526 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 527 | SWIFT_VERSION = 5.0; 528 | }; 529 | name = Debug; 530 | }; 531 | 996CABA91BF67A7C00931EAD /* Release */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 535 | CLANG_ENABLE_MODULES = YES; 536 | DEVELOPMENT_TEAM = 4TATSTJ3J3; 537 | FRAMEWORK_SEARCH_PATHS = ( 538 | "$(inherited)", 539 | "$(PROJECT_DIR)/Carthage/Build/iOS", 540 | ); 541 | INFOPLIST_FILE = Sources/wsTests/Info.plist; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | "@loader_path/Frameworks", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = com.s4cha.ws.wsTests; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_COMPILATION_MODE = wholemodule; 550 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 551 | SWIFT_VERSION = 5.0; 552 | }; 553 | name = Release; 554 | }; 555 | /* End XCBuildConfiguration section */ 556 | 557 | /* Begin XCConfigurationList section */ 558 | 996CAB8A1BF67A7C00931EAD /* Build configuration list for PBXProject "ws" */ = { 559 | isa = XCConfigurationList; 560 | buildConfigurations = ( 561 | 996CABA21BF67A7C00931EAD /* Debug */, 562 | 996CABA31BF67A7C00931EAD /* Release */, 563 | ); 564 | defaultConfigurationIsVisible = 0; 565 | defaultConfigurationName = Release; 566 | }; 567 | 996CABA41BF67A7C00931EAD /* Build configuration list for PBXNativeTarget "ws" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 996CABA51BF67A7C00931EAD /* Debug */, 571 | 996CABA61BF67A7C00931EAD /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 996CABA71BF67A7C00931EAD /* Build configuration list for PBXNativeTarget "wsTests" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 996CABA81BF67A7C00931EAD /* Debug */, 580 | 996CABA91BF67A7C00931EAD /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | /* End XCConfigurationList section */ 586 | }; 587 | rootObject = 996CAB871BF67A7C00931EAD /* Project object */; 588 | } 589 | -------------------------------------------------------------------------------- /ws.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ws.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ws.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ws.xcodeproj/project.xcworkspace/xcshareddata/ws.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "2A207DD5A356A0668BACACCC100AE1BFFD91CEFF", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "5402AE07EF7C555479E4B1DC63F4D8FF55EB313D" : 9223372036854775807, 8 | "2A207DD5A356A0668BACACCC100AE1BFFD91CEFF" : 9223372036854775807, 9 | "A1D8B8AF49F5F0DD934D5F0F98DC49AB75F411B7" : 9223372036854775807, 10 | "67620B5EFA902936DF04070AF595B76AB0333747" : 9223372036854775807 11 | }, 12 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "295A74BC-66F8-475E-AB64-27982C3B16EB", 13 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 14 | "5402AE07EF7C555479E4B1DC63F4D8FF55EB313D" : "ws\/Carthage\/Checkouts\/Arrow\/", 15 | "2A207DD5A356A0668BACACCC100AE1BFFD91CEFF" : "ws\/", 16 | "A1D8B8AF49F5F0DD934D5F0F98DC49AB75F411B7" : "ws\/Carthage\/Checkouts\/then\/", 17 | "67620B5EFA902936DF04070AF595B76AB0333747" : "ws\/Carthage\/Checkouts\/Alamofire\/" 18 | }, 19 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ws", 20 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 21 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ws.xcodeproj", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 23 | { 24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/freshOS\/ws.git", 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "2A207DD5A356A0668BACACCC100AE1BFFD91CEFF" 27 | }, 28 | { 29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:freshOS\/Arrow.git", 30 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 31 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5402AE07EF7C555479E4B1DC63F4D8FF55EB313D" 32 | }, 33 | { 34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Alamofire\/Alamofire.git", 35 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 36 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "67620B5EFA902936DF04070AF595B76AB0333747" 37 | }, 38 | { 39 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:freshOS\/then.git", 40 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 41 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "A1D8B8AF49F5F0DD934D5F0F98DC49AB75F411B7" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /ws.xcodeproj/xcshareddata/xcschemes/ws.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | --------------------------------------------------------------------------------