├── README.md
├── Gister.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── Gister
├── Gister.xcdatamodeld
│ ├── .xccurrentversion
│ └── Gister.xcdatamodel
│ │ └── contents
├── Scenes
│ └── ListGists
│ │ ├── ListGistsModels.swift
│ │ ├── ListGistsWorker.swift
│ │ ├── ListGistsPresenter.swift
│ │ ├── ListGistsInteractor.swift
│ │ ├── ListGistsRouter.swift
│ │ └── ListGistsViewController.swift
├── APIs
│ ├── Gist.swift
│ └── GistAPI.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── AppDelegate.swift
├── GisterTests
├── Gist.json
├── Info.plist
├── GisterTests.swift
├── Seeds.swift
├── Scenes
│ └── ListGists
│ │ ├── ListGistsPresenterTests.swift
│ │ ├── ListGistsViewControllerTests.swift
│ │ ├── ListGistsWorkerTests.swift
│ │ └── ListGistsInteractorTests.swift
└── APIs
│ └── GistAPITest.swift
├── GisterUITests
├── Info.plist
└── GisterUITests.swift
└── .gitignore
/README.md:
--------------------------------------------------------------------------------
1 | # Gister
2 | This app lists all public gists using GitHub's API. It demonstrates how to encapsulate all API code and write unit tests for it.
3 |
--------------------------------------------------------------------------------
/Gister.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Gister/Gister.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | Gister.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Gister.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Gister/Gister.xcdatamodeld/Gister.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/GisterTests/Gist.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "owner": {
4 | "login": "amy"
5 | },
6 | "files": {
7 | "amy.txt": {
8 | "filename": "amy.txt",
9 | "type": "text/plain"
10 | }
11 | },
12 | "html_url": "https://gist.github.com/rayvinly/27e1cc51efc3a1015a1e"
13 | },
14 | {
15 | "owner": {
16 | "login": "bob"
17 | },
18 | "files": {
19 | "bob.html": {
20 | "filename": "bob.html",
21 | "type": "text/html"
22 | }
23 | },
24 | "html_url": "https://gist.github.com/rayvinly/46396696f020c3e0931c"
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/GisterTests/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 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/GisterUITests/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 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsModels.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsModels.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | enum ListGists
16 | {
17 | // MARK: Use cases
18 |
19 | enum FetchGists
20 | {
21 | struct Request
22 | {
23 | }
24 | struct Response
25 | {
26 | var gists: [Gist]
27 | }
28 | struct ViewModel
29 | {
30 | struct DisplayedGist
31 | {
32 | var login: String
33 | var url: String
34 | var filename: String
35 | var filetype: String
36 | }
37 | var displayedGists: [DisplayedGist]
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/GisterTests/GisterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GisterTests.swift
3 | // GisterTests
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Gister
11 |
12 | class GisterTests: XCTestCase
13 | {
14 | override func setUp()
15 | {
16 | super.setUp()
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | }
19 |
20 | override func tearDown()
21 | {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | super.tearDown()
24 | }
25 |
26 | func testExample()
27 | {
28 | // This is an example of a functional test case.
29 | // Use XCTAssert and related functions to verify your tests produce the correct results.
30 | }
31 |
32 | func testPerformanceExample()
33 | {
34 | // This is an example of a performance test case.
35 | self.measure {
36 | // Put the code you want to measure the time of here.
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsWorker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsWorker.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | protocol ListGistsWorkerDelegate
16 | {
17 | func listGistsWorker(listGistsWorker: ListGistsWorker, didFetchGists gists: [Gist])
18 | }
19 |
20 | class ListGistsWorker: GistAPIDelegate
21 | {
22 | var gistAPI: GistAPIProtocol = GistAPI()
23 | var delegate: ListGistsWorkerDelegate?
24 |
25 | // MARK: Block implementation
26 |
27 | func fetch(completionHandler: @escaping ([Gist]) -> Void)
28 | {
29 | gistAPI.fetch { (gists) in
30 | completionHandler(gists)
31 | }
32 | }
33 |
34 | // MARK: Delegate implementation
35 |
36 | func fetch()
37 | {
38 | gistAPI.delegate = self
39 | gistAPI.fetch()
40 | }
41 |
42 | func gistAPI(gistAPI: GistAPIProtocol, didFetchGists gists: [Gist])
43 | {
44 | delegate?.listGistsWorker(listGistsWorker: self, didFetchGists: gists)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Gister/APIs/Gist.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gist.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/21/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Gist
12 | {
13 | var login: String
14 | var url: String
15 | var filename: String
16 | var filetype: String
17 | }
18 |
19 | extension Gist: Equatable
20 | {
21 | static func ==(lhs: Gist, rhs: Gist) -> Bool
22 | {
23 | return lhs.login == rhs.login &&
24 | lhs.url == rhs.url &&
25 | lhs.filename == rhs.filename &&
26 | lhs.filetype == rhs.filetype
27 | }
28 | }
29 |
30 | extension Gist: Hashable
31 | {
32 | var hashValue: Int
33 | {
34 | return login.hashValue
35 | }
36 | }
37 |
38 | extension ListGists.FetchGists.ViewModel.DisplayedGist: Equatable
39 | {
40 | static func ==(lhs: ListGists.FetchGists.ViewModel.DisplayedGist, rhs: ListGists.FetchGists.ViewModel.DisplayedGist) -> Bool
41 | {
42 | return lhs.login == rhs.login &&
43 | lhs.url == rhs.url &&
44 | lhs.filename == rhs.filename &&
45 | lhs.filetype == rhs.filetype
46 | }
47 | }
48 |
49 | extension ListGists.FetchGists.ViewModel.DisplayedGist: Hashable
50 | {
51 | var hashValue: Int
52 | {
53 | return login.hashValue
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsPresenter.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | protocol ListGistsPresentationLogic
16 | {
17 | func presentFetchedGists(response: ListGists.FetchGists.Response)
18 | }
19 |
20 | class ListGistsPresenter: ListGistsPresentationLogic
21 | {
22 | weak var viewController: ListGistsDisplayLogic?
23 |
24 | // MARK: Fetch Gists
25 |
26 | func presentFetchedGists(response: ListGists.FetchGists.Response)
27 | {
28 | let displayedGists = convertGists(gists: response.gists)
29 | let viewModel = ListGists.FetchGists.ViewModel(displayedGists: displayedGists)
30 | viewController?.displayFetchedGists(viewModel: viewModel)
31 | }
32 |
33 | private func convertGists(gists: [Gist]) -> [ListGists.FetchGists.ViewModel.DisplayedGist]
34 | {
35 | return gists.map { ListGists.FetchGists.ViewModel.DisplayedGist(login: $0.login, url: $0.url, filename: $0.filename, filetype: $0.filetype) }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GisterUITests/GisterUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GisterUITests.swift
3 | // GisterUITests
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class GisterUITests: XCTestCase
12 | {
13 | override func setUp()
14 | {
15 | super.setUp()
16 |
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 |
19 | // In UI tests it is usually best to stop immediately when a failure occurs.
20 | continueAfterFailure = false
21 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
22 | XCUIApplication().launch()
23 |
24 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
25 | }
26 |
27 | override func tearDown()
28 | {
29 | // Put teardown code here. This method is called after the invocation of each test method in the class.
30 | super.tearDown()
31 | }
32 |
33 | func testExample()
34 | {
35 | // Use recording to get started writing UI tests.
36 | // Use XCTAssert and related functions to verify your tests produce the correct results.
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Gister/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/GisterTests/Seeds.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Seeds.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/20/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | @testable import Gister
10 | import XCTest
11 |
12 | struct Seeds
13 | {
14 | struct Gists
15 | {
16 | static let text = Gist(login: "amy", url: "https://gist.github.com/rayvinly/27e1cc51efc3a1015a1e", filename: "amy.txt", filetype: "text/plain")
17 | static let html = Gist(login: "bob", url: "https://gist.github.com/rayvinly/46396696f020c3e0931c", filename: "bob.html", filetype: "text/html")
18 | }
19 |
20 | struct DisplayedGists
21 | {
22 | static let text = ListGists.FetchGists.ViewModel.DisplayedGist(login: Gists.text.login, url: Gists.text.url, filename: Gists.text.filename, filetype: Gists.text.filetype)
23 | static let html = ListGists.FetchGists.ViewModel.DisplayedGist(login: Gists.html.login, url: Gists.html.url, filename: Gists.html.filename, filetype: Gists.html.filetype)
24 | }
25 |
26 | struct JSON {
27 | static let data: Data =
28 | {
29 | let bundle = Bundle(identifier: "com.clean-swift.GisterTests")!
30 | let path = bundle.path(forResource: "Gist", ofType: "json")!
31 | let data = FileManager.default.contents(atPath: path)!
32 | let json = try! JSONSerialization.jsonObject(with: data, options: .allowFragments)
33 | return data
34 | }()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Gister/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsInteractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsInteractor.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | protocol ListGistsBusinessLogic
16 | {
17 | func fetchGists(request: ListGists.FetchGists.Request)
18 | }
19 |
20 | protocol ListGistsDataStore
21 | {
22 | }
23 |
24 | class ListGistsInteractor: ListGistsBusinessLogic, ListGistsDataStore, ListGistsWorkerDelegate
25 | {
26 | var presenter: ListGistsPresentationLogic?
27 | var listGistsWorker = ListGistsWorker()
28 |
29 | enum AsyncOpKind {
30 | case block, delegate
31 | }
32 | var asyncOpKind = AsyncOpKind.block
33 |
34 | // MARK: Fetch Gists
35 |
36 | func fetchGists(request: ListGists.FetchGists.Request)
37 | {
38 | switch asyncOpKind
39 | {
40 | case .block:
41 | // MARK: Block implementation
42 | listGistsWorker.fetch { (gists) in
43 | let response = ListGists.FetchGists.Response(gists: gists)
44 | self.presenter?.presentFetchedGists(response: response)
45 | }
46 | case .delegate:
47 | // MARK: Delegate method implementation
48 | listGistsWorker.delegate = self
49 | listGistsWorker.fetch()
50 | }
51 | }
52 |
53 | func listGistsWorker(listGistsWorker: ListGistsWorker, didFetchGists gists: [Gist])
54 | {
55 | let response = ListGists.FetchGists.Response(gists: gists)
56 | presenter?.presentFetchedGists(response: response)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
--------------------------------------------------------------------------------
/Gister/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsRouter.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | @objc protocol ListGistsRoutingLogic
16 | {
17 | //func routeToSomewhere(segue: UIStoryboardSegue?)
18 | }
19 |
20 | protocol ListGistsDataPassing
21 | {
22 | var dataStore: ListGistsDataStore? { get }
23 | }
24 |
25 | class ListGistsRouter: NSObject, ListGistsRoutingLogic, ListGistsDataPassing
26 | {
27 | weak var viewController: ListGistsViewController?
28 | var dataStore: ListGistsDataStore?
29 |
30 | // MARK: Routing
31 |
32 | //func routeToSomewhere(segue: UIStoryboardSegue?)
33 | //{
34 | // if let segue = segue {
35 | // let destinationVC = segue.destination as! SomewhereViewController
36 | // var destinationDS = destinationVC.router!.dataStore!
37 | // passDataToSomewhere(source: dataStore!, destination: &destinationDS)
38 | // } else {
39 | // let storyboard = UIStoryboard(name: "Main", bundle: nil)
40 | // let destinationVC = storyboard.instantiateViewController(withIdentifier: "SomewhereViewController") as! SomewhereViewController
41 | // var destinationDS = destinationVC.router!.dataStore!
42 | // passDataToSomewhere(source: dataStore!, destination: &destinationDS)
43 | // navigateToSomewhere(source: viewController!, destination: destinationVC)
44 | // }
45 | //}
46 |
47 | // MARK: Navigation
48 |
49 | //func navigateToSomewhere(source: ListGistsViewController, destination: SomewhereViewController)
50 | //{
51 | // source.show(destination, sender: nil)
52 | //}
53 |
54 | // MARK: Passing data
55 |
56 | //func passDataToSomewhere(source: ListGistsDataStore, destination: inout SomewhereDataStore)
57 | //{
58 | // destination.name = source.name
59 | //}
60 | }
61 |
--------------------------------------------------------------------------------
/GisterTests/Scenes/ListGists/ListGistsPresenterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsPresenterTests.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | @testable import Gister
14 | import XCTest
15 |
16 | class ListGistsPresenterTests: XCTestCase
17 | {
18 | // MARK: Subject under test
19 |
20 | var sut: ListGistsPresenter!
21 |
22 | // MARK: Test lifecycle
23 |
24 | override func setUp()
25 | {
26 | super.setUp()
27 | setupListGistsPresenter()
28 | }
29 |
30 | override func tearDown()
31 | {
32 | super.tearDown()
33 | }
34 |
35 | // MARK: Test setup
36 |
37 | func setupListGistsPresenter()
38 | {
39 | sut = ListGistsPresenter()
40 | }
41 |
42 | // MARK: Test doubles
43 |
44 | class ListGistsDisplayLogicSpy: ListGistsDisplayLogic
45 | {
46 | var displayFetchedGistsCalled = false
47 | var displayFetchedGistsViewModel: ListGists.FetchGists.ViewModel!
48 |
49 | func displayFetchedGists(viewModel: ListGists.FetchGists.ViewModel)
50 | {
51 | displayFetchedGistsCalled = true
52 | displayFetchedGistsViewModel = viewModel
53 | }
54 | }
55 |
56 | // MARK: Tests
57 |
58 | func testPresentFetchedGistsShouldAskViewControllerToDisplayGists()
59 | {
60 | // Given
61 | let listGistsDisplayLogicSpy = ListGistsDisplayLogicSpy()
62 | sut.viewController = listGistsDisplayLogicSpy
63 |
64 | // When
65 | let gists = [Seeds.Gists.text, Seeds.Gists.html]
66 | let response = ListGists.FetchGists.Response(gists: gists)
67 | sut.presentFetchedGists(response: response)
68 |
69 | // Then
70 | let expectedGists = [Seeds.DisplayedGists.text, Seeds.DisplayedGists.html]
71 | let actualGists = listGistsDisplayLogicSpy.displayFetchedGistsViewModel.displayedGists
72 | XCTAssertTrue(listGistsDisplayLogicSpy.displayFetchedGistsCalled, "presentFetchedGists(response:) should ask the view controller to display gists")
73 | XCTAssertEqual(actualGists, expectedGists, "presentFetchedGists(response:) should display the correct gists")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Gister/Scenes/ListGists/ListGistsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsViewController.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | import UIKit
14 |
15 | protocol ListGistsDisplayLogic: class
16 | {
17 | func displayFetchedGists(viewModel: ListGists.FetchGists.ViewModel)
18 | }
19 |
20 | class ListGistsViewController: UITableViewController, ListGistsDisplayLogic
21 | {
22 | var interactor: ListGistsBusinessLogic?
23 | var router: (NSObjectProtocol & ListGistsRoutingLogic & ListGistsDataPassing)?
24 |
25 | // MARK: Object lifecycle
26 |
27 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
28 | {
29 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
30 | setup()
31 | }
32 |
33 | required init?(coder aDecoder: NSCoder)
34 | {
35 | super.init(coder: aDecoder)
36 | setup()
37 | }
38 |
39 | // MARK: Setup
40 |
41 | private func setup()
42 | {
43 | let viewController = self
44 | let interactor = ListGistsInteractor()
45 | let presenter = ListGistsPresenter()
46 | let router = ListGistsRouter()
47 | viewController.interactor = interactor
48 | viewController.router = router
49 | interactor.presenter = presenter
50 | presenter.viewController = viewController
51 | router.viewController = viewController
52 | router.dataStore = interactor
53 | }
54 |
55 | // MARK: Routing
56 |
57 | override func prepare(for segue: UIStoryboardSegue, sender: Any?)
58 | {
59 | if let scene = segue.identifier {
60 | let selector = NSSelectorFromString("routeTo\(scene)WithSegue:")
61 | if let router = router, router.responds(to: selector) {
62 | router.perform(selector, with: segue)
63 | }
64 | }
65 | }
66 |
67 | // MARK: View lifecycle
68 |
69 | override func viewDidLoad()
70 | {
71 | super.viewDidLoad()
72 | fetchGists()
73 | }
74 |
75 | // MARK: Table view
76 |
77 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
78 | {
79 | return displayedGists.count
80 | }
81 |
82 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
83 | {
84 | let displayedGist = displayedGists[indexPath.row]
85 | var cell = tableView.dequeueReusableCell(withIdentifier: "GistCell")
86 | if cell == nil {
87 | cell = UITableViewCell(style: .value1, reuseIdentifier: "GistCell")
88 | }
89 | cell?.textLabel?.text = displayedGist.login
90 | cell?.detailTextLabel?.text = displayedGist.filetype
91 | return cell!
92 | }
93 |
94 | // MARK: Fetch Gists
95 |
96 | var displayedGists: [ListGists.FetchGists.ViewModel.DisplayedGist] = []
97 |
98 | @IBAction func refreshButtonTapped(_ sender: Any)
99 | {
100 | fetchGists()
101 | }
102 |
103 | func fetchGists()
104 | {
105 | let request = ListGists.FetchGists.Request()
106 | interactor?.fetchGists(request: request)
107 | }
108 |
109 | func displayFetchedGists(viewModel: ListGists.FetchGists.ViewModel)
110 | {
111 | displayedGists = viewModel.displayedGists
112 | tableView.reloadData()
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/GisterTests/Scenes/ListGists/ListGistsViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsViewControllerTests.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | @testable import Gister
14 | import XCTest
15 |
16 | class ListGistsViewControllerTests: XCTestCase
17 | {
18 | // MARK: Subject under test
19 |
20 | var sut: ListGistsViewController!
21 | var window: UIWindow!
22 |
23 | // MARK: Test lifecycle
24 |
25 | override func setUp()
26 | {
27 | super.setUp()
28 | window = UIWindow()
29 | setupListGistsViewController()
30 | }
31 |
32 | override func tearDown()
33 | {
34 | window = nil
35 | super.tearDown()
36 | }
37 |
38 | // MARK: Test setup
39 |
40 | func setupListGistsViewController()
41 | {
42 | let bundle = Bundle.main
43 | let storyboard = UIStoryboard(name: "Main", bundle: bundle)
44 | sut = storyboard.instantiateViewController(withIdentifier: "ListGistsViewController") as! ListGistsViewController
45 | }
46 |
47 | func loadView()
48 | {
49 | window.addSubview(sut.view)
50 | RunLoop.current.run(until: Date())
51 | }
52 |
53 | // MARK: Test doubles
54 |
55 | class TableViewSpy: UITableView
56 | {
57 | // MARK: Method call expectations
58 |
59 | var reloadDataCalled = false
60 |
61 | // MARK: Spied methods
62 |
63 | override func reloadData()
64 | {
65 | reloadDataCalled = true
66 | }
67 | }
68 |
69 | class ListGistsBusinessLogicSpy: ListGistsBusinessLogic
70 | {
71 | var fetchGistsCalled = false
72 |
73 | func fetchGists(request: ListGists.FetchGists.Request)
74 | {
75 | fetchGistsCalled = true
76 | }
77 | }
78 |
79 | // MARK: Tests
80 |
81 | func testShouldFetchGistsWhenViewIsLoaded()
82 | {
83 | // Given
84 | let listGistsBusinessLogicSpy = ListGistsBusinessLogicSpy()
85 | sut.interactor = listGistsBusinessLogicSpy
86 |
87 | // When
88 | loadView()
89 |
90 | // Then
91 | XCTAssertTrue(listGistsBusinessLogicSpy.fetchGistsCalled, "viewDidLoad() should ask the interactor to fetch gists")
92 | }
93 |
94 | func testShouldFetchGistsWhenRefreshButtonIsTapped()
95 | {
96 | // Given
97 | let listGistsBusinessLogicSpy = ListGistsBusinessLogicSpy()
98 | sut.interactor = listGistsBusinessLogicSpy
99 | loadView()
100 |
101 | // When
102 | sut.refreshButtonTapped(sut)
103 |
104 | // Then
105 | XCTAssertTrue(listGistsBusinessLogicSpy.fetchGistsCalled, "viewDidLoad() should ask the interactor to fetch gists")
106 | }
107 |
108 | func testDisplayFetchedGistsShouldReloadTableView()
109 | {
110 | // Given
111 | let tableViewSpy = TableViewSpy()
112 | sut.tableView = tableViewSpy
113 | loadView()
114 |
115 | // When
116 | let expectedGists = [Seeds.DisplayedGists.text, Seeds.DisplayedGists.html]
117 | let viewModel = ListGists.FetchGists.ViewModel(displayedGists: expectedGists)
118 | sut.displayFetchedGists(viewModel: viewModel)
119 |
120 | // Then
121 | let actualGists = sut.displayedGists
122 | XCTAssertEqual(actualGists, expectedGists, "displayFetchedGists(viewModel:) should display the gists results")
123 | XCTAssert(tableViewSpy.reloadDataCalled, "displayFetchedGists(viewModel:) should reload the table view")
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Gister/APIs/GistAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GistAPI.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol GistAPIProtocol
12 | {
13 | func fetch(completionHandler: @escaping ([Gist]) -> Void)
14 | func fetch()
15 | var delegate: GistAPIDelegate? { get set }
16 | }
17 |
18 | protocol GistAPIDelegate
19 | {
20 | func gistAPI(gistAPI: GistAPIProtocol, didFetchGists gists: [Gist])
21 | }
22 |
23 | class GistAPI: NSObject, GistAPIProtocol, URLSessionDataDelegate, URLSessionTaskDelegate
24 | {
25 | var delegate: GistAPIDelegate?
26 | lazy var session: URLSession = {
27 | let config = URLSessionConfiguration.ephemeral
28 | if let delegate = self.delegate {
29 | return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
30 | } else {
31 | return URLSession(configuration: config)
32 | }
33 | }()
34 | private var dataTask: URLSessionDataTask!
35 | var results = [String: NSMutableData]()
36 | // Reference: https://developer.github.com/v3/gists
37 | private let url = URL(string: "https://api.github.com/gists/public")!
38 |
39 | // MARK: - Block implementation
40 |
41 | func fetch(completionHandler: @escaping ([Gist]) -> Void)
42 | {
43 | dataTask = session.dataTask(with: url) { (data, response, error) in
44 | if let data = data {
45 | let json = try! JSONSerialization.jsonObject(with: data, options: .allowFragments)
46 | let gists = self.parseJSONToGists(json: json)
47 | DispatchQueue.main.async {
48 | completionHandler(gists)
49 | }
50 | } else {
51 | DispatchQueue.main.async {
52 | completionHandler([])
53 | }
54 | }
55 | }
56 | dataTask.resume()
57 | }
58 |
59 | // MARK: - Delegate implementation
60 |
61 | func fetch()
62 | {
63 | dataTask = session.dataTask(with: url)
64 | dataTask.resume()
65 | }
66 |
67 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
68 | {
69 | let key = String(dataTask.taskIdentifier)
70 | var result = results[key]
71 | if result == nil {
72 | result = NSMutableData(data: data)
73 | results[key] = result
74 | } else {
75 | result?.append(data)
76 | }
77 | }
78 |
79 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
80 | {
81 | if let _ = error {
82 | delegate?.gistAPI(gistAPI: self, didFetchGists: [])
83 | } else {
84 | let key = String(task.taskIdentifier)
85 | if let result = results[key] as Data? {
86 | let json = try! JSONSerialization.jsonObject(with: result, options: .allowFragments)
87 | results[key] = nil
88 | let gists = self.parseJSONToGists(json: json)
89 | delegate?.gistAPI(gistAPI: self, didFetchGists: gists)
90 | } else {
91 | delegate?.gistAPI(gistAPI: self, didFetchGists: [])
92 | }
93 | }
94 | }
95 |
96 | // MARK: - Parsing Gist JSON
97 |
98 | private func parseJSONToGists(json: Any) -> [Gist]
99 | {
100 | var gists = [Gist]()
101 |
102 | if let array = json as? [Any] {
103 | for hash in array {
104 |
105 | var login: String?
106 | var url: String?
107 | var filename: String?
108 | var filetype: String?
109 |
110 | if let hash = hash as? [String: Any] {
111 |
112 | if let owner = hash["owner"] as? [String: Any] {
113 | login = owner["login"] as? String
114 | } else {
115 | login = "no user"
116 | }
117 |
118 | url = hash["html_url"] as? String
119 |
120 | if let files = hash["files"] as? [String: Any]{
121 | if let file = files.first?.value as? [String: Any] {
122 | filename = file["filename"] as? String
123 | filetype = file["type"] as? String
124 | }
125 | }
126 | }
127 |
128 | if let login = login, let url = url, let filename = filename, let filetype = filetype {
129 | let gist = Gist(login: login, url: url, filename: filename, filetype: filetype)
130 | gists.append(gist)
131 | }
132 | }
133 | }
134 |
135 | return gists
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/GisterTests/Scenes/ListGists/ListGistsWorkerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsWorkerTests.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | @testable import Gister
14 | import XCTest
15 |
16 | class ListGistsWorkerTests: XCTestCase
17 | {
18 | // MARK: Subject under test
19 |
20 | var sut: ListGistsWorker!
21 |
22 | // MARK: Test lifecycle
23 |
24 | override func setUp()
25 | {
26 | super.setUp()
27 | setupListGistsWorker()
28 | }
29 |
30 | override func tearDown()
31 | {
32 | super.tearDown()
33 | }
34 |
35 | // MARK: Test setup
36 |
37 | func setupListGistsWorker()
38 | {
39 | sut = ListGistsWorker()
40 | }
41 |
42 | // MARK: Test doubles
43 |
44 | class GistAPISpy: GistAPIProtocol
45 | {
46 | let gists = [Seeds.Gists.text, Seeds.Gists.html]
47 |
48 | var fetchWithCompletionHandlerCalled = false
49 | var fetchWithDelegateCalled = false
50 | var delegate: GistAPIDelegate?
51 |
52 | func fetch(completionHandler: @escaping ([Gist]) -> Void)
53 | {
54 | fetchWithCompletionHandlerCalled = true
55 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
56 | completionHandler(self.gists)
57 | }
58 | }
59 |
60 | func fetch()
61 | {
62 | fetchWithDelegateCalled = true
63 | }
64 | }
65 |
66 | class ListGistsWorkerDelegateSpy: ListGistsWorkerDelegate
67 | {
68 | var listGistsWorkerDidFetchGistsCalled = false
69 | var listGistsWorkerDidFetchGistsResults = [Gist]()
70 |
71 | func listGistsWorker(listGistsWorker: ListGistsWorker, didFetchGists gists: [Gist])
72 | {
73 | listGistsWorkerDidFetchGistsCalled = true
74 | listGistsWorkerDidFetchGistsResults = gists
75 | }
76 | }
77 |
78 | // MARK: Tests
79 |
80 | // MARK: Block implementation
81 |
82 | func testFetchShouldAskGistAPIToFetchGistsWithBlock()
83 | {
84 | // Given
85 | let gistAPISpy = GistAPISpy()
86 | sut.gistAPI = gistAPISpy
87 |
88 | // When
89 | sut.fetch { (gists) in }
90 |
91 | // Then
92 | XCTAssertTrue(gistAPISpy.fetchWithCompletionHandlerCalled, "fetch(completionHandler:) should ask Gist API to fetch gists")
93 | }
94 |
95 | func testFetchShouldReturnGistsResultsToBlock()
96 | {
97 | // Given
98 | let gistAPISpy = GistAPISpy()
99 | sut.gistAPI = gistAPISpy
100 |
101 | // When
102 | var actualGists: [Gist]?
103 | let fetchCompleted = expectation(description: "Wait for fetch to complete")
104 | sut.fetch { (gists) in
105 | actualGists = gists
106 | fetchCompleted.fulfill()
107 | }
108 | waitForExpectations(timeout: 5.0, handler: nil)
109 |
110 | // Then
111 | let expectedGists = gistAPISpy.gists
112 | XCTAssertEqual(actualGists!, expectedGists, "fetch(completionHandler:) should return an array of gists to completion block if the fetch succeeds")
113 | }
114 |
115 | // MARK: Delegate implementation
116 |
117 | func testFetchShouldAskGistAPIToFetchGistsWithDelegate()
118 | {
119 | // Given
120 | let gistAPISpy = GistAPISpy()
121 | sut.gistAPI = gistAPISpy
122 |
123 | // When
124 | sut.fetch()
125 |
126 | // Then
127 | XCTAssertTrue(gistAPISpy.fetchWithDelegateCalled, "fetch(completionHandler:) should ask Gist API to fetch gists")
128 | }
129 |
130 | func testGistAPIDidFetchGistsShouldNotifyDelegateWithGistsResults()
131 | {
132 | // Given
133 | let gistAPISpy = GistAPISpy()
134 | sut.gistAPI = gistAPISpy
135 | let listGistsWorkerDelegateSpy = ListGistsWorkerDelegateSpy()
136 | sut.delegate = listGistsWorkerDelegateSpy
137 |
138 | // When
139 | let gists = [Seeds.Gists.text, Seeds.Gists.html]
140 | sut.gistAPI(gistAPI: gistAPISpy, didFetchGists: gists)
141 |
142 | // Then
143 | let expectedGists = gistAPISpy.gists
144 | let actualGists = listGistsWorkerDelegateSpy.listGistsWorkerDidFetchGistsResults
145 | XCTAssertTrue(listGistsWorkerDelegateSpy.listGistsWorkerDidFetchGistsCalled, "fetch(completionHandler:) should notify its delegate")
146 | XCTAssertEqual(actualGists, expectedGists, "fetch(completionHandler:) should return an array of gists if the fetch succeeds")
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Gister/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreData
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate
14 | {
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
18 | {
19 | // Override point for customization after application launch.
20 | return true
21 | }
22 |
23 | func applicationWillResignActive(_ application: UIApplication)
24 | {
25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
27 | }
28 |
29 | func applicationDidEnterBackground(_ application: UIApplication)
30 | {
31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
33 | }
34 |
35 | func applicationWillEnterForeground(_ application: UIApplication)
36 | {
37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 | func applicationDidBecomeActive(_ application: UIApplication)
41 | {
42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
43 | }
44 |
45 | func applicationWillTerminate(_ application: UIApplication)
46 | {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | // Saves changes in the application's managed object context before the application terminates.
49 | self.saveContext()
50 | }
51 |
52 | // MARK: - Core Data stack
53 |
54 | lazy var persistentContainer: NSPersistentContainer = {
55 | /*
56 | The persistent container for the application. This implementation
57 | creates and returns a container, having loaded the store for the
58 | application to it. This property is optional since there are legitimate
59 | error conditions that could cause the creation of the store to fail.
60 | */
61 | let container = NSPersistentContainer(name: "Gister")
62 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
63 | if let error = error as NSError? {
64 | // Replace this implementation with code to handle the error appropriately.
65 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
66 |
67 | /*
68 | Typical reasons for an error here include:
69 | * The parent directory does not exist, cannot be created, or disallows writing.
70 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
71 | * The device is out of space.
72 | * The store could not be migrated to the current model version.
73 | Check the error message to determine what the actual problem was.
74 | */
75 | fatalError("Unresolved error \(error), \(error.userInfo)")
76 | }
77 | })
78 | return container
79 | }()
80 |
81 | // MARK: - Core Data Saving support
82 |
83 | func saveContext ()
84 | {
85 | let context = persistentContainer.viewContext
86 | if context.hasChanges {
87 | do {
88 | try context.save()
89 | } catch {
90 | // Replace this implementation with code to handle the error appropriately.
91 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
92 | let nserror = error as NSError
93 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/GisterTests/Scenes/ListGists/ListGistsInteractorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListGistsInteractorTests.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright (c) 2017 Clean Swift LLC. All rights reserved.
7 | //
8 | // This file was generated by the Clean Swift Xcode Templates so
9 | // you can apply clean architecture to your iOS and Mac projects,
10 | // see http://clean-swift.com
11 | //
12 |
13 | @testable import Gister
14 | import XCTest
15 |
16 | class ListGistsInteractorTests: XCTestCase
17 | {
18 | // MARK: Subject under test
19 |
20 | var sut: ListGistsInteractor!
21 |
22 | // MARK: Test lifecycle
23 |
24 | override func setUp()
25 | {
26 | super.setUp()
27 | setupListGistsInteractor()
28 | }
29 |
30 | override func tearDown()
31 | {
32 | super.tearDown()
33 | }
34 |
35 | // MARK: Test setup
36 |
37 | func setupListGistsInteractor()
38 | {
39 | sut = ListGistsInteractor()
40 | }
41 |
42 | // MARK: Test doubles
43 |
44 | class ListGistsWorkerSpy: ListGistsWorker
45 | {
46 | let gists = [Seeds.Gists.text, Seeds.Gists.html]
47 |
48 | var fetchWithCompletionHandlerCalled = false
49 | var fetchWithDelegateCalled = false
50 |
51 | override func fetch(completionHandler: @escaping ([Gist]) -> Void)
52 | {
53 | fetchWithCompletionHandlerCalled = true
54 | completionHandler(gists)
55 | }
56 |
57 | override func fetch()
58 | {
59 | fetchWithDelegateCalled = true
60 | delegate?.listGistsWorker(listGistsWorker: self, didFetchGists: gists)
61 | }
62 | }
63 |
64 | class ListGistsPresentationLogicSpy: ListGistsPresentationLogic
65 | {
66 | var presentFetchedGistsCalled = false
67 |
68 | func presentFetchedGists(response: ListGists.FetchGists.Response)
69 | {
70 | presentFetchedGistsCalled = true
71 | }
72 | }
73 |
74 | // MARK: Tests
75 |
76 | // MARK: Block implementation
77 |
78 | func testFetchGistsShouldAskWorkerToFetchGistsWithBlock()
79 | {
80 | guard sut.asyncOpKind == .block else { return }
81 |
82 | // Given
83 | let listGistsWorkerSpy = ListGistsWorkerSpy()
84 | sut.listGistsWorker = listGistsWorkerSpy
85 |
86 | // When
87 | let request = ListGists.FetchGists.Request()
88 | sut.fetchGists(request: request)
89 |
90 | // Then
91 | XCTAssertTrue(listGistsWorkerSpy.fetchWithCompletionHandlerCalled, "fetchGists(request:) should ask the worker to fetch gists")
92 | }
93 |
94 | func testFetchGistsShouldAskPresenterToFormatGists()
95 | {
96 | // Given
97 | let listGistsWorkerSpy = ListGistsWorkerSpy()
98 | sut.listGistsWorker = listGistsWorkerSpy
99 | let listGistsPresentationLogicSpy = ListGistsPresentationLogicSpy()
100 | sut.presenter = listGistsPresentationLogicSpy
101 |
102 | // When
103 | let request = ListGists.FetchGists.Request()
104 | sut.fetchGists(request: request)
105 |
106 | // Then
107 | XCTAssertTrue(listGistsPresentationLogicSpy.presentFetchedGistsCalled, "fetchGists(request:) should ask the presenter to format gists")
108 | }
109 |
110 | // MARK: Delegate implementation
111 |
112 | func testFetchGistsShouldAskWorkerToFetchGistsWithDelegate()
113 | {
114 | guard sut.asyncOpKind == .delegate else { return }
115 |
116 | // Given
117 | let listGistsWorkerSpy = ListGistsWorkerSpy()
118 | sut.listGistsWorker = listGistsWorkerSpy
119 |
120 | // When
121 | let request = ListGists.FetchGists.Request()
122 | sut.fetchGists(request: request)
123 |
124 | // Then
125 | XCTAssertTrue(listGistsWorkerSpy.fetchWithDelegateCalled, "fetchGists(request:) should ask the worker to fetch gists")
126 | XCTAssertNotNil(sut.listGistsWorker.delegate, "fetchGists(request:) should set itself to be the delegate to be notified of fetch results")
127 | }
128 |
129 | func testListGistsWorkerDidFetchGistsShouldAskPresenterToFormatGists()
130 | {
131 | // Given
132 | let listGistsWorkerSpy = ListGistsWorkerSpy()
133 | sut.listGistsWorker = listGistsWorkerSpy
134 | let listGistsPresentationLogicSpy = ListGistsPresentationLogicSpy()
135 | sut.presenter = listGistsPresentationLogicSpy
136 |
137 | // When
138 | sut.listGistsWorker(listGistsWorker: listGistsWorkerSpy, didFetchGists: listGistsWorkerSpy.gists)
139 |
140 | // Then
141 | XCTAssertTrue(listGistsPresentationLogicSpy.presentFetchedGistsCalled, "listGistsWorker(listGistsWorker:didFetchGists:) should ask the presenter to format gists")
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/Gister/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/GisterTests/APIs/GistAPITest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GistAPITest.swift
3 | // Gister
4 | //
5 | // Created by Raymond Law on 10/12/17.
6 | // Copyright © 2017 Clean Swift LLC. All rights reserved.
7 | //
8 |
9 | @testable import Gister
10 | import XCTest
11 |
12 | // MARK: Test doubles
13 |
14 | class URLSessionSpy: URLSession
15 | {
16 | var dataTaskWithURLCalled = false
17 | var dataTask = URLSessionDataTaskSpy()
18 | var data: Data?
19 |
20 | override func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
21 | {
22 | dataTaskWithURLCalled = true
23 | completionHandler(data, nil, nil)
24 | return dataTask
25 | }
26 |
27 | override func dataTask(with url: URL) -> URLSessionDataTask
28 | {
29 | dataTaskWithURLCalled = true
30 | return dataTask
31 | }
32 | }
33 |
34 | class URLSessionDataTaskSpy: URLSessionDataTask
35 | {
36 | var resumeCalled = false
37 | override var taskIdentifier: Int { return 0 }
38 |
39 | override func resume()
40 | {
41 | resumeCalled = true
42 | }
43 | }
44 |
45 | class GistAPIDelegateSpy: GistAPIDelegate
46 | {
47 | var gistAPIDidFetchGistsCalled = false
48 | var gistAPIDidFetchGistsResults: [Gist]?
49 |
50 | func gistAPI(gistAPI: GistAPIProtocol, didFetchGists gists: [Gist])
51 | {
52 | gistAPIDidFetchGistsCalled = true
53 | gistAPIDidFetchGistsResults = gists
54 | }
55 | }
56 |
57 | class GistAPITest: XCTestCase
58 | {
59 | // MARK: Subject under test
60 |
61 | var sut: GistAPI!
62 |
63 | // MARK: Test lifecycle
64 |
65 | override func setUp()
66 | {
67 | super.setUp()
68 | setupGistAPI()
69 | }
70 |
71 | override func tearDown()
72 | {
73 | super.tearDown()
74 | }
75 |
76 | // MARK: Test setup
77 |
78 | func setupGistAPI()
79 | {
80 | sut = GistAPI()
81 | }
82 |
83 | // MARK: Tests
84 |
85 | // MARK: - Block implementation
86 |
87 | func testFetchShouldAskURLSessionToFetchGistsFromGitHubWithBlock()
88 | {
89 | // Given
90 | let sessionSpy = URLSessionSpy()
91 | sut.session = sessionSpy
92 |
93 | // When
94 | let fetchCompleted = expectation(description: "Wait for fetch to complete")
95 | sut.fetch { (gists) in
96 | fetchCompleted.fulfill()
97 | }
98 | waitForExpectations(timeout: 5.0, handler: nil)
99 |
100 | // Then
101 | XCTAssertTrue(sessionSpy.dataTaskWithURLCalled, "fetch(completionHandler:) should ask URLSession to fetch gists from GitHub")
102 | XCTAssertTrue(sessionSpy.dataTask.resumeCalled, "fetch(completionHandler:) should start the data task")
103 | }
104 |
105 | func testFetchShouldParseJSONToGists()
106 | {
107 | // Given
108 | let sessionSpy = URLSessionSpy()
109 | sut.session = sessionSpy
110 | sessionSpy.data = Seeds.JSON.data
111 |
112 | // When
113 | var actualGists: [Gist]?
114 | let fetchCompleted = expectation(description: "Wait for fetch to complete")
115 | sut.fetch { (gists) in
116 | actualGists = gists
117 | fetchCompleted.fulfill()
118 | }
119 | waitForExpectations(timeout: 5.0, handler: nil)
120 |
121 | // Then
122 | let expectedGists = [Seeds.Gists.text, Seeds.Gists.html]
123 | XCTAssertEqual(actualGists!, expectedGists, "fetch(completionHandler:) should return an array of gists if the fetch succeeds")
124 | }
125 |
126 | func testFetchShouldFailGracefully()
127 | {
128 | // Given
129 | let sessionSpy = URLSessionSpy()
130 | sut.session = sessionSpy
131 | sessionSpy.data = nil
132 |
133 | // When
134 | var actualGists: [Gist]?
135 | let fetchCompleted = expectation(description: "Wait for fetch to complete")
136 | sut.fetch { (gists) in
137 | actualGists = gists
138 | fetchCompleted.fulfill()
139 | }
140 | waitForExpectations(timeout: 5.0, handler: nil)
141 |
142 | // Then
143 | let expectedGists = [Gist]()
144 | XCTAssertEqual(actualGists!, expectedGists, "fetch(completionHandler:) should return an empty array if the fetch fails")
145 | }
146 |
147 | // MARK: - Delegate implementation
148 |
149 | func testFetchShouldAskURLSessionToFetchGistsFromGitHubWithDelegate()
150 | {
151 | // Given
152 | let sessionSpy = URLSessionSpy()
153 | sut.session = sessionSpy
154 |
155 | // When
156 | sut.fetch()
157 |
158 | // Then
159 | XCTAssertTrue(sessionSpy.dataTaskWithURLCalled, "fetch(completionHandler:) should ask URLSession to fetch gists from GitHub")
160 | XCTAssertTrue(sessionSpy.dataTask.resumeCalled, "fetch(completionHandler:) should start the data task")
161 | }
162 |
163 | func testURLSessionTaskDidCompleteWithErrorShouldNotifyDelegateWithGistsResults()
164 | {
165 | // Given
166 | let sessionSpy = URLSessionSpy()
167 | sut.session = sessionSpy
168 | let gistAPIDelegateSpy = GistAPIDelegateSpy()
169 | sut.delegate = gistAPIDelegateSpy
170 | let dataTaskSpy = URLSessionDataTaskSpy()
171 | let key = String(dataTaskSpy.taskIdentifier)
172 | sut.results = [key: NSMutableData(data: Seeds.JSON.data)]
173 |
174 | // When
175 | sut.urlSession(sessionSpy, task: dataTaskSpy, didCompleteWithError: nil)
176 |
177 | // Then
178 | let actualGists = gistAPIDelegateSpy.gistAPIDidFetchGistsResults!
179 | let expectedGists = [Seeds.Gists.text, Seeds.Gists.html]
180 | XCTAssertTrue(gistAPIDelegateSpy.gistAPIDidFetchGistsCalled, "urlSession(_:task:didCompleteWithError:) should notify the delegate")
181 | XCTAssertEqual(actualGists, expectedGists, "urlSession(_:task:didCompleteWithError:) should return the correct gists results")
182 | }
183 |
184 | func testURLSessionTaskDidCompleteWithErrorShouldFailGracefully()
185 | {
186 | // Given
187 | let sessionSpy = URLSessionSpy()
188 | sut.session = sessionSpy
189 | let gistAPIDelegateSpy = GistAPIDelegateSpy()
190 | sut.delegate = gistAPIDelegateSpy
191 | let dataTaskSpy = URLSessionDataTaskSpy()
192 | let error: Error? = NSError(domain: "GisterAPI", code: 911, userInfo: ["Unit Test" : "No harm done."])
193 |
194 | // When
195 | sut.urlSession(sessionSpy, task: dataTaskSpy, didCompleteWithError: error)
196 |
197 | // Then
198 | let actualGists = gistAPIDelegateSpy.gistAPIDidFetchGistsResults!
199 | XCTAssertTrue(gistAPIDelegateSpy.gistAPIDidFetchGistsCalled, "urlSession(_:task:didCompleteWithError:) should notify the delegate")
200 | XCTAssertTrue(actualGists.isEmpty, "urlSession(_:task:didCompleteWithError:) should return empty results")
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/Gister.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 7160C7561F8FA4F300506FE1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7551F8FA4F300506FE1 /* AppDelegate.swift */; };
11 | 7160C75B1F8FA4F300506FE1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7160C7591F8FA4F300506FE1 /* Main.storyboard */; };
12 | 7160C75E1F8FA4F300506FE1 /* Gister.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7160C75C1F8FA4F300506FE1 /* Gister.xcdatamodeld */; };
13 | 7160C7601F8FA4F300506FE1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7160C75F1F8FA4F300506FE1 /* Assets.xcassets */; };
14 | 7160C7631F8FA4F300506FE1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7160C7611F8FA4F300506FE1 /* LaunchScreen.storyboard */; };
15 | 7160C76E1F8FA4F300506FE1 /* GisterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C76D1F8FA4F300506FE1 /* GisterTests.swift */; };
16 | 7160C7791F8FA4F300506FE1 /* GisterUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7781F8FA4F300506FE1 /* GisterUITests.swift */; };
17 | 7160C78E1F8FA62800506FE1 /* ListGistsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7881F8FA62800506FE1 /* ListGistsInteractor.swift */; };
18 | 7160C78F1F8FA62800506FE1 /* ListGistsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7891F8FA62800506FE1 /* ListGistsModels.swift */; };
19 | 7160C7901F8FA62800506FE1 /* ListGistsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C78A1F8FA62800506FE1 /* ListGistsPresenter.swift */; };
20 | 7160C7911F8FA62800506FE1 /* ListGistsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C78B1F8FA62800506FE1 /* ListGistsRouter.swift */; };
21 | 7160C7921F8FA62800506FE1 /* ListGistsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C78C1F8FA62800506FE1 /* ListGistsViewController.swift */; };
22 | 7160C7931F8FA62800506FE1 /* ListGistsWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C78D1F8FA62800506FE1 /* ListGistsWorker.swift */; };
23 | 7160C79A1F8FA68800506FE1 /* ListGistsInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7961F8FA68800506FE1 /* ListGistsInteractorTests.swift */; };
24 | 7160C79B1F8FA68800506FE1 /* ListGistsPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7971F8FA68800506FE1 /* ListGistsPresenterTests.swift */; };
25 | 7160C79C1F8FA68800506FE1 /* ListGistsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7981F8FA68800506FE1 /* ListGistsViewControllerTests.swift */; };
26 | 7160C79D1F8FA68800506FE1 /* ListGistsWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7991F8FA68800506FE1 /* ListGistsWorkerTests.swift */; };
27 | 7160C7A01F8FA73600506FE1 /* GistAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C79F1F8FA73600506FE1 /* GistAPI.swift */; };
28 | 7160C7A51F90017D00506FE1 /* GistAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7160C7A41F90017D00506FE1 /* GistAPITest.swift */; };
29 | 71C590591F9B08A50098542B /* Gist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C590581F9B08A50098542B /* Gist.swift */; };
30 | 71D5A6BE1F9AC1B500BFA7A7 /* Gist.json in Resources */ = {isa = PBXBuildFile; fileRef = 71D5A6BD1F9AC1B500BFA7A7 /* Gist.json */; };
31 | 71D5A6C01F9AC1F500BFA7A7 /* Seeds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D5A6BF1F9AC1F500BFA7A7 /* Seeds.swift */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXContainerItemProxy section */
35 | 7160C76A1F8FA4F300506FE1 /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = 7160C74A1F8FA4F300506FE1 /* Project object */;
38 | proxyType = 1;
39 | remoteGlobalIDString = 7160C7511F8FA4F300506FE1;
40 | remoteInfo = Gister;
41 | };
42 | 7160C7751F8FA4F300506FE1 /* PBXContainerItemProxy */ = {
43 | isa = PBXContainerItemProxy;
44 | containerPortal = 7160C74A1F8FA4F300506FE1 /* Project object */;
45 | proxyType = 1;
46 | remoteGlobalIDString = 7160C7511F8FA4F300506FE1;
47 | remoteInfo = Gister;
48 | };
49 | /* End PBXContainerItemProxy section */
50 |
51 | /* Begin PBXFileReference section */
52 | 7160C7521F8FA4F300506FE1 /* Gister.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gister.app; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 7160C7551F8FA4F300506FE1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
54 | 7160C75A1F8FA4F300506FE1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
55 | 7160C75D1F8FA4F300506FE1 /* Gister.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Gister.xcdatamodel; sourceTree = ""; };
56 | 7160C75F1F8FA4F300506FE1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
57 | 7160C7621F8FA4F300506FE1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
58 | 7160C7641F8FA4F300506FE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | 7160C7691F8FA4F300506FE1 /* GisterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GisterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 7160C76D1F8FA4F300506FE1 /* GisterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GisterTests.swift; sourceTree = ""; };
61 | 7160C76F1F8FA4F300506FE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
62 | 7160C7741F8FA4F300506FE1 /* GisterUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GisterUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63 | 7160C7781F8FA4F300506FE1 /* GisterUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GisterUITests.swift; sourceTree = ""; };
64 | 7160C77A1F8FA4F300506FE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
65 | 7160C7881F8FA62800506FE1 /* ListGistsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsInteractor.swift; path = Scenes/ListGists/ListGistsInteractor.swift; sourceTree = ""; };
66 | 7160C7891F8FA62800506FE1 /* ListGistsModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsModels.swift; path = Scenes/ListGists/ListGistsModels.swift; sourceTree = ""; };
67 | 7160C78A1F8FA62800506FE1 /* ListGistsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsPresenter.swift; path = Scenes/ListGists/ListGistsPresenter.swift; sourceTree = ""; };
68 | 7160C78B1F8FA62800506FE1 /* ListGistsRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsRouter.swift; path = Scenes/ListGists/ListGistsRouter.swift; sourceTree = ""; };
69 | 7160C78C1F8FA62800506FE1 /* ListGistsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsViewController.swift; path = Scenes/ListGists/ListGistsViewController.swift; sourceTree = ""; };
70 | 7160C78D1F8FA62800506FE1 /* ListGistsWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsWorker.swift; path = Scenes/ListGists/ListGistsWorker.swift; sourceTree = ""; };
71 | 7160C7961F8FA68800506FE1 /* ListGistsInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsInteractorTests.swift; path = Scenes/ListGists/ListGistsInteractorTests.swift; sourceTree = ""; };
72 | 7160C7971F8FA68800506FE1 /* ListGistsPresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsPresenterTests.swift; path = Scenes/ListGists/ListGistsPresenterTests.swift; sourceTree = ""; };
73 | 7160C7981F8FA68800506FE1 /* ListGistsViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsViewControllerTests.swift; path = Scenes/ListGists/ListGistsViewControllerTests.swift; sourceTree = ""; };
74 | 7160C7991F8FA68800506FE1 /* ListGistsWorkerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ListGistsWorkerTests.swift; path = Scenes/ListGists/ListGistsWorkerTests.swift; sourceTree = ""; };
75 | 7160C79F1F8FA73600506FE1 /* GistAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GistAPI.swift; path = APIs/GistAPI.swift; sourceTree = ""; };
76 | 7160C7A41F90017D00506FE1 /* GistAPITest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GistAPITest.swift; path = APIs/GistAPITest.swift; sourceTree = ""; };
77 | 71C590581F9B08A50098542B /* Gist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Gist.swift; path = APIs/Gist.swift; sourceTree = ""; };
78 | 71D5A6BD1F9AC1B500BFA7A7 /* Gist.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Gist.json; sourceTree = ""; };
79 | 71D5A6BF1F9AC1F500BFA7A7 /* Seeds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Seeds.swift; sourceTree = ""; };
80 | /* End PBXFileReference section */
81 |
82 | /* Begin PBXFrameworksBuildPhase section */
83 | 7160C74F1F8FA4F300506FE1 /* Frameworks */ = {
84 | isa = PBXFrameworksBuildPhase;
85 | buildActionMask = 2147483647;
86 | files = (
87 | );
88 | runOnlyForDeploymentPostprocessing = 0;
89 | };
90 | 7160C7661F8FA4F300506FE1 /* Frameworks */ = {
91 | isa = PBXFrameworksBuildPhase;
92 | buildActionMask = 2147483647;
93 | files = (
94 | );
95 | runOnlyForDeploymentPostprocessing = 0;
96 | };
97 | 7160C7711F8FA4F300506FE1 /* Frameworks */ = {
98 | isa = PBXFrameworksBuildPhase;
99 | buildActionMask = 2147483647;
100 | files = (
101 | );
102 | runOnlyForDeploymentPostprocessing = 0;
103 | };
104 | /* End PBXFrameworksBuildPhase section */
105 |
106 | /* Begin PBXGroup section */
107 | 7160C7491F8FA4F300506FE1 = {
108 | isa = PBXGroup;
109 | children = (
110 | 7160C7541F8FA4F300506FE1 /* Gister */,
111 | 7160C76C1F8FA4F300506FE1 /* GisterTests */,
112 | 7160C7771F8FA4F300506FE1 /* GisterUITests */,
113 | 7160C7531F8FA4F300506FE1 /* Products */,
114 | );
115 | sourceTree = "";
116 | };
117 | 7160C7531F8FA4F300506FE1 /* Products */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 7160C7521F8FA4F300506FE1 /* Gister.app */,
121 | 7160C7691F8FA4F300506FE1 /* GisterTests.xctest */,
122 | 7160C7741F8FA4F300506FE1 /* GisterUITests.xctest */,
123 | );
124 | name = Products;
125 | sourceTree = "";
126 | };
127 | 7160C7541F8FA4F300506FE1 /* Gister */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 7160C79E1F8FA6EA00506FE1 /* APIs */,
131 | 7160C7861F8FA60400506FE1 /* Scenes */,
132 | 7160C7551F8FA4F300506FE1 /* AppDelegate.swift */,
133 | 7160C7591F8FA4F300506FE1 /* Main.storyboard */,
134 | 7160C75F1F8FA4F300506FE1 /* Assets.xcassets */,
135 | 7160C7611F8FA4F300506FE1 /* LaunchScreen.storyboard */,
136 | 7160C7641F8FA4F300506FE1 /* Info.plist */,
137 | 7160C75C1F8FA4F300506FE1 /* Gister.xcdatamodeld */,
138 | );
139 | path = Gister;
140 | sourceTree = "";
141 | };
142 | 7160C76C1F8FA4F300506FE1 /* GisterTests */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 7160C7A11F90014600506FE1 /* APIs */,
146 | 7160C7941F8FA66D00506FE1 /* Scenes */,
147 | 7160C76D1F8FA4F300506FE1 /* GisterTests.swift */,
148 | 71D5A6BF1F9AC1F500BFA7A7 /* Seeds.swift */,
149 | 71D5A6BD1F9AC1B500BFA7A7 /* Gist.json */,
150 | 7160C76F1F8FA4F300506FE1 /* Info.plist */,
151 | );
152 | path = GisterTests;
153 | sourceTree = "";
154 | };
155 | 7160C7771F8FA4F300506FE1 /* GisterUITests */ = {
156 | isa = PBXGroup;
157 | children = (
158 | 7160C7781F8FA4F300506FE1 /* GisterUITests.swift */,
159 | 7160C77A1F8FA4F300506FE1 /* Info.plist */,
160 | );
161 | path = GisterUITests;
162 | sourceTree = "";
163 | };
164 | 7160C7861F8FA60400506FE1 /* Scenes */ = {
165 | isa = PBXGroup;
166 | children = (
167 | 7160C7871F8FA60600506FE1 /* ListGists */,
168 | );
169 | name = Scenes;
170 | sourceTree = "";
171 | };
172 | 7160C7871F8FA60600506FE1 /* ListGists */ = {
173 | isa = PBXGroup;
174 | children = (
175 | 7160C7881F8FA62800506FE1 /* ListGistsInteractor.swift */,
176 | 7160C7891F8FA62800506FE1 /* ListGistsModels.swift */,
177 | 7160C78A1F8FA62800506FE1 /* ListGistsPresenter.swift */,
178 | 7160C78B1F8FA62800506FE1 /* ListGistsRouter.swift */,
179 | 7160C78C1F8FA62800506FE1 /* ListGistsViewController.swift */,
180 | 7160C78D1F8FA62800506FE1 /* ListGistsWorker.swift */,
181 | );
182 | name = ListGists;
183 | sourceTree = "";
184 | };
185 | 7160C7941F8FA66D00506FE1 /* Scenes */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 7160C7951F8FA66D00506FE1 /* ListGists */,
189 | );
190 | name = Scenes;
191 | sourceTree = "";
192 | };
193 | 7160C7951F8FA66D00506FE1 /* ListGists */ = {
194 | isa = PBXGroup;
195 | children = (
196 | 7160C7961F8FA68800506FE1 /* ListGistsInteractorTests.swift */,
197 | 7160C7971F8FA68800506FE1 /* ListGistsPresenterTests.swift */,
198 | 7160C7981F8FA68800506FE1 /* ListGistsViewControllerTests.swift */,
199 | 7160C7991F8FA68800506FE1 /* ListGistsWorkerTests.swift */,
200 | );
201 | name = ListGists;
202 | sourceTree = "";
203 | };
204 | 7160C79E1F8FA6EA00506FE1 /* APIs */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 7160C79F1F8FA73600506FE1 /* GistAPI.swift */,
208 | 71C590581F9B08A50098542B /* Gist.swift */,
209 | );
210 | name = APIs;
211 | sourceTree = "";
212 | };
213 | 7160C7A11F90014600506FE1 /* APIs */ = {
214 | isa = PBXGroup;
215 | children = (
216 | 7160C7A41F90017D00506FE1 /* GistAPITest.swift */,
217 | );
218 | name = APIs;
219 | sourceTree = "";
220 | };
221 | /* End PBXGroup section */
222 |
223 | /* Begin PBXNativeTarget section */
224 | 7160C7511F8FA4F300506FE1 /* Gister */ = {
225 | isa = PBXNativeTarget;
226 | buildConfigurationList = 7160C77D1F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "Gister" */;
227 | buildPhases = (
228 | 7160C74E1F8FA4F300506FE1 /* Sources */,
229 | 7160C74F1F8FA4F300506FE1 /* Frameworks */,
230 | 7160C7501F8FA4F300506FE1 /* Resources */,
231 | );
232 | buildRules = (
233 | );
234 | dependencies = (
235 | );
236 | name = Gister;
237 | productName = Gister;
238 | productReference = 7160C7521F8FA4F300506FE1 /* Gister.app */;
239 | productType = "com.apple.product-type.application";
240 | };
241 | 7160C7681F8FA4F300506FE1 /* GisterTests */ = {
242 | isa = PBXNativeTarget;
243 | buildConfigurationList = 7160C7801F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "GisterTests" */;
244 | buildPhases = (
245 | 7160C7651F8FA4F300506FE1 /* Sources */,
246 | 7160C7661F8FA4F300506FE1 /* Frameworks */,
247 | 7160C7671F8FA4F300506FE1 /* Resources */,
248 | );
249 | buildRules = (
250 | );
251 | dependencies = (
252 | 7160C76B1F8FA4F300506FE1 /* PBXTargetDependency */,
253 | );
254 | name = GisterTests;
255 | productName = GisterTests;
256 | productReference = 7160C7691F8FA4F300506FE1 /* GisterTests.xctest */;
257 | productType = "com.apple.product-type.bundle.unit-test";
258 | };
259 | 7160C7731F8FA4F300506FE1 /* GisterUITests */ = {
260 | isa = PBXNativeTarget;
261 | buildConfigurationList = 7160C7831F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "GisterUITests" */;
262 | buildPhases = (
263 | 7160C7701F8FA4F300506FE1 /* Sources */,
264 | 7160C7711F8FA4F300506FE1 /* Frameworks */,
265 | 7160C7721F8FA4F300506FE1 /* Resources */,
266 | );
267 | buildRules = (
268 | );
269 | dependencies = (
270 | 7160C7761F8FA4F300506FE1 /* PBXTargetDependency */,
271 | );
272 | name = GisterUITests;
273 | productName = GisterUITests;
274 | productReference = 7160C7741F8FA4F300506FE1 /* GisterUITests.xctest */;
275 | productType = "com.apple.product-type.bundle.ui-testing";
276 | };
277 | /* End PBXNativeTarget section */
278 |
279 | /* Begin PBXProject section */
280 | 7160C74A1F8FA4F300506FE1 /* Project object */ = {
281 | isa = PBXProject;
282 | attributes = {
283 | LastSwiftUpdateCheck = 0830;
284 | LastUpgradeCheck = 0930;
285 | ORGANIZATIONNAME = "Clean Swift LLC";
286 | TargetAttributes = {
287 | 7160C7511F8FA4F300506FE1 = {
288 | CreatedOnToolsVersion = 8.3.3;
289 | DevelopmentTeam = 5LBZDN8XB8;
290 | LastSwiftMigration = 0930;
291 | ProvisioningStyle = Automatic;
292 | };
293 | 7160C7681F8FA4F300506FE1 = {
294 | CreatedOnToolsVersion = 8.3.3;
295 | DevelopmentTeam = 5LBZDN8XB8;
296 | LastSwiftMigration = 0930;
297 | ProvisioningStyle = Automatic;
298 | TestTargetID = 7160C7511F8FA4F300506FE1;
299 | };
300 | 7160C7731F8FA4F300506FE1 = {
301 | CreatedOnToolsVersion = 8.3.3;
302 | DevelopmentTeam = 5LBZDN8XB8;
303 | LastSwiftMigration = 0930;
304 | ProvisioningStyle = Automatic;
305 | TestTargetID = 7160C7511F8FA4F300506FE1;
306 | };
307 | };
308 | };
309 | buildConfigurationList = 7160C74D1F8FA4F300506FE1 /* Build configuration list for PBXProject "Gister" */;
310 | compatibilityVersion = "Xcode 3.2";
311 | developmentRegion = English;
312 | hasScannedForEncodings = 0;
313 | knownRegions = (
314 | en,
315 | Base,
316 | );
317 | mainGroup = 7160C7491F8FA4F300506FE1;
318 | productRefGroup = 7160C7531F8FA4F300506FE1 /* Products */;
319 | projectDirPath = "";
320 | projectRoot = "";
321 | targets = (
322 | 7160C7511F8FA4F300506FE1 /* Gister */,
323 | 7160C7681F8FA4F300506FE1 /* GisterTests */,
324 | 7160C7731F8FA4F300506FE1 /* GisterUITests */,
325 | );
326 | };
327 | /* End PBXProject section */
328 |
329 | /* Begin PBXResourcesBuildPhase section */
330 | 7160C7501F8FA4F300506FE1 /* Resources */ = {
331 | isa = PBXResourcesBuildPhase;
332 | buildActionMask = 2147483647;
333 | files = (
334 | 7160C7631F8FA4F300506FE1 /* LaunchScreen.storyboard in Resources */,
335 | 7160C7601F8FA4F300506FE1 /* Assets.xcassets in Resources */,
336 | 7160C75B1F8FA4F300506FE1 /* Main.storyboard in Resources */,
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | };
340 | 7160C7671F8FA4F300506FE1 /* Resources */ = {
341 | isa = PBXResourcesBuildPhase;
342 | buildActionMask = 2147483647;
343 | files = (
344 | 71D5A6BE1F9AC1B500BFA7A7 /* Gist.json in Resources */,
345 | );
346 | runOnlyForDeploymentPostprocessing = 0;
347 | };
348 | 7160C7721F8FA4F300506FE1 /* Resources */ = {
349 | isa = PBXResourcesBuildPhase;
350 | buildActionMask = 2147483647;
351 | files = (
352 | );
353 | runOnlyForDeploymentPostprocessing = 0;
354 | };
355 | /* End PBXResourcesBuildPhase section */
356 |
357 | /* Begin PBXSourcesBuildPhase section */
358 | 7160C74E1F8FA4F300506FE1 /* Sources */ = {
359 | isa = PBXSourcesBuildPhase;
360 | buildActionMask = 2147483647;
361 | files = (
362 | 71C590591F9B08A50098542B /* Gist.swift in Sources */,
363 | 7160C7931F8FA62800506FE1 /* ListGistsWorker.swift in Sources */,
364 | 7160C78E1F8FA62800506FE1 /* ListGistsInteractor.swift in Sources */,
365 | 7160C7A01F8FA73600506FE1 /* GistAPI.swift in Sources */,
366 | 7160C7901F8FA62800506FE1 /* ListGistsPresenter.swift in Sources */,
367 | 7160C7561F8FA4F300506FE1 /* AppDelegate.swift in Sources */,
368 | 7160C78F1F8FA62800506FE1 /* ListGistsModels.swift in Sources */,
369 | 7160C75E1F8FA4F300506FE1 /* Gister.xcdatamodeld in Sources */,
370 | 7160C7921F8FA62800506FE1 /* ListGistsViewController.swift in Sources */,
371 | 7160C7911F8FA62800506FE1 /* ListGistsRouter.swift in Sources */,
372 | );
373 | runOnlyForDeploymentPostprocessing = 0;
374 | };
375 | 7160C7651F8FA4F300506FE1 /* Sources */ = {
376 | isa = PBXSourcesBuildPhase;
377 | buildActionMask = 2147483647;
378 | files = (
379 | 7160C76E1F8FA4F300506FE1 /* GisterTests.swift in Sources */,
380 | 7160C7A51F90017D00506FE1 /* GistAPITest.swift in Sources */,
381 | 71D5A6C01F9AC1F500BFA7A7 /* Seeds.swift in Sources */,
382 | 7160C79D1F8FA68800506FE1 /* ListGistsWorkerTests.swift in Sources */,
383 | 7160C79C1F8FA68800506FE1 /* ListGistsViewControllerTests.swift in Sources */,
384 | 7160C79A1F8FA68800506FE1 /* ListGistsInteractorTests.swift in Sources */,
385 | 7160C79B1F8FA68800506FE1 /* ListGistsPresenterTests.swift in Sources */,
386 | );
387 | runOnlyForDeploymentPostprocessing = 0;
388 | };
389 | 7160C7701F8FA4F300506FE1 /* Sources */ = {
390 | isa = PBXSourcesBuildPhase;
391 | buildActionMask = 2147483647;
392 | files = (
393 | 7160C7791F8FA4F300506FE1 /* GisterUITests.swift in Sources */,
394 | );
395 | runOnlyForDeploymentPostprocessing = 0;
396 | };
397 | /* End PBXSourcesBuildPhase section */
398 |
399 | /* Begin PBXTargetDependency section */
400 | 7160C76B1F8FA4F300506FE1 /* PBXTargetDependency */ = {
401 | isa = PBXTargetDependency;
402 | target = 7160C7511F8FA4F300506FE1 /* Gister */;
403 | targetProxy = 7160C76A1F8FA4F300506FE1 /* PBXContainerItemProxy */;
404 | };
405 | 7160C7761F8FA4F300506FE1 /* PBXTargetDependency */ = {
406 | isa = PBXTargetDependency;
407 | target = 7160C7511F8FA4F300506FE1 /* Gister */;
408 | targetProxy = 7160C7751F8FA4F300506FE1 /* PBXContainerItemProxy */;
409 | };
410 | /* End PBXTargetDependency section */
411 |
412 | /* Begin PBXVariantGroup section */
413 | 7160C7591F8FA4F300506FE1 /* Main.storyboard */ = {
414 | isa = PBXVariantGroup;
415 | children = (
416 | 7160C75A1F8FA4F300506FE1 /* Base */,
417 | );
418 | name = Main.storyboard;
419 | sourceTree = "";
420 | };
421 | 7160C7611F8FA4F300506FE1 /* LaunchScreen.storyboard */ = {
422 | isa = PBXVariantGroup;
423 | children = (
424 | 7160C7621F8FA4F300506FE1 /* Base */,
425 | );
426 | name = LaunchScreen.storyboard;
427 | sourceTree = "";
428 | };
429 | /* End PBXVariantGroup section */
430 |
431 | /* Begin XCBuildConfiguration section */
432 | 7160C77B1F8FA4F300506FE1 /* Debug */ = {
433 | isa = XCBuildConfiguration;
434 | buildSettings = {
435 | ALWAYS_SEARCH_USER_PATHS = NO;
436 | CLANG_ANALYZER_NONNULL = YES;
437 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
438 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
439 | CLANG_CXX_LIBRARY = "libc++";
440 | CLANG_ENABLE_MODULES = YES;
441 | CLANG_ENABLE_OBJC_ARC = YES;
442 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
443 | CLANG_WARN_BOOL_CONVERSION = YES;
444 | CLANG_WARN_COMMA = YES;
445 | CLANG_WARN_CONSTANT_CONVERSION = YES;
446 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
447 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
448 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
449 | CLANG_WARN_EMPTY_BODY = YES;
450 | CLANG_WARN_ENUM_CONVERSION = YES;
451 | CLANG_WARN_INFINITE_RECURSION = YES;
452 | CLANG_WARN_INT_CONVERSION = YES;
453 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
454 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
457 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
458 | CLANG_WARN_STRICT_PROTOTYPES = YES;
459 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
460 | CLANG_WARN_UNREACHABLE_CODE = YES;
461 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
462 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
463 | COPY_PHASE_STRIP = NO;
464 | DEBUG_INFORMATION_FORMAT = dwarf;
465 | ENABLE_STRICT_OBJC_MSGSEND = YES;
466 | ENABLE_TESTABILITY = YES;
467 | GCC_C_LANGUAGE_STANDARD = gnu99;
468 | GCC_DYNAMIC_NO_PIC = NO;
469 | GCC_NO_COMMON_BLOCKS = YES;
470 | GCC_OPTIMIZATION_LEVEL = 0;
471 | GCC_PREPROCESSOR_DEFINITIONS = (
472 | "DEBUG=1",
473 | "$(inherited)",
474 | );
475 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
476 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
477 | GCC_WARN_UNDECLARED_SELECTOR = YES;
478 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
479 | GCC_WARN_UNUSED_FUNCTION = YES;
480 | GCC_WARN_UNUSED_VARIABLE = YES;
481 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
482 | MTL_ENABLE_DEBUG_INFO = YES;
483 | ONLY_ACTIVE_ARCH = YES;
484 | SDKROOT = iphoneos;
485 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
486 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
487 | TARGETED_DEVICE_FAMILY = "1,2";
488 | };
489 | name = Debug;
490 | };
491 | 7160C77C1F8FA4F300506FE1 /* Release */ = {
492 | isa = XCBuildConfiguration;
493 | buildSettings = {
494 | ALWAYS_SEARCH_USER_PATHS = NO;
495 | CLANG_ANALYZER_NONNULL = YES;
496 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
497 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
498 | CLANG_CXX_LIBRARY = "libc++";
499 | CLANG_ENABLE_MODULES = YES;
500 | CLANG_ENABLE_OBJC_ARC = YES;
501 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
502 | CLANG_WARN_BOOL_CONVERSION = YES;
503 | CLANG_WARN_COMMA = YES;
504 | CLANG_WARN_CONSTANT_CONVERSION = YES;
505 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
506 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
507 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
508 | CLANG_WARN_EMPTY_BODY = YES;
509 | CLANG_WARN_ENUM_CONVERSION = YES;
510 | CLANG_WARN_INFINITE_RECURSION = YES;
511 | CLANG_WARN_INT_CONVERSION = YES;
512 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
513 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
514 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
515 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
516 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
517 | CLANG_WARN_STRICT_PROTOTYPES = YES;
518 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
519 | CLANG_WARN_UNREACHABLE_CODE = YES;
520 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
521 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
522 | COPY_PHASE_STRIP = NO;
523 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
524 | ENABLE_NS_ASSERTIONS = NO;
525 | ENABLE_STRICT_OBJC_MSGSEND = YES;
526 | GCC_C_LANGUAGE_STANDARD = gnu99;
527 | GCC_NO_COMMON_BLOCKS = YES;
528 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
529 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
530 | GCC_WARN_UNDECLARED_SELECTOR = YES;
531 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
532 | GCC_WARN_UNUSED_FUNCTION = YES;
533 | GCC_WARN_UNUSED_VARIABLE = YES;
534 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
535 | MTL_ENABLE_DEBUG_INFO = NO;
536 | SDKROOT = iphoneos;
537 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
538 | TARGETED_DEVICE_FAMILY = "1,2";
539 | VALIDATE_PRODUCT = YES;
540 | };
541 | name = Release;
542 | };
543 | 7160C77E1F8FA4F300506FE1 /* Debug */ = {
544 | isa = XCBuildConfiguration;
545 | buildSettings = {
546 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
547 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
548 | INFOPLIST_FILE = Gister/Info.plist;
549 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
550 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.Gister";
551 | PRODUCT_NAME = "$(TARGET_NAME)";
552 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
553 | SWIFT_VERSION = 4.0;
554 | };
555 | name = Debug;
556 | };
557 | 7160C77F1F8FA4F300506FE1 /* Release */ = {
558 | isa = XCBuildConfiguration;
559 | buildSettings = {
560 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
561 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
562 | INFOPLIST_FILE = Gister/Info.plist;
563 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
564 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.Gister";
565 | PRODUCT_NAME = "$(TARGET_NAME)";
566 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
567 | SWIFT_VERSION = 4.0;
568 | };
569 | name = Release;
570 | };
571 | 7160C7811F8FA4F300506FE1 /* Debug */ = {
572 | isa = XCBuildConfiguration;
573 | buildSettings = {
574 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
575 | BUNDLE_LOADER = "$(TEST_HOST)";
576 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
577 | INFOPLIST_FILE = GisterTests/Info.plist;
578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
579 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.GisterTests";
580 | PRODUCT_NAME = "$(TARGET_NAME)";
581 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
582 | SWIFT_VERSION = 4.0;
583 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gister.app/Gister";
584 | };
585 | name = Debug;
586 | };
587 | 7160C7821F8FA4F300506FE1 /* Release */ = {
588 | isa = XCBuildConfiguration;
589 | buildSettings = {
590 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
591 | BUNDLE_LOADER = "$(TEST_HOST)";
592 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
593 | INFOPLIST_FILE = GisterTests/Info.plist;
594 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
595 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.GisterTests";
596 | PRODUCT_NAME = "$(TARGET_NAME)";
597 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
598 | SWIFT_VERSION = 4.0;
599 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gister.app/Gister";
600 | };
601 | name = Release;
602 | };
603 | 7160C7841F8FA4F300506FE1 /* Debug */ = {
604 | isa = XCBuildConfiguration;
605 | buildSettings = {
606 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
607 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
608 | INFOPLIST_FILE = GisterUITests/Info.plist;
609 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
610 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.GisterUITests";
611 | PRODUCT_NAME = "$(TARGET_NAME)";
612 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
613 | SWIFT_VERSION = 4.0;
614 | TEST_TARGET_NAME = Gister;
615 | };
616 | name = Debug;
617 | };
618 | 7160C7851F8FA4F300506FE1 /* Release */ = {
619 | isa = XCBuildConfiguration;
620 | buildSettings = {
621 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
622 | DEVELOPMENT_TEAM = 5LBZDN8XB8;
623 | INFOPLIST_FILE = GisterUITests/Info.plist;
624 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
625 | PRODUCT_BUNDLE_IDENTIFIER = "com.clean-swift.GisterUITests";
626 | PRODUCT_NAME = "$(TARGET_NAME)";
627 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
628 | SWIFT_VERSION = 4.0;
629 | TEST_TARGET_NAME = Gister;
630 | };
631 | name = Release;
632 | };
633 | /* End XCBuildConfiguration section */
634 |
635 | /* Begin XCConfigurationList section */
636 | 7160C74D1F8FA4F300506FE1 /* Build configuration list for PBXProject "Gister" */ = {
637 | isa = XCConfigurationList;
638 | buildConfigurations = (
639 | 7160C77B1F8FA4F300506FE1 /* Debug */,
640 | 7160C77C1F8FA4F300506FE1 /* Release */,
641 | );
642 | defaultConfigurationIsVisible = 0;
643 | defaultConfigurationName = Release;
644 | };
645 | 7160C77D1F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "Gister" */ = {
646 | isa = XCConfigurationList;
647 | buildConfigurations = (
648 | 7160C77E1F8FA4F300506FE1 /* Debug */,
649 | 7160C77F1F8FA4F300506FE1 /* Release */,
650 | );
651 | defaultConfigurationIsVisible = 0;
652 | defaultConfigurationName = Release;
653 | };
654 | 7160C7801F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "GisterTests" */ = {
655 | isa = XCConfigurationList;
656 | buildConfigurations = (
657 | 7160C7811F8FA4F300506FE1 /* Debug */,
658 | 7160C7821F8FA4F300506FE1 /* Release */,
659 | );
660 | defaultConfigurationIsVisible = 0;
661 | defaultConfigurationName = Release;
662 | };
663 | 7160C7831F8FA4F300506FE1 /* Build configuration list for PBXNativeTarget "GisterUITests" */ = {
664 | isa = XCConfigurationList;
665 | buildConfigurations = (
666 | 7160C7841F8FA4F300506FE1 /* Debug */,
667 | 7160C7851F8FA4F300506FE1 /* Release */,
668 | );
669 | defaultConfigurationIsVisible = 0;
670 | defaultConfigurationName = Release;
671 | };
672 | /* End XCConfigurationList section */
673 |
674 | /* Begin XCVersionGroup section */
675 | 7160C75C1F8FA4F300506FE1 /* Gister.xcdatamodeld */ = {
676 | isa = XCVersionGroup;
677 | children = (
678 | 7160C75D1F8FA4F300506FE1 /* Gister.xcdatamodel */,
679 | );
680 | currentVersion = 7160C75D1F8FA4F300506FE1 /* Gister.xcdatamodel */;
681 | path = Gister.xcdatamodeld;
682 | sourceTree = "";
683 | versionGroupType = wrapper.xcdatamodel;
684 | };
685 | /* End XCVersionGroup section */
686 | };
687 | rootObject = 7160C74A1F8FA4F300506FE1 /* Project object */;
688 | }
689 |
--------------------------------------------------------------------------------