Skip to content

Commit 55cf66c

Browse files
authored
Merge pull request #1 from k-angama/feature/update
feat: refactoring and add unit tests
2 parents 2b9a3e3 + 61da870 commit 55cf66c

27 files changed

+510
-134
lines changed

CleanArchKit.podspec

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Pod::Spec.new do |s|
33
s.name = 'CleanArchKit'
4-
s.version = '0.1.0'
4+
s.version = '0.1.1'
55
s.swift_version = "5.0"
66
s.summary = 'An iOS library to create a project with the Clean Architecture using Router and MVVM pattern.'
77

@@ -21,8 +21,5 @@ DESC
2121
s.exclude_files = 'CleanArchKit/CleanArchKitTests/**/*.{swift}'
2222

2323
s.frameworks = 'UIKit'
24-
s.dependency 'RxSwift', '~> 6.1.0'
25-
s.dependency 'RxCocoa', '~> 6.1.0'
26-
s.dependency 'RxDataSources', '~> 5.0'
2724

2825
end

CleanArchKit/CleanArchKit.xcodeproj/project.pbxproj

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
2562060529E5A2CB00896F1C /* CleanArchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 256205FC29E5A2CB00896F1C /* CleanArchKit.framework */; };
10+
2525942529F19DDF0007F0D1 /* CleanArchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 256205FC29E5A2CB00896F1C /* CleanArchKit.framework */; };
11+
2525942E29F27DE30007F0D1 /* MockMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2525942D29F27DE30007F0D1 /* MockMapper.swift */; };
1112
2562060A29E5A2CB00896F1C /* CleanArchKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562060929E5A2CB00896F1C /* CleanArchKitTests.swift */; };
1213
2562060B29E5A2CB00896F1C /* CleanArchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 256205FF29E5A2CB00896F1C /* CleanArchKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
1314
2562063729E5A55D00896F1C /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562063629E5A55D00896F1C /* ViewModelProtocol.swift */; };
@@ -17,9 +18,6 @@
1718
2562063F29E5A5F300896F1C /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562063E29E5A5F300896F1C /* BaseViewController.swift */; };
1819
2562064129E5A61800896F1C /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562064029E5A61800896F1C /* Router.swift */; };
1920
2562064329E5A64700896F1C /* BaseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562064229E5A64700896F1C /* BaseRouter.swift */; };
20-
2562064629E5A6B100896F1C /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2562064529E5A6B100896F1C /* RxSwift.framework */; };
21-
2562065F29E5AA6300896F1C /* RxDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2562065E29E5AA6300896F1C /* RxDataSources.framework */; };
22-
2562066229E5AA6600896F1C /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2562066129E5AA6600896F1C /* RxCocoa.framework */; };
2321
25C293D729E6A0830062AE27 /* BaseUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C293D629E6A0830062AE27 /* BaseUseCase.swift */; };
2422
25C293D929E6A0BD0062AE27 /* BaseMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C293D829E6A0BD0062AE27 /* BaseMapper.swift */; };
2523
/* End PBXBuildFile section */
@@ -35,6 +33,8 @@
3533
/* End PBXContainerItemProxy section */
3634

3735
/* Begin PBXFileReference section */
36+
2525941929F18B290007F0D1 /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
37+
2525942D29F27DE30007F0D1 /* MockMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMapper.swift; sourceTree = "<group>"; };
3838
256205FC29E5A2CB00896F1C /* CleanArchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CleanArchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3939
256205FF29E5A2CB00896F1C /* CleanArchKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CleanArchKit.h; sourceTree = "<group>"; };
4040
2562060429E5A2CB00896F1C /* CleanArchKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CleanArchKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -46,9 +46,6 @@
4646
2562063E29E5A5F300896F1C /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
4747
2562064029E5A61800896F1C /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
4848
2562064229E5A64700896F1C /* BaseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRouter.swift; sourceTree = "<group>"; };
49-
2562064529E5A6B100896F1C /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50-
2562065E29E5AA6300896F1C /* RxDataSources.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxDataSources.framework; sourceTree = BUILT_PRODUCTS_DIR; };
51-
2562066129E5AA6600896F1C /* RxCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5249
25C293D629E6A0830062AE27 /* BaseUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUseCase.swift; sourceTree = "<group>"; };
5350
25C293D829E6A0BD0062AE27 /* BaseMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMapper.swift; sourceTree = "<group>"; };
5451
/* End PBXFileReference section */
@@ -58,23 +55,28 @@
5855
isa = PBXFrameworksBuildPhase;
5956
buildActionMask = 2147483647;
6057
files = (
61-
2562066229E5AA6600896F1C /* RxCocoa.framework in Frameworks */,
62-
2562064629E5A6B100896F1C /* RxSwift.framework in Frameworks */,
63-
2562065F29E5AA6300896F1C /* RxDataSources.framework in Frameworks */,
6458
);
6559
runOnlyForDeploymentPostprocessing = 0;
6660
};
6761
2562060129E5A2CB00896F1C /* Frameworks */ = {
6862
isa = PBXFrameworksBuildPhase;
6963
buildActionMask = 2147483647;
7064
files = (
71-
2562060529E5A2CB00896F1C /* CleanArchKit.framework in Frameworks */,
65+
2525942529F19DDF0007F0D1 /* CleanArchKit.framework in Frameworks */,
7266
);
7367
runOnlyForDeploymentPostprocessing = 0;
7468
};
7569
/* End PBXFrameworksBuildPhase section */
7670

