Skip to content

Commit 44f67a2

Browse files
authored
Eliminate force unwrapped types on ViewControllers (#12)
* Eliminate force unwrapped types on ViewControllers * Update README.md
1 parent 572048f commit 44f67a2

16 files changed

+330
-260
lines changed

README.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,25 @@ Concrete implementation is below:
4040

4141
```swift
4242
class FooViewController: UIViewController {
43-
private var model: FooModelContract!
44-
private var viewMediator: FooViewMediatorContract!
45-
private var controller: FooControllerContract!
43+
private var model: FooModelContract
44+
private var viewMediator: FooViewMediatorContract?
45+
private var controller: FooControllerContract?
4646

47-
// We should give FooModel to be able to change initial state of the screen.
48-
static func create(model: FooModelContract) -> FooViewController? {
49-
guard let viewController = R.storyboard.fooScreen.FooViewController() else {
50-
return nil
51-
}
52-
53-
viewController.model = model
54-
return viewController
47+
init(model: FooModelContract) -> FooViewController? {
48+
self.model = model
49+
super.init(nibName: nil, bundle: nil)
5550
}
5651

57-
58-
@IBOutlet weak var barView: BarView!
59-
@IBOutlet weak var bazView: BizzView!
52+
required init?(coder aDecoder: NSCoder) {
53+
// NOTE: We should not instantiate the ViewController by using UINibs to
54+
// eliminate fields that have force unwrapping types.
55+
return nil
56+
}
6057

6158
// Connect Model and ViewMediator, Controller.
62-
override func viewDidLoad() {
63-
super.viewDidLoad()
59+
override func loadView() {
60+
let rootView = FooRootView()
61+
self.view = rootView
6462

6563
let controller = FooController(
6664
willNotifyTo: self.model,
@@ -70,8 +68,8 @@ class FooViewController: UIViewController {
7068
self.viewMediator = FooViewMediator(
7169
observing: self.model,
7270
handling: (
73-
bar: self.barView,
74-
baz: self.bazView
71+
bar: rootView.barView,
72+
baz: rootView.bazView
7573
)
7674
)
7775
self.viewMediator.delegate = controller

TestableDesignExample.xcodeproj/project.pbxproj

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
2475E427AF93298C1BA03EB1 /* StargazersControllerMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E892E746679C895D57CC /* StargazersControllerMediator.swift */; };
3030
2475E437BCE2D828E2DE8608 /* PagedElementCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EE00EDD7E12FE4F3158F /* PagedElementCollection.swift */; };
3131
2475E4644B84BA23207DBA28 /* PagingModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E9701455B0421AAF96DF /* PagingModelTests.swift */; };
32+
2475E4661715371D7250B39F /* UserScreenRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E67AF3F104CABDC6A755 /* UserScreenRootView.swift */; };
3233
2475E4F5BAA39082F5129854 /* GitHubUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E96540D7FDFD907E38A1 /* GitHubUser.swift */; };
3334
2475E50D500D2D57253936F6 /* UserModelStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E2F51CEB39BCDE5D9437 /* UserModelStub.swift */; };
3435
2475E52AFF57CA80947B35B8 /* PageEndDetectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA6CE7CCD06FE9AAE392 /* PageEndDetectionStrategy.swift */; };
3536
2475E578041452F8670C5A8C /* UserMvcComposerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EDEF357B21E401A94659 /* UserMvcComposerTests.swift */; };
3637
2475E5EEB2DAE071F555A468 /* PagingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EE1B5C297CBB2DB792F7 /* PagingModel.swift */; };
3738
2475E63DC2AE2BBCED7A5D62 /* PagingCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E7ECB9C6033E2222F3D9 /* PagingCursorTests.swift */; };
39+
2475E76DB6877549D4EF0A3E /* FilledLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA23F072080B484A2B68 /* FilledLayout.swift */; };
3840
2475E797D65B825304504F9B /* FatalErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EB38628EF03DCAD17F84 /* FatalErrorViewController.swift */; };
3941
2475E7A6273DD9CBF4ABA5BA /* AnyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E8757F392A3269C299DC /* AnyError.swift */; };
4042
2475E81254A76DFDA50D9D83 /* StargazersViewMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E0C468760C2DAE1C894B /* StargazersViewMediator.swift */; };
@@ -51,6 +53,7 @@
5153
2475EA2BD6E67ECEAE176195 /* RemoteImageSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E767B5CB5B7E0C0B0A6E /* RemoteImageSource.swift */; };
5254
2475EA44D9638AEDA24CE1DC /* PageRepositorySpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E29FA1DBCF20C012D776 /* PageRepositorySpy.swift */; };
5355
2475EB37E60B9FA89BC83FF4 /* GitHubApiClientStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EAAA03A6CC33D5660297 /* GitHubApiClientStub.swift */; };
56+
2475EBBD47C22B8513391662 /* StargazersScreenRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E62C84D772E7C978BBEE /* StargazersScreenRootView.swift */; };
5457
2475EBBEE0CB66156780EAC1 /* PageRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EBC20C528C65C22AC76E /* PageRepositoryStub.swift */; };
5558
2475EBC38C427D41A452E006 /* GitHubStargazer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA9AAFF3FDC512301D94 /* GitHubStargazer.swift */; };
5659
2475EBE734270642753E85C1 /* StargazerRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E49AC1AFD52DCC81BE25 /* StargazerRepositoryStub.swift */; };
@@ -74,7 +77,8 @@
7477
2475EF6D67DC1229AB744C71 /* StargazersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA8A1CD58D89A6FC8E3E /* StargazersModel.swift */; };
7578
2475EF784651E52247E3C648 /* GitHubRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E9C17E4BBC136112F1C3 /* GitHubRepository.swift */; };
7679
2475EF8187459829B292AFA9 /* R.generatedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E5AD62D5E53E91973FEB /* R.generatedTests.swift */; };
77-
62266B811E7525EE00A55A01 /* UserScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 62266B801E7525EE00A55A01 /* UserScreen.storyboard */; };
80+
6292A2B01F60D821001A7172 /* UserScreenRootView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6292A2AF1F60D821001A7172 /* UserScreenRootView.xib */; };
81+
6292A2B21F60E902001A7172 /* StargazersScreenRootView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6292A2B11F60E902001A7172 /* StargazersScreenRootView.xib */; };
7882
62A1614D1E73C1CC003D28DC /* RxBlocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62A1614B1E73C1CC003D28DC /* RxBlocking.framework */; };
7983
62A1614E1E73C1CC003D28DC /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62A1614C1E73C1CC003D28DC /* RxTest.framework */; };
8084
62A161511E73C259003D28DC /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62A161501E73C259003D28DC /* RxSwift.framework */; };
@@ -86,7 +90,6 @@
8690
62A1616C1E741761003D28DC /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62A1616B1E741761003D28DC /* Result.framework */; };
8791
62A1616E1E743EF2003D28DC /* StargazerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62A1616D1E743EF2003D28DC /* StargazerCell.xib */; };
8892
62A161701E7440FE003D28DC /* StargazerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A1616F1E7440FE003D28DC /* StargazerCell.swift */; };
89-
62A161721E7447BC003D28DC /* StargazersScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 62A161711E7447BC003D28DC /* StargazersScreen.storyboard */; };
9093
62A161741E744812003D28DC /* StargazersMvcComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A161731E744812003D28DC /* StargazersMvcComposer.swift */; };
9194
62A161781E746105003D28DC /* FatalErrorScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 62A161771E746105003D28DC /* FatalErrorScreen.storyboard */; };
9295
/* End PBXBuildFile section */
@@ -138,8 +141,10 @@
138141
2475E5AD62D5E53E91973FEB /* R.generatedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.generatedTests.swift; sourceTree = "<group>"; };
139142
2475E5B4D098FE6945B3F148 /* sample.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = sample.png; sourceTree = "<group>"; };
140143
2475E6244DDBD595E9FCA79A /* StargazersModelStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StargazersModelStub.swift; sourceTree = "<group>"; };
144+
2475E62C84D772E7C978BBEE /* StargazersScreenRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StargazersScreenRootView.swift; sourceTree = "<group>"; };
141145
2475E633D1D5C9D0166E0EF1 /* FontRegistry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontRegistry.swift; sourceTree = "<group>"; };
142146
2475E6654583784DB2FAC099 /* user.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = user.json; sourceTree = "<group>"; };
147+
2475E67AF3F104CABDC6A755 /* UserScreenRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserScreenRootView.swift; sourceTree = "<group>"; };
143148
2475E686AF0E489B78CE12D8 /* PageElementCountStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageElementCountStrategy.swift; sourceTree = "<group>"; };
144149
2475E6904F2577E36489E9DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = "<group>"; };
145150
2475E6CC4A27B8058F8CAE7F /* UserMvcComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserMvcComposer.swift; sourceTree = "<group>"; };
@@ -157,6 +162,7 @@
157162
2475E9C17E4BBC136112F1C3 /* GitHubRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubRepository.swift; sourceTree = "<group>"; };
158163
2475E9F9C6BB7D41F55161A7 /* GitHubApiClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubApiClient.swift; sourceTree = "<group>"; };
159164
2475E9FD342D791575BDC09E /* Bag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = "<group>"; };
165+
2475EA23F072080B484A2B68 /* FilledLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilledLayout.swift; sourceTree = "<group>"; };
160166
2475EA6CE7CCD06FE9AAE392 /* PageEndDetectionStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageEndDetectionStrategy.swift; sourceTree = "<group>"; };
161167
2475EA8A1CD58D89A6FC8E3E /* StargazersModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StargazersModel.swift; sourceTree = "<group>"; };
162168
2475EA9AAFF3FDC512301D94 /* GitHubStargazer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubStargazer.swift; sourceTree = "<group>"; };
@@ -182,7 +188,8 @@
182188
2475EF639EA034BCC0C26512 /* octicons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file.ttf; name = octicons.ttf; path = Resources/octicons.ttf; sourceTree = "<group>"; };
183189
2475EF9C786B34F42182126A /* NavigatorStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigatorStub.swift; sourceTree = "<group>"; };
184190
2475EFC5E1532F9230A85D82 /* reposStargazers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = reposStargazers.json; sourceTree = "<group>"; };
185-
62266B801E7525EE00A55A01 /* UserScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserScreen.storyboard; sourceTree = "<group>"; };
191+
6292A2AF1F60D821001A7172 /* UserScreenRootView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserScreenRootView.xib; sourceTree = "<group>"; };
192+
6292A2B11F60E902001A7172 /* StargazersScreenRootView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StargazersScreenRootView.xib; sourceTree = "<group>"; };
186193
62A161471E73BCD4003D28DC /* Rswift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Rswift.framework; path = Carthage/Build/iOS/Rswift.framework; sourceTree = "<group>"; };
187194
62A1614B1E73C1CC003D28DC /* RxBlocking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxBlocking.framework; path = Carthage/Build/iOS/RxBlocking.framework; sourceTree = "<group>"; };
188195
62A1614C1E73C1CC003D28DC /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = "<group>"; };
@@ -201,7 +208,6 @@
201208
62A1616B1E741761003D28DC /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = "<group>"; };
202209
62A1616D1E743EF2003D28DC /* StargazerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StargazerCell.xib; sourceTree = "<group>"; };
203210
62A1616F1E7440FE003D28DC /* StargazerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StargazerCell.swift; sourceTree = "<group>"; };
204-
62A161711E7447BC003D28DC /* StargazersScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = StargazersScreen.storyboard; sourceTree = "<group>"; };
205211
62A161731E744812003D28DC /* StargazersMvcComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StargazersMvcComposer.swift; sourceTree = "<group>"; };
206212
62A161771E746105003D28DC /* FatalErrorScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = FatalErrorScreen.storyboard; sourceTree = "<group>"; };
207213
/* End PBXFileReference section */
@@ -246,7 +252,6 @@
246252
2475E2D3B67D9970FD6B94BF /* StargazerRepository.swift */,
247253
62A1616D1E743EF2003D28DC /* StargazerCell.xib */,
248254
62A1616F1E7440FE003D28DC /* StargazerCell.swift */,
249-
62A161711E7447BC003D28DC /* StargazersScreen.storyboard */,
250255
62A161731E744812003D28DC /* StargazersMvcComposer.swift */,
251256
2475E0C468760C2DAE1C894B /* StargazersViewMediator.swift */,
252257
2475E892E746679C895D57CC /* StargazersControllerMediator.swift */,
@@ -255,6 +260,8 @@
255260
2475EC7516DF00CF3095186A /* StargazersModelTests.swift */,
256261
2475E6244DDBD595E9FCA79A /* StargazersModelStub.swift */,
257262
2475E15A53F5DC95C1A18F2C /* StargazersInfiniteScrollController.swift */,
263+
2475E62C84D772E7C978BBEE /* StargazersScreenRootView.swift */,
264+
6292A2B11F60E902001A7172 /* StargazersScreenRootView.xib */,
258265
);
259266
path = Stargazers;
260267
sourceTree = "<group>";
@@ -297,6 +304,7 @@
297304
2475E2939E6563048AA378B3 /* RemoteImageSourceTests.swift */,
298305
2475E8757F392A3269C299DC /* AnyError.swift */,
299306
2475E6DC10E68D586EF9C728 /* TitleHolder.swift */,
307+
2475ED929826EEFF779C94F0 /* Layout */,
300308
);
301309
path = TestableDesignExample;
302310
sourceTree = "<group>";
@@ -387,11 +395,12 @@
387395
isa = PBXGroup;
388396
children = (
389397
2475E6CC4A27B8058F8CAE7F /* UserMvcComposer.swift */,
390-
62266B801E7525EE00A55A01 /* UserScreen.storyboard */,
391398
2475EDEF357B21E401A94659 /* UserMvcComposerTests.swift */,
392399
2475ECEADCC16CF800154CD5 /* UserModel.swift */,
393400
2475E4D128866C453FAF4F53 /* UserViewMediator.swift */,
394401
2475E2F51CEB39BCDE5D9437 /* UserModelStub.swift */,
402+
6292A2AF1F60D821001A7172 /* UserScreenRootView.xib */,
403+
2475E67AF3F104CABDC6A755 /* UserScreenRootView.swift */,
395404
);
396405
path = User;
397406
sourceTree = "<group>";
@@ -424,6 +433,14 @@
424433
path = Resources;
425434
sourceTree = "<group>";
426435
};
436+
2475ED929826EEFF779C94F0 /* Layout */ = {
437+
isa = PBXGroup;
438+
children = (
439+
2475EA23F072080B484A2B68 /* FilledLayout.swift */,
440+
);
441+
path = Layout;
442+
sourceTree = "<group>";
443+
};
427444
2475EE09DDDD552BE88E9783 /* TestableDesignExampleUITests */ = {
428445
isa = PBXGroup;
429446
children = (
@@ -567,9 +584,9 @@
567584
buildActionMask = 2147483647;
568585
files = (
569586
2475E184C491ECB5D4520C4D /* Assets.xcassets in Resources */,
570-
62A161721E7447BC003D28DC /* StargazersScreen.storyboard in Resources */,
571587
2475E9807E8143A4B3A751A5 /* LaunchScreen.storyboard in Resources */,
572-
62266B811E7525EE00A55A01 /* UserScreen.storyboard in Resources */,
588+
6292A2B21F60E902001A7172 /* StargazersScreenRootView.xib in Resources */,
589+
6292A2B01F60D821001A7172 /* UserScreenRootView.xib in Resources */,
573590
62A1616E1E743EF2003D28DC /* StargazerCell.xib in Resources */,
574591
2475EDED3B2768D715A948C3 /* Main.storyboard in Resources */,
575592
62A161781E746105003D28DC /* FatalErrorScreen.storyboard in Resources */,
@@ -709,6 +726,9 @@
709726
2475EEB00A8811E9698BD7E7 /* UserModel.swift in Sources */,
710727
2475ED242C02F97EC979B063 /* UserViewMediator.swift in Sources */,
711728
2475E0DE8D2D86FEB4B92AE3 /* TitleHolder.swift in Sources */,
729+
2475E4661715371D7250B39F /* UserScreenRootView.swift in Sources */,
730+
2475E76DB6877549D4EF0A3E /* FilledLayout.swift in Sources */,
731+
2475EBBD47C22B8513391662 /* StargazersScreenRootView.swift in Sources */,
712732
);
713733
runOnlyForDeploymentPostprocessing = 0;
714734
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import UIKit
2+
3+
4+
5+
enum FilledLayout {
6+
static func fill(subview: UIView, into superview: UIView) {
7+
let constraints: [NSLayoutConstraint] = [
8+
superview.topAnchor.constraint(equalTo: subview.topAnchor),
9+
superview.bottomAnchor.constraint(equalTo: subview.bottomAnchor),
10+
superview.leftAnchor.constraint(equalTo: subview.leftAnchor),
11+
superview.rightAnchor.constraint(equalTo: subview.rightAnchor),
12+
]
13+
14+
constraints.forEach { constraint in
15+
constraint.isActive = true
16+
}
17+
}
18+
}

TestableDesignExample/MvcArchitecture/Stargazers/StargazersControllerMediator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ extension StargazerControllerMediator: UITableViewDelegate {
6464

6565
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
6666
let stargazer = self.viewMediator.visibleStargazers[indexPath.row]
67-
let stargazerViewController = UserMvcComposer.create(
68-
byModel: UserModel(
67+
let stargazerViewController = UserMvcComposer(
68+
representing: UserModel(
6969
withInitialState: .fetched(
7070
result: .success(stargazer)
7171
)

0 commit comments

Comments
 (0)