├── .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 | 
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 | [](https://developer.apple.com/swift)
10 | 
11 | [](https://github.com/Carthage/Carthage)
12 | [](https://cocoapods.org)
13 | [](https://github.com/freshOS/ws/blob/master/LICENSE)
14 | [](https://app.bitrise.io/app/a6d157138f9ee86d)
15 | [](https://codebeat.co/projects/github-com-freshos-ws)
16 | 
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 |
--------------------------------------------------------------------------------