7771
/* Begin PBXGroup section */
72+
2525942C29F27DCC0007F0D1 /* Mock */ = {
73+
isa = PBXGroup;
74+
children = (
75+
2525942D29F27DE30007F0D1 /* MockMapper.swift */,
76+
);
77+
path = Mock;
78+
sourceTree = "<group>";
79+
};
7880
256205F229E5A2CB00896F1C = {
7981
isa = PBXGroup;
8082
children = (
@@ -107,6 +109,7 @@
107109
2562060829E5A2CB00896F1C /* CleanArchKitTests */ = {
108110
isa = PBXGroup;
109111
children = (
112+
2525942C29F27DCC0007F0D1 /* Mock */,
110113
2562060929E5A2CB00896F1C /* CleanArchKitTests.swift */,
111114
);
112115
path = CleanArchKitTests;
@@ -138,9 +141,7 @@
138141
2562064429E5A6B100896F1C /* Frameworks */ = {
139142
isa = PBXGroup;
140143
children = (
141-
2562066129E5AA6600896F1C /* RxCocoa.framework */,
142-
2562065E29E5AA6300896F1C /* RxDataSources.framework */,
143-
2562064529E5A6B100896F1C /* RxSwift.framework */,
144+
2525941929F18B290007F0D1 /* RxSwift.framework */,
144145
);
145146
name = Frameworks;
146147
sourceTree = "<group>";
@@ -271,6 +272,7 @@
271272
isa = PBXSourcesBuildPhase;
272273
buildActionMask = 2147483647;
273274
files = (
275+
2525942E29F27DE30007F0D1 /* MockMapper.swift in Sources */,
274276
2562060A29E5A2CB00896F1C /* CleanArchKitTests.swift in Sources */,
275277
);
276278
runOnlyForDeploymentPostprocessing = 0;
@@ -419,26 +421,19 @@
419421
DYLIB_INSTALL_NAME_BASE = "@rpath";
420422
FRAMEWORK_SEARCH_PATHS = "";
421423
GENERATE_INFOPLIST_FILE = YES;
424+
HEADER_SEARCH_PATHS = "";
422425
INFOPLIST_KEY_NSHumanReadableCopyright = "";
423426
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
424427
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
425-
LD_RUNPATH_SEARCH_PATHS = (
426-
"$(inherited)",
427-
"@executable_path/Frameworks",
428-
"@loader_path/Frameworks",
429-
);
430428
MARKETING_VERSION = 1.0;
431429
PRODUCT_BUNDLE_IDENTIFIER = com.kangama.CleanArchKit;
432430
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
433431
SKIP_INSTALL = YES;
434432
SWIFT_EMIT_LOC_STRINGS = YES;
435433
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
436434
SWIFT_VERSION = 5.0;
437-
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
438-
"$(BUILT_PRODUCTS_DIR)/RxSwift",
439-
"$(BUILT_PRODUCTS_DIR)/RxDataSources",
440-
"$(BUILT_PRODUCTS_DIR)/RxCocoa",
441-
);
435+
SYSTEM_FRAMEWORK_SEARCH_PATHS = "";
436+
SYSTEM_HEADER_SEARCH_PATHS = "";
442437
TARGETED_DEVICE_FAMILY = "1,2";
443438
};
444439
name = Debug;
@@ -458,22 +453,14 @@
458453
INFOPLIST_KEY_NSHumanReadableCopyright = "";
459454
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
460455
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
461-
LD_RUNPATH_SEARCH_PATHS = (
462-
"$(inherited)",
463-
"@executable_path/Frameworks",
464-
"@loader_path/Frameworks",
465-
);
466456
MARKETING_VERSION = 1.0;
467457
PRODUCT_BUNDLE_IDENTIFIER = com.kangama.CleanArchKit;
468458
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
469459
SKIP_INSTALL = YES;
470460
SWIFT_EMIT_LOC_STRINGS = YES;
471461
SWIFT_VERSION = 5.0;
472-
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
473-
"$(BUILT_PRODUCTS_DIR)/RxSwift",
474-
"$(BUILT_PRODUCTS_DIR)/RxDataSources",
475-
"$(BUILT_PRODUCTS_DIR)/RxCocoa",
476-
);
462+
SYSTEM_FRAMEWORK_SEARCH_PATHS = "";
463+
SYSTEM_HEADER_SEARCH_PATHS = "";
477464
TARGETED_DEVICE_FAMILY = "1,2";
478465
};
479466
name = Release;
@@ -485,7 +472,10 @@
485472
CODE_SIGN_STYLE = Automatic;
486473
CURRENT_PROJECT_VERSION = 1;
487474
DEVELOPMENT_TEAM = FEV8Q8DHKC;
475+
FRAMEWORK_SEARCH_PATHS = "";
488476
GENERATE_INFOPLIST_FILE = YES;
477+
HEADER_SEARCH_PATHS = "";
478+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
489479
MARKETING_VERSION = 1.0;
490480
PRODUCT_BUNDLE_IDENTIFIER = com.kangama.CleanArchKitTests;
491481
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -502,7 +492,10 @@
502492
CODE_SIGN_STYLE = Automatic;
503493
CURRENT_PROJECT_VERSION = 1;
504494
DEVELOPMENT_TEAM = FEV8Q8DHKC;
495+
FRAMEWORK_SEARCH_PATHS = "";
505496
GENERATE_INFOPLIST_FILE = YES;
497+
HEADER_SEARCH_PATHS = "";
498+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
506499
MARKETING_VERSION = 1.0;
507500
PRODUCT_BUNDLE_IDENTIFIER = com.kangama.CleanArchKitTests;
508501
PRODUCT_NAME = "$(TARGET_NAME)";

CleanArchKit/CleanArchKit/Base/BaseMapper.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@
77

88
import Foundation
99

10-
public protocol BaseMapper: MapperFromEntity, MapperToEntity {}
10+
public protocol BaseMapper: MapperFromDomainEntity, MapperToDomainEntity {}
1111

12-
public protocol MapperFromEntity {
12+
public protocol MapperFromDomainEntity {
1313

14-
associatedtype A
15-
associatedtype B
14+
associatedtype A: EntityDomain
15+
associatedtype B: EntityRaw
1616

17-
static func mapFromEntity(type: A) -> B
17+
static func mapFromDomain(type: A) -> B
1818

1919
}
2020

21-
public protocol MapperToEntity{
21+
public protocol MapperToDomainEntity{
2222

23-
associatedtype A
24-
associatedtype B
23+
associatedtype A: EntityDomain
24+
associatedtype B: EntityRaw
2525

26-
static func mapToEntity(type: B) -> A
26+
static func mapToDomain(type: B) -> A
2727

2828
}
29+
30+
public protocol EntityRaw {}
31+
32+
public protocol EntityDomain {}

CleanArchKit/CleanArchKit/Base/BaseRouter.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ open class BaseRouter<T: Route>: Router {
2626

2727
open func transition(route: T) {}
2828

29+
deinit {
30+
print("deinit Router - \(Self.self)")
31+
}
2932
}

CleanArchKit/CleanArchKit/Base/BaseViewController.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import Foundation
99
import UIKit
10-
import RxSwift
1110

1211
open class BaseViewController<T: ViewModelProtocol>: UIViewController, ViewControllerProtocol, DIProtocol {
1312

@@ -16,10 +15,6 @@ open class BaseViewController<T: ViewModelProtocol>: UIViewController, ViewContr
1615
*/
1716
public var viewModel:T!
1817

19-
/**
20-
* This returns ARC (RAII) like resource management to `RxSwift`.
21-
*/
22-
public var disposeBag = DisposeBag()
2318

2419
open override func viewDidLoad() {
2520
super.viewDidLoad()
@@ -50,4 +45,8 @@ open class BaseViewController<T: ViewModelProtocol>: UIViewController, ViewContr
5045
*/
5146
open func setupUI() {}
5247

48+
deinit {
49+
print("deinit ViewController - \(Self.debugDescription())")
50+
}
51+
5352
}

CleanArchKit/CleanArchKit/Base/BaseViewModel.swift

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,35 @@
66
//
77

88
import Foundation
9-
import RxSwift
9+
import UIKit
1010

1111
open class BaseViewModel<T: Router, Input: InputProtocol, Output: OutputProtocol>: NSObject, ViewModelProtocol {
1212

1313
public var input: Input
1414

1515
public var output: Output
1616

17-
public let router: T
17+
private var _router: T
18+
public var router: T {
19+
if(Thread.callStackSymbols.contains(where: {
20+
$0.contains("UIViewController")
21+
})){
22+
let message = "Not used to the UIViewController"
23+
#if DEBUG
24+
fatalError(message)
25+
#else
26+
print(message)
27+
#endif
28+
}
29+
return _router
30+
}
1831

19-
/**
20-
* This returns ARC (RAII) like resource management to `RxSwift`.
21-
*/
22-
public let disposeBag = DisposeBag()
2332

2433
required public init(viewController: UIViewController? = nil) {
2534
self.input = Input()
2635
self.output = Output()
27-
self.router = T(viewController: viewController)
36+
self._router = T(viewController: viewController)
37+
2838
}
2939

3040
/**
@@ -42,4 +52,8 @@ open class BaseViewModel<T: Router, Input: InputProtocol, Output: OutputProtocol
4252
*/
4353
open func observers() {}
4454

55+
deinit {
56+
print("deinit ViewModel - \(Self.debugDescription())")
57+
}
58+
4559
}

CleanArchKit/CleanArchKitTests/CleanArchKitTests.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@ final class CleanArchKitTests: XCTestCase {
1818
// Put teardown code here. This method is called after the invocation of each test method in the class.
1919
}
2020

21-
func testExample() throws {
22-
// This is an example of a functional test case.
23-
// Use XCTAssert and related functions to verify your tests produce the correct results.
24-
// Any test you write for XCTest can be annotated as throws and async.
25-
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26-
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
21+
func testRawToDomainMapperExample() throws {
22+
let mockRaw = MockRaw(name: "Michel Dupond", email: "mdupond@test.com")
23+
let toDomain = MockMapper.mapToDomain(type: mockRaw)
24+
XCTAssertEqual(toDomain.email, "mdupond@test.com")
25+
XCTAssertEqual(toDomain.firstname, "Michel")
26+
XCTAssertEqual(toDomain.lastname, "Dupond")
2727
}
28-
29-
func testPerformanceExample() throws {
30-
// This is an example of a performance test case.
31-
self.measure {
32-
// Put the code you want to measure the time of here.
33-
}
28+
29+
func testDomainToRawMapperExample() throws {
30+
let mockDomain = MockDomain(firstname: "Michel", lastname: "Dupond", email: "mdupond@test.com")
31+
let fromRaw = MockMapper.mapFromDomain(type: mockDomain)
32+
XCTAssertEqual(fromRaw.email, "[email protected]")
33+
XCTAssertEqual(fromRaw.name, "Michel Dupond")
3434
}
3535

3636
}
37+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// MockMapper.swift
3+
// CleanArchKitTests
4+
//
5+
// Created by Karim Angama on 21/04/2023.
6+
//
7+
8+
import Foundation
9+
@testable import CleanArchKit
10+
11+
struct MockRaw: EntityRaw {
12+
let name: String?
13+
let email: String?
14+
}
15+
16+
struct MockDomain: EntityDomain {
17+
let firstname: String
18+
let lastname: String
19+
let email: String
20+
}
21+
22+
struct MockMapper: BaseMapper {
23+
24+
static func mapToDomain(type: MockRaw) -> MockDomain {
25+
MockDomain(
26+
firstname: type.name?.components(separatedBy: " ").first ?? "",
27+
lastname: type.name?.components(separatedBy: " ").last ?? "",
28+
email: type.email ?? ""
29+
)
30+
}
31+
32+
static func mapFromDomain(type: MockDomain) -> MockRaw {
33+
MockRaw(
34+
name: "\(type.firstname) \(type.lastname)",
35+
email: type.email
36+
)
37+
}
38+
39+
}

0 commit comments

Comments
 (0)