├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .vscode
└── settings.json
├── ComponentizeDemo
├── Application
│ ├── Application.podspec
│ ├── ApplicationProtocol.podspec
│ ├── Protocol
│ │ └── ApplicationProtocol.swift
│ └── Source
│ │ └── Application.swift
└── Auth
│ ├── Auth.podspec
│ ├── AuthProtocol.podspec
│ ├── Protocol
│ └── AuthProtocol.swift
│ └── Source
│ └── Auth.swift
├── LICENSE
├── MIOSwiftyArchitecture.podspec
├── Package.resolved
├── Package.swift
├── Podfile
├── Podfile.lock
├── README-AppDocker.md
├── README.md
├── SwiftArchitecture.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── SAD.xcscheme
│ └── SwiftArchitectureUITests.xcscheme
├── SwiftArchitecture.xcworkspace
└── xcshareddata
│ └── WorkspaceSettings.xcsettings
├── SwiftArchitectureTests
├── Info.plist
└── swiftArchitectureTests.swift
├── SwiftArchitectureUITests
├── Info.plist
├── MockComponents.swift
└── swiftArchitectureUITests.swift
├── SwiftyArchitecture
├── AppDelegate.swift
├── AppLinkRegistery.plist
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── launch_swifty_icon.imageset
│ │ ├── Contents.json
│ │ └── iconfinder_332_Swift_logo_4375493.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Base
│ ├── AppDock
│ │ ├── AppContext
│ │ │ ├── AppContext+Standard.swift
│ │ │ ├── AppContext.swift
│ │ │ ├── AppDockConstants.swift
│ │ │ └── UserContext.swift
│ │ └── DataCenter
│ │ │ ├── Accessor.swift
│ │ │ ├── DataCenter.swift
│ │ │ ├── Private
│ │ │ └── Requests
│ │ │ │ └── RequestRecords.swift
│ │ │ ├── RealmDataBase.swift
│ │ │ ├── ScheduledDatabase.swift
│ │ │ └── Store.swift
│ ├── Assistance
│ │ ├── Algo
│ │ │ └── Stack.swift
│ │ ├── Combine
│ │ │ └── CombineSupport.swift
│ │ ├── Concurrency
│ │ │ ├── Actor+Support.swift
│ │ │ └── Concurrency+Support.swift
│ │ ├── ConstValue&Function
│ │ │ ├── Consts.swift
│ │ │ ├── Path.swift
│ │ │ ├── PublicFunctions.swift
│ │ │ └── UIRelevance.swift
│ │ ├── Extensions
│ │ │ ├── Array+Extensions.swift
│ │ │ ├── CollectionType+Extension.swift
│ │ │ ├── Dictionary+Extension.swift
│ │ │ ├── Double+Extension.swift
│ │ │ ├── NSDate+Extentsions.swift
│ │ │ ├── String+Extensions.swift
│ │ │ ├── UIColor+Extensions.swift
│ │ │ └── UIView+Extensions.swift
│ │ └── Kits
│ │ │ ├── Atomic
│ │ │ ├── Atomic.swift
│ │ │ └── RwQueue.swift
│ │ │ ├── SystemLog
│ │ │ └── LogInterface.swift
│ │ │ └── TextInputLimiter
│ │ │ └── TextInputLimiter.swift
│ ├── Componentize
│ │ ├── Common
│ │ │ └── Consts+Componentize.swift
│ │ ├── Module
│ │ │ ├── Initiator+Utils.swift
│ │ │ ├── Initiator.swift
│ │ │ ├── ModuleManager.swift
│ │ │ └── SessionModule.swift
│ │ └── Navigation
│ │ │ ├── LinkParser.swift
│ │ │ ├── Navigation+PageGroup.swift
│ │ │ ├── Navigation+Private.swift
│ │ │ ├── Navigation.swift
│ │ │ ├── NavigationUtils.swift
│ │ │ └── README-Navigation.md
│ ├── Networking
│ │ ├── API
│ │ │ ├── API+Concurrency.swift
│ │ │ ├── API.swift
│ │ │ ├── ApiDelegate.swift
│ │ │ ├── ApiRouter.swift
│ │ │ └── ResponseSerializer.swift
│ │ ├── Generators
│ │ │ ├── Errors.swift
│ │ │ └── KMRequestGenerator.swift
│ │ ├── Protocols
│ │ │ ├── ApiCallbackProtocol.swift
│ │ │ └── ApiInfoProtocol.swift
│ │ └── Server
│ │ │ └── Server.swift
│ ├── Persistance
│ │ ├── DatabaseManager.swift
│ │ ├── DefaultDatabase.swift
│ │ └── Protocols
│ │ │ ├── DatabaseCommand.swift
│ │ │ └── KMPersistance.swift
│ ├── RxExtension
│ │ ├── DelayWorker.swift
│ │ ├── ProducerQueue.swift
│ │ └── RxExtension.swift
│ ├── Testable
│ │ ├── ModuleManager+Testable.swift
│ │ └── Networking
│ │ │ └── API+Testable.swift
│ └── Tests
│ │ ├── APITests.swift
│ │ ├── AssistanceTests.swift
│ │ ├── ConcurrencySupportTests.swift
│ │ ├── InitiatorTests.swift
│ │ └── RxTests.swift
├── Demo
│ ├── ApiManager
│ │ ├── BaseAPI.swift
│ │ ├── TestAPI.swift
│ │ └── TestFailureAPI.swift
│ ├── Appearance
│ │ ├── Theme.swift
│ │ ├── ThemeTestViewController.swift
│ │ └── ThemeTestViewController.xib
│ ├── CommonModule.swift
│ ├── GlobalImports.swift
│ ├── InfiniteTableView
│ │ └── InfiniteTableViewController.swift
│ ├── Model
│ │ ├── UserModel.swift
│ │ └── Video.swift
│ ├── ModulesRegistery.plist
│ ├── Persistance
│ │ ├── RealmTests.swift
│ │ └── UserTable.swift
│ ├── Services
│ │ ├── NotificationService.swift
│ │ └── UserService.swift
│ ├── UIDectector.swift
│ └── ViewController
│ │ ├── Auth
│ │ ├── AuthTestViewController.swift
│ │ └── AuthTestViewController.xib
│ │ ├── Code Block
│ │ ├── CodeBlockEditorViewController.swift
│ │ ├── CodeBlockEditorViewController.xib
│ │ ├── SALayoutManager.swift
│ │ ├── SATextStorage.swift
│ │ └── SATextView.swift
│ │ ├── Combine
│ │ ├── Combine.swift
│ │ ├── CombineTestsViewController.swift
│ │ └── CombineTestsViewController.xib
│ │ ├── Concurrency
│ │ ├── ConcurrencyTestViewController.swift
│ │ └── ConcurrencyTestViewController.xib
│ │ ├── DataCenter
│ │ ├── DataCenterTestViewController.swift
│ │ ├── DataCenterTestViewController.xib
│ │ ├── TestObjModifyVC.swift
│ │ └── TestObjModifyVC.xib
│ │ ├── InternalTestVC.swift
│ │ ├── Navigation
│ │ └── HomeNavigationHandler.swift
│ │ └── ViewController.swift
├── Info.plist
├── Resource
│ └── swiftArchitecture-Bridging-Header.h
├── SceneDelegate.swift
└── swiftArchitecture.xcdatamodeld
│ ├── .xccurrentversion
│ └── swiftArchitecture.xcdatamodel
│ └── contents
├── pod_publish.sh
└── swiftArchitecture.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
└── IDEWorkspaceChecks.plist
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 | *.DS_Store
20 |
21 | # CocoaPods
22 | #
23 | # We recommend against adding the Pods directory to your .gitignore. However
24 | # you should judge for yourself, the pros and cons are mentioned at:
25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
26 | #
27 |
28 | Pods/
29 |
30 |
31 | # Carthage
32 | #
33 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
34 | # Carthage/Checkouts
35 |
36 | Carthage/Build
37 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ComponentizeDemo/Application/Application.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint UploadAcceleratorSDK.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'Application'
11 | s.version = '0.0.1'
12 | s.summary = 'Breezy architecture in Swift for building iOS applications.'
13 | s.description = <<-DESC
14 | * Breezy architecture in Swift for building iOS applications. It offers lots of functions which simple and easy to use for developer.
15 | DESC
16 | s.homepage = 'https://github.com/Mioke/SwiftArchitectureWithPOP'
17 | # s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'Mioke Klein' => 'mioke0428@gmail.com' }
19 | s.source = { :git => 'https://github.com/Mioke/SwiftArchitectureWithPOP.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '11.0'
22 | s.swift_versions = '5.0'
23 |
24 | # s.frameworks = 'UIKit', 'Foundation'
25 | # s.libraries = 'c++', 'sqlite3'
26 |
27 | s.source_files = 'Source/**/*.swift'
28 |
29 | s.dependency 'AuthProtocol'
30 |
31 | s.dependency 'ApplicationProtocol'
32 | s.dependency 'MIOSwiftyArchitecture'
33 |
34 | # s.xcconfig = { "SWIFT_OBJC_BRIDGING_HEADER" => "SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h" }
35 | # s.module_map = 'SwiftyArchitecture/Resource/module.modulemap'
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Application/ApplicationProtocol.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint UploadAcceleratorSDK.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'ApplicationProtocol'
11 | s.version = '0.0.1'
12 | s.summary = 'Breezy architecture in Swift for building iOS applications.'
13 | s.description = <<-DESC
14 | * Breezy architecture in Swift for building iOS applications. It offers lots of functions which simple and easy to use for developer.
15 | DESC
16 | s.homepage = 'https://github.com/Mioke/SwiftArchitectureWithPOP'
17 | # s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'Mioke Klein' => 'mioke0428@gmail.com' }
19 | s.source = { :git => 'https://github.com/Mioke/SwiftArchitectureWithPOP.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '11.0'
22 | s.swift_versions = '5.0'
23 |
24 | # s.frameworks = 'UIKit', 'Foundation'
25 | # s.libraries = 'c++', 'sqlite3'
26 |
27 | s.source_files = 'Protocol/**/*.swift'
28 |
29 | s.dependency 'MIOSwiftyArchitecture'
30 |
31 | # s.xcconfig = { "SWIFT_OBJC_BRIDGING_HEADER" => "SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h" }
32 | # s.module_map = 'SwiftyArchitecture/Resource/module.modulemap'
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Application/Protocol/ApplicationProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MIOSwiftyArchitecture
3 |
4 | public protocol ApplicationProtocol {
5 | func startApplication()
6 | }
7 |
8 | public extension ModuleIdentifier {
9 | static let application: ModuleIdentifier = "com.klein.module.application"
10 | }
11 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Application/Source/Application.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MIOSwiftyArchitecture
3 | import ApplicationProtocol
4 | import AuthProtocol
5 | import RxSwift
6 |
7 | class ApplicationModule: SessionModuleProtocol, ApplicationProtocol {
8 |
9 | static func sessionDidAuthenticate(with userID: String) {
10 | KitLogger.info()
11 | }
12 |
13 | static func sessionDidDeauthenticate(with userID: String) {
14 | KitLogger.info()
15 | }
16 |
17 | static var moduleIdentifier: ModuleIdentifier {
18 | return .application
19 | }
20 | required init() { }
21 |
22 | let disposeBag = DisposeBag()
23 |
24 | func moduleDidLoad(with manager: ModuleManager) {
25 | if let user = manager.session?.userID {
26 | print(user)
27 | }
28 | }
29 |
30 | func startApplication() {
31 | // AppContext.standard
32 | // .setup()
33 | // .subscribe { finished in
34 | // print(AppContext.standard.previousLaunchedUserId as Any)
35 | // guard let authModule = try? self.moduleManager?.bridge.resolve(.auth) as? AuthServiceProtocol else {
36 | // fatalError()
37 | // }
38 | // if let _ = AppContext.standard.previousLaunchedUserId {
39 | // authModule.refreshAuthenticationIfNeeded(completion: { user in
40 | // print(user)
41 | // })
42 | // }
43 | // }
44 | // .disposed(by: disposeBag)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Auth/Auth.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint UploadAcceleratorSDK.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'Auth'
11 | s.version = '0.0.1'
12 | s.summary = 'Breezy architecture in Swift for building iOS applications.'
13 | s.description = <<-DESC
14 | * Breezy architecture in Swift for building iOS applications. It offers lots of functions which simple and easy to use for developer.
15 | DESC
16 | s.homepage = 'https://github.com/Mioke/SwiftArchitectureWithPOP'
17 | # s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'Mioke Klein' => 'mioke0428@gmail.com' }
19 | s.source = { :git => 'https://github.com/Mioke/SwiftArchitectureWithPOP.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '11.0'
22 | s.swift_versions = '5.0'
23 |
24 | # s.frameworks = 'UIKit', 'Foundation'
25 | # s.libraries = 'c++', 'sqlite3'
26 |
27 | s.source_files = 'Source/**/*.swift'
28 |
29 | s.dependency 'AuthProtocol'
30 | s.dependency 'MIOSwiftyArchitecture'
31 |
32 | s.dependency 'ApplicationProtocol'
33 |
34 | # s.xcconfig = { "SWIFT_OBJC_BRIDGING_HEADER" => "SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h" }
35 | # s.module_map = 'SwiftyArchitecture/Resource/module.modulemap'
36 |
37 | end
38 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Auth/AuthProtocol.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint UploadAcceleratorSDK.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'AuthProtocol'
11 | s.version = '0.0.1'
12 | s.summary = 'Breezy architecture in Swift for building iOS applications.'
13 | s.description = <<-DESC
14 | * Breezy architecture in Swift for building iOS applications. It offers lots of functions which simple and easy to use for developer.
15 | DESC
16 | s.homepage = 'https://github.com/Mioke/SwiftArchitectureWithPOP'
17 | # s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'Mioke Klein' => 'mioke0428@gmail.com' }
19 | s.source = { :git => 'https://github.com/Mioke/SwiftArchitectureWithPOP.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '11.0'
22 | s.swift_versions = '5.0'
23 |
24 | # s.frameworks = 'UIKit', 'Foundation'
25 | # s.libraries = 'c++', 'sqlite3'
26 |
27 | s.source_files = 'Protocol/**/*.swift'
28 |
29 | s.dependency 'MIOSwiftyArchitecture'
30 |
31 | # s.xcconfig = { "SWIFT_OBJC_BRIDGING_HEADER" => "SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h" }
32 | # s.module_map = 'SwiftyArchitecture/Resource/module.modulemap'
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Auth/Protocol/AuthProtocol.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MIOSwiftyArchitecture
3 | import RxSwift
4 |
5 | public class TestUser: UserProtocol, Codable {
6 |
7 | public var id: String
8 | public var age: Int
9 | public var token: String
10 | public var expiration: Date?
11 |
12 | public static let modelVersion: Int = 1
13 |
14 | public enum CodingKeys: CodingKey {
15 | case id
16 | case age
17 | case token
18 | case expiration
19 | }
20 |
21 | public init(id: String, age: Int, token: String) {
22 | self.id = id
23 | self.age = age
24 | self.token = token
25 | self.expiration = Date().offsetWeek(1)
26 | }
27 | }
28 |
29 | extension TestUser {
30 | public var customDebugDescription: String {
31 | return "id - \(id), age - \(age)\ntoken: \(token)"
32 | }
33 | }
34 |
35 | public protocol AuthServiceProtocol {
36 | func authenticate(completion: @escaping (TestUser) -> ()) throws
37 | func deauthenticate(completion: @escaping (Error?) -> Void)
38 | func refreshAuthenticationIfNeeded(completion: @escaping (TestUser) -> ()) -> Void
39 | }
40 |
41 | public extension ModuleIdentifier {
42 | static let auth: ModuleIdentifier = "com.klein.module.auth"
43 | }
44 |
--------------------------------------------------------------------------------
/ComponentizeDemo/Auth/Source/Auth.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MIOSwiftyArchitecture
3 | import AuthProtocol
4 |
5 | class AuthModule: ModuleProtocol, AuthServiceProtocol {
6 |
7 | static var moduleIdentifier: ModuleIdentifier {
8 | return .auth
9 | }
10 | required init() { }
11 |
12 | func moduleDidLoad(with manager: ModuleManager) {
13 |
14 | }
15 |
16 | func authenticate(completion: @escaping (TestUser) -> ()) throws {
17 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
18 | let user = TestUser(id: "15780", age: 25, token: self.genRandomToken())
19 | ModuleManager.default.beginUserSession(with: user.id)
20 | self.markAsReleasing()
21 | completion(user)
22 | }
23 | }
24 |
25 | func genRandomToken() -> String {
26 | let len = 12
27 | let codec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-="
28 |
29 | return (0.. ()) {
36 | // if let previousUID = AppContext.standard.previousLaunchedUserId {
37 | // let previousUser = User(id: previousUID)
38 | // let previousContext = AppContext(user: previousUser)
39 | // // get token from previous context data center, like:
40 | // // let credential = previousContext.store.object(with: previousUID, type: Credential.Type)
41 | // print(previousContext)
42 | // // and then do some refresh token logic here.
43 | // }
44 | }
45 |
46 | func deauthenticate(completion: @escaping (Error?) -> Void) {
47 |
48 | }
49 | }
50 |
51 | extension AuthModule: ModuleInitiatorProtocol {
52 | static var identifier: String { moduleIdentifier }
53 | static var priority: Initiator.Priority { .high }
54 | static var dependencies: [String] { [] }
55 | static var operation: Initiator.Operation {
56 | return {
57 | print("Auth module initiator operation running.")
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Klein Mioke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/MIOSwiftyArchitecture.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint UploadAcceleratorSDK.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'MIOSwiftyArchitecture'
11 | s.version = '2.0.1'
12 | s.summary = 'Breezy architecture in Swift for building iOS applications.'
13 | s.description = <<-DESC
14 | * Breezy architecture in Swift for building iOS applications. It offers lots of functions which simple and easy to use for developer.
15 | DESC
16 | s.homepage = 'https://github.com/Mioke/SwiftArchitectureWithPOP'
17 | s.license = { :type => 'MIT', :file => 'LICENSE' }
18 | s.author = { 'Mioke Klein' => 'mioke0428@gmail.com' }
19 | s.source = { :git => 'https://github.com/Mioke/SwiftArchitectureWithPOP.git', :tag => s.version.to_s }
20 |
21 | s.ios.deployment_target = '13.0'
22 | s.swift_versions = '5'
23 |
24 | # s.frameworks = 'UIKit', 'Foundation'
25 | # s.libraries = 'c++', 'sqlite3'
26 |
27 | # s.source_files = 'SwiftyArchitecture/Base/**/*.swift'
28 | s.default_subspecs = 'Assistance', 'Networking', 'RxExtension', 'AppDock', 'Componentize'
29 |
30 | s.subspec 'Assistance' do |ss|
31 | ss.frameworks = 'UIKit', 'Foundation'
32 | ss.source_files = 'SwiftyArchitecture/Base/Assistance/**/*.swift'
33 | end
34 |
35 | s.subspec 'Networking' do |ss|
36 | ss.frameworks = 'UIKit', 'Foundation'
37 | ss.source_files = 'SwiftyArchitecture/Base/Networking/**/*.swift'
38 | ss.dependency 'Alamofire', '~> 5.4'
39 | ss.dependency 'ObjectMapper', '~> 4.2'
40 | ss.dependency 'MIOSwiftyArchitecture/Assistance'
41 | end
42 |
43 | # s.subspec 'Persistance' do |ss|
44 | # ss.frameworks = 'UIKit', 'Foundation'
45 | # ss.source_files = 'SwiftyArchitecture/Base/Persistance/**/*.swift'
46 | # ss.dependency 'FMDB'
47 | # ss.dependency 'MIOSwiftyArchitecture/Assistance'
48 | # end
49 |
50 | s.subspec 'RxExtension' do |ss|
51 | ss.source_files = 'SwiftyArchitecture/Base/RxExtension/**/*.swift'
52 | ss.dependency 'MIOSwiftyArchitecture/Assistance'
53 | ss.dependency 'MIOSwiftyArchitecture/Networking'
54 | ss.dependency 'RxSwift', '~> 6.2'
55 | end
56 |
57 | s.subspec 'AppDock' do |ss|
58 | ss.source_files = 'SwiftyArchitecture/Base/AppDock/**/*.swift'
59 | ss.dependency 'MIOSwiftyArchitecture/Assistance'
60 | ss.dependency 'MIOSwiftyArchitecture/Networking'
61 | ss.dependency 'MIOSwiftyArchitecture/RxExtension'
62 | ss.dependency 'RxSwift', '~> 6.2'
63 | ss.dependency 'RxCocoa', '~> 6.2'
64 | ss.dependency 'RxRealm' # 5.0.4
65 | ss.dependency 'RealmSwift' # 10.45.2
66 | ss.dependency "Realm"
67 | end
68 |
69 | s.subspec 'Componentize' do |ss|
70 | ss.source_files = 'SwiftyArchitecture/Base/Componentize/**/*.swift'
71 | ss.dependency 'MIOSwiftyArchitecture/Assistance'
72 | ss.dependency 'Swinject', '~> 2.8'
73 | ss.dependency 'RxSwift', '~> 6.2'
74 | end
75 |
76 | s.subspec 'Testable' do |ss|
77 | ss.source_files = 'SwiftyArchitecture/Base/Testable/**/*.swift'
78 | ss.dependency 'MIOSwiftyArchitecture/Assistance'
79 | ss.dependency 'MIOSwiftyArchitecture/Networking'
80 | ss.dependency 'MIOSwiftyArchitecture/RxExtension'
81 | ss.dependency 'MIOSwiftyArchitecture/AppDock'
82 | ss.dependency 'MIOSwiftyArchitecture/Componentize'
83 | end
84 |
85 | s.test_spec 'Tests' do |test_spec|
86 | test_spec.source_files = 'SwiftyArchitecture/Base/Tests/**/*.swift'
87 | # test_spec.dependency 'OCMock' # This dependency will only be linked with your tests.
88 | test_spec.dependency 'MIOSwiftyArchitecture/Assistance'
89 | test_spec.dependency 'MIOSwiftyArchitecture/Networking'
90 | test_spec.dependency 'MIOSwiftyArchitecture/RxExtension'
91 | test_spec.dependency 'MIOSwiftyArchitecture/AppDock'
92 | test_spec.dependency 'MIOSwiftyArchitecture/Componentize'
93 | test_spec.dependency 'MIOSwiftyArchitecture/Testable'
94 |
95 | test_spec.dependency 'RxSwift', '~> 6.2'
96 | end
97 |
98 | # s.xcconfig = { "SWIFT_OBJC_BRIDGING_HEADER" => "SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h" }
99 | # s.module_map = 'SwiftyArchitecture/Resource/module.modulemap'
100 |
101 | end
102 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "alamofire",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/Alamofire/Alamofire.git",
7 | "state" : {
8 | "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad",
9 | "version" : "5.8.1"
10 | }
11 | },
12 | {
13 | "identity" : "objectmapper",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/tristanhimmelman/ObjectMapper.git",
16 | "state" : {
17 | "revision" : "0b96a734de3ea1c87374ae677064f86adb0716ec",
18 | "version" : "4.2.0"
19 | }
20 | },
21 | {
22 | "identity" : "realm-core",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/realm/realm-core.git",
25 | "state" : {
26 | "revision" : "7227d6a447821c28895daa099b6c7cd4c99d461b",
27 | "version" : "13.25.1"
28 | }
29 | },
30 | {
31 | "identity" : "realm-swift",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/realm/realm-swift.git",
34 | "state" : {
35 | "revision" : "836cc4b8619886f979f8961c3f592a82b0741591",
36 | "version" : "10.45.3"
37 | }
38 | },
39 | {
40 | "identity" : "rxrealm",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/Mioke/RxRealm.git",
43 | "state" : {
44 | "branch" : "main",
45 | "revision" : "f930c360fef32372bc839c6939d4e0ee8f8276ac"
46 | }
47 | },
48 | {
49 | "identity" : "rxswift",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/ReactiveX/RxSwift.git",
52 | "state" : {
53 | "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
54 | "version" : "6.6.0"
55 | }
56 | }
57 | ],
58 | "version" : 2
59 | }
60 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftyAppArch",
8 | platforms: [.iOS(.v13)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "SwiftyAppArch",
13 | targets: ["SwiftyAppArch"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/realm/realm-swift.git", from: "10.45.2"),
17 | .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
18 | .package(url: "https://github.com/tristanhimmelman/ObjectMapper.git", from: "4.2.0"),
19 | .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.6.0"),
20 | // .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.4"),
21 | .package(url: "https://github.com/Mioke/RxRealm.git", branch: "main"),
22 | ],
23 | targets: [
24 | // Targets are the basic building blocks of a package, defining a module or a test suite.
25 | // Targets can depend on other targets in this package and products from dependencies.
26 | .target(
27 | name: "SwiftyAppArch",
28 | dependencies: [
29 | .product(name: "RealmSwift", package: "realm-swift"),
30 | .product(name: "Alamofire", package: "Alamofire"),
31 | .product(name: "ObjectMapper", package: "ObjectMapper"),
32 | .product(name: "RxSwift", package: "RxSwift"),
33 | .product(name: "RxCocoa", package: "RxSwift"),
34 | .product(name: "RxRealm", package: "RxRealm"),
35 | ],
36 | path: "./SwiftyArchitecture/Base",
37 | exclude: ["Persistance", "Tests"]),
38 | // .testTarget(
39 | // name: "SwiftyAppArchTests",
40 | // dependencies: ["SwiftyAppArch"]),
41 | ],
42 | swiftLanguageVersions: [.v5]
43 | )
44 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 | platform :ios, '15.0'
3 | use_frameworks!
4 | install! 'cocoapods', :preserve_pod_file_structure => true
5 |
6 | target "SAD" do
7 | # networking
8 | pod 'Alamofire'
9 |
10 | # rx
11 | pod 'RxSwift', '~> 6.0'
12 | pod 'RxDataSources'
13 |
14 | # persistence
15 |
16 | # RxRealm not up-to-date, so we have to modify it ourselves.
17 | pod 'RxRealm', :git => 'https://github.com/Mioke/RxRealm.git', :branch => 'main'
18 | pod 'RealmSwift'
19 |
20 | pod 'MIOSwiftyArchitecture', :path => './', :testspecs => ['Tests']
21 | pod 'MIOSwiftyArchitecture/Testable', :path => './'
22 |
23 |
24 |
25 | # componentization
26 | pod 'Application', :path => './ComponentizeDemo/Application'
27 | pod 'ApplicationProtocol', :path => './ComponentizeDemo/Application'
28 |
29 | pod 'Auth', :path => './ComponentizeDemo/Auth'
30 | pod 'AuthProtocol', :path => './ComponentizeDemo/Auth'
31 |
32 | # pod 'Notification', :path => './ComponentizeDemo/Notification'
33 | # pod 'NotificationProtocol', :path => './ComponentizeDemo/Notification'
34 |
35 | pod 'Swinject'
36 | pod 'SwinjectAutoregistration'
37 |
38 | # other utils
39 | pod 'SnapKit'
40 | pod 'FLEX'
41 |
42 | target 'SwiftArchitectureTests' do
43 |
44 | end
45 |
46 | target 'SwiftArchitectureUITests' do
47 |
48 | end
49 | end
50 |
51 | # pod 'FMDB/FTS' # FMDB with FTS
52 | # pod 'FMDB/standalone' # FMDB with latest SQLite amalgamation source
53 | # pod 'FMDB/standalone/FTS' # FMDB with latest SQLite amalgamation source and FTS
54 | # pod 'FMDB/SQLCipher' # FMDB with SQLCipher
55 |
56 |
57 | post_install do |installer_representation|
58 | installer_representation.pods_project.targets.each do |target|
59 |
60 | target.build_configurations.each do |config|
61 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/SwiftArchitecture.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftArchitecture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftArchitecture.xcodeproj/xcshareddata/xcschemes/SAD.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/SwiftArchitecture.xcodeproj/xcshareddata/xcschemes/SwiftArchitectureUITests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
29 |
35 |
36 |
37 |
38 |
39 |
49 |
50 |
56 |
57 |
59 |
60 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/SwiftArchitecture.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftArchitectureTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/SwiftArchitectureTests/swiftArchitectureTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // swiftArchitectureTests.swift
3 | // swiftArchitectureTests
4 | //
5 | // Created by Klein Mioke on 15/11/23.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SAD
11 | @testable import MIOSwiftyArchitecture
12 | @testable import Auth
13 | @testable import AuthProtocol
14 |
15 | class swiftArchitectureTests: XCTestCase {
16 |
17 | override func setUp() {
18 | super.setUp()
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 |
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 | func testExample() {
29 | // This is an example of a functional test case.
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testPerformanceExample() {
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 | }
41 |
--------------------------------------------------------------------------------
/SwiftArchitectureUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/SwiftArchitectureUITests/MockComponents.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockComponents.swift
3 | // SwiftArchitectureUITests
4 | //
5 | // Created by KelanJiang on 2022/5/24.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import MIOSwiftyArchitecture
11 | import AuthProtocol
12 | import Auth
13 | import ApplicationProtocol
14 | import Application
15 |
16 |
17 | class MockAuth: ModuleProtocol {
18 |
19 | func moduleDidLoad(with manager: ModuleManager) {
20 | // mocked module will never running this method.
21 | }
22 |
23 | static var moduleIdentifier: ModuleIdentifier {
24 | return .auth
25 | }
26 |
27 | required init() {
28 |
29 | }
30 | }
31 |
32 | extension MockAuth: AuthServiceProtocol {
33 |
34 | func authenticate(completion: @escaping (User) -> ()) throws {
35 | let user = User(id: "15780")
36 | ModuleManager.default.beginUserSession(with: user.id)
37 | self.markAsReleasing()
38 | }
39 |
40 | func refreshAuthenticationIfNeeded(completion: @escaping (User) -> ()) {
41 | if let previousUID = AppContext.standard.previousLaunchedUserId {
42 | let previousUser = User(id: previousUID)
43 | let previousContext = AppContext(user: previousUser)
44 | // get token from previous context data center, like:
45 | // let credential = previousContext.store.object(with: previousUID, type: Credential.Type)
46 | print(previousContext)
47 | // and then do some refresh token logic here.
48 | }
49 | }
50 |
51 | func deauthenticate(completion: @escaping (Error?) -> Void) {
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/SwiftArchitectureUITests/swiftArchitectureUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // swiftArchitectureUITests.swift
3 | // swiftArchitectureUITests
4 | //
5 | // Created by Klein Mioke on 15/11/23.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import MIOSwiftyArchitecture
11 | import ApplicationProtocol
12 |
13 | class swiftArchitectureUITests: XCTestCase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 |
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 |
20 | // In UI tests it is usually best to stop immediately when a failure occurs.
21 | continueAfterFailure = false
22 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
23 | XCUIApplication().launch()
24 |
25 | // 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.
26 | let auth = MockAuth()
27 | guard ModuleManager.default.bridge.inject(instance: auth) else {
28 | fatalError()
29 | }
30 | }
31 |
32 | override func tearDown() {
33 | // Put teardown code here. This method is called after the invocation of each test method in the class.
34 | super.tearDown()
35 | }
36 |
37 | func testExample() {
38 | // Use recording to get started writing UI tests.
39 | // Use XCTAssert and related functions to verify your tests produce the correct results.
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/AppLinkRegistery.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Modules
6 |
7 |
8 | id
9 | home
10 | handler
11 | SAD.HomeNavigationHandler
12 | paths
13 |
14 | messages
15 |
16 | target
17 | SAD.InfiniteTableViewController
18 |
19 | data-center
20 |
21 | target
22 | SAD.DataCenterTestViewController
23 |
24 | theme
25 |
26 | isAccessibleExternally
27 |
28 | target
29 | SAD.ThemeTestViewController
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Assets.xcassets/launch_swifty_icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "iconfinder_332_Swift_logo_4375493.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Assets.xcassets/launch_swifty_icon.imageset/iconfinder_332_Swift_logo_4375493.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mioke/SwiftyArchitecture/445c9b450057c7681089bd7ca179df6eb04481d3/SwiftyArchitecture/Assets.xcassets/launch_swifty_icon.imageset/iconfinder_332_Swift_logo_4375493.png
--------------------------------------------------------------------------------
/SwiftyArchitecture/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 |
29 |
30 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/AppContext/AppDockConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDockConstants.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/2/17.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Consts {
11 | static let appDockDomain = "com.mioke.swiftyarchitecture.appdock"
12 | }
13 |
14 | public extension KitErrors {
15 | private static var noDelegate: Info {
16 | .init(code: .noDelegate, message: "The delegate of this function hasn't been set yet.")
17 | }
18 | static let noDelegateError: NSError = error(domain: Consts.appDockDomain, info: noDelegate)
19 |
20 |
21 | private static var alreadyAuthenticatedInfo: Info {
22 | return .init(code: .alreadyAuthenticated, message: "AppContext already in a custom user context, can't authenticate a new user now.")
23 | }
24 |
25 | static var alreadyAuthenticated : NSError {
26 | return error(domain: Consts.appDockDomain, info: alreadyAuthenticatedInfo)
27 | }
28 |
29 | private static var notFoundInfo: Info {
30 | .init(code: .notFound, message: "The record can't be found in Store.")
31 | }
32 |
33 | /// File or data record cannot be found in the sandbox or database.
34 | static var notFound: NSError {
35 | error(domain: Consts.appDockDomain, info: notFoundInfo)
36 | }
37 | }
38 |
39 |
40 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/AppContext/UserContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserContext.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/2/16.
6 | //
7 |
8 | import UIKit
9 | import RxSwift
10 | import RealmSwift
11 |
12 | public protocol UserProtocol: DataConvertible {
13 | /// The identifier of a user, the primary key of the conversion data of the user model.
14 | var id: String { get set }
15 |
16 | /// Your customized user model version. Because of sometimes your user model will got updated when your code grows,
17 | /// like from `Userv1` to `Userv2` it will add more properties in it, so when the model changes, please update this
18 | /// version number to a new one (larger), and the setup process will let you go into a migration process.
19 | static var modelVersion: Int { get }
20 | }
21 |
22 | /// The authentication states.
23 | public enum AuthState {
24 | /// Not logged in, has no session or user data.
25 | case unauthenticated
26 | /// Logged in and stored all the newest user data.
27 | case authenticated
28 | /// Has previous user's data but the application has already restarted, needs to be refreshed.
29 | case presession
30 | }
31 |
32 | public protocol AuthControllerDelegate: AnyObject {
33 | /// Called when Standard AppContext is demanded to trigger a new user login process.
34 | /// - Returns: The authentication process.
35 | func authenticate() -> Observable
36 |
37 | /// When the Standard AppContext found a user context cache in sandbox, or `refreshAuthentication` is called,
38 | /// this function will be called to decide whether the user context should be refreshed.
39 | /// - Parameters:
40 | /// - user: The previous user data.
41 | /// - isStartup: is called from application start process.
42 | /// - Returns: A decission of whether should refresh the user data.
43 | func shouldRefreshAuthentication(with user: UserProtocol, isStartup: Bool) -> Bool
44 |
45 | /// Called when there is needed to refresh the current user context.
46 | /// - Parameter user: The old user data.
47 | /// - Returns: The refresh process and return a new user in the end.
48 | func refreshAuthentication(with user: UserProtocol) -> Observable
49 |
50 | /// Called when the AppContext is logging out.
51 | /// - Returns: The deauthentication process.
52 | func deauthenticate() -> ObservableSignal
53 | }
54 |
55 | public class AuthController: NSObject {
56 |
57 | public weak var delegate: AuthControllerDelegate?
58 |
59 | init(delegate: AuthControllerDelegate?) {
60 | self.delegate = delegate
61 | }
62 | }
63 |
64 | class DefaultUser: UserProtocol, Codable {
65 | static let _id = "__standard__"
66 | static let modelVersion: Int = 1
67 |
68 | var id: String = DefaultUser._id
69 |
70 | enum CodingKeys: CodingKey {
71 | case id
72 | }
73 | }
74 |
75 | class _UserMeta: Object {
76 | @Persisted(primaryKey: true) var key: Int
77 | @Persisted var currentUserId: String
78 | @Persisted var isLoggedIn: Bool
79 | }
80 |
81 | class _UserData: Object {
82 | @Persisted(primaryKey: true) var userId: String
83 | @Persisted var codableData: Foundation.Data
84 | @Persisted var version: Int
85 | }
86 |
87 | class _User {
88 | struct Wrapper: Codable {
89 | var `class`: String
90 | var data: Foundation.Data
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/DataCenter/Accessor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Accessor.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2020/4/10.
6 | // Copyright © 2020 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxRealm
12 | import RealmSwift
13 |
14 | public class Accessor {
15 |
16 | public static func context(_ type: Store.ContextType) -> Self {
17 | return self.init(contextType: type)
18 | }
19 |
20 | let contextType: Store.ContextType
21 |
22 | internal required init(contextType: Store.ContextType) {
23 | self.contextType = contextType
24 | }
25 |
26 | private var store: ObservableDataBase {
27 | return AppContext.current.store.context(contextType)
28 | }
29 |
30 | public var all: Observable<[T]> {
31 | return store.objects(with: T.self).map { $0.toArray() }
32 | }
33 |
34 | public func object(with key: KeyType) -> Observable {
35 | return store.object(with: key, type: T.self)
36 | }
37 |
38 | public func objects(with predicate: NSPredicate) -> Observable<[T]> {
39 | return store.objects(with: T.self, predicate: predicate).map { $0.toArray() }
40 | }
41 |
42 | public func objects(with query: @escaping (Query) -> Query) -> Observable<[T]> {
43 | return store.objects(with: T.self, where: query).map { $0.toArray() }
44 | }
45 |
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/DataCenter/Private/Requests/RequestRecords.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestRecords.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by Mioke Klein on 2021/5/7.
6 | //
7 |
8 | import UIKit
9 |
10 | struct RequestRecordNode {
11 | let requestTime: CFAbsoluteTime
12 | let freshness: RequestFreshness
13 |
14 | var expirationDate: CFAbsoluteTime {
15 | guard case .seconds(let seconds) = freshness else {
16 | return 0
17 | }
18 | return requestTime + CFAbsoluteTime(seconds)
19 | }
20 | }
21 |
22 | internal class RequestRecords: NSObject {
23 |
24 | @ThreadSafe
25 | var map: [String: RequestRecordNode] = [:]
26 |
27 | func key(ofType t: Request) -> String {
28 | var key = String(describing: type(of: t.self))
29 | if let params = t.params,
30 | let data = try? JSONSerialization.data(withJSONObject: params, options: []) {
31 | key += ".\(data.hashValue)"
32 | }
33 | return key
34 | }
35 |
36 | func last(ofRequest request: Request) -> (key: String, node: RequestRecordNode)? {
37 | let key = self.key(ofType: request)
38 | if let node = map[key] {
39 | return (key, node)
40 | }
41 | return nil
42 | }
43 |
44 | func update(request: Request) -> Void {
45 | let freshness = T.requestFreshness
46 | if case .none = freshness {
47 | return
48 | }
49 | let time = CFAbsoluteTimeGetCurrent()
50 | map[key(ofType: request)] = RequestRecordNode(requestTime: time, freshness: freshness)
51 | }
52 |
53 | func shouldSend(request: Request) -> Bool {
54 | guard let info = self.last(ofRequest: request) else {
55 | return true
56 | }
57 | let should = info.node.expirationDate < CFAbsoluteTimeGetCurrent()
58 | if should {
59 | map[info.key] = nil
60 | }
61 | return should
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/DataCenter/RealmDataBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RealmDataBase.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2020/4/10.
6 | // Copyright © 2020 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxRealm
11 | import RxSwift
12 | import RealmSwift
13 |
14 | public typealias RealmMigrationBlock = RealmSwift.MigrationBlock
15 |
16 | public final class RealmDataBase: NSObject {
17 |
18 | public enum `Type` {
19 | case file
20 | case memory
21 | }
22 |
23 | private var realmConfiguration: Realm.Configuration
24 |
25 | /// The read-write context of Realm.
26 | public var realm: Realm {
27 | get throws { return try currentThreadInstance }
28 | }
29 | // TODO: - Add read-only context and syncing context logic.
30 | // public var readOnly: Realm
31 |
32 | public private(set) var type: RealmDataBase.`Type` = .file
33 |
34 | internal var internalQueue: DispatchQueue = .init(label: Consts.domainPrefix + ".RDB.internal", qos: .default)
35 | internal lazy var internalScheduler: SchedulerType = SerialDispatchQueueScheduler(
36 | queue: internalQueue,
37 | internalSerialQueueName: Consts.domainPrefix + ".RDB.internal")
38 |
39 | /// Create a file type database using an app context's information.
40 | /// - Parameters:
41 | /// - context: The app context contains user information.
42 | /// - migration: The customize migration handler.
43 | public init(location: URL, schemaVersion: UInt64, migration: @escaping RealmMigrationBlock) throws {
44 | self.realmConfiguration = Realm.Configuration(
45 | fileURL: location,
46 | schemaVersion: schemaVersion,
47 | migrationBlock: migration,
48 | deleteRealmIfMigrationNeeded: true
49 | )
50 | }
51 |
52 | init(realmConfiguration: Realm.Configuration) {
53 | self.realmConfiguration = realmConfiguration
54 | super.init()
55 | }
56 |
57 | public static func inMemoryDatabase(appContext context: AppContext) -> RealmDataBase {
58 | var config = Realm.Configuration.defaultConfiguration;
59 | config.inMemoryIdentifier = "RLM.MEMORY.\(context.userId)";
60 | let db = RealmDataBase(realmConfiguration: config)
61 | db.type = .memory
62 | return db
63 | }
64 |
65 | public var currentThreadInstance: Realm {
66 | get throws {
67 | return try Realm(configuration: realmConfiguration)
68 | }
69 | }
70 |
71 | public func invalidate() {
72 | try? realm.invalidate()
73 | }
74 | }
75 |
76 | extension Realm {
77 |
78 | public func safeWrite(_ block: (Realm) throws -> Void) throws -> Void {
79 | if isInWriteTransaction {
80 | try block(self)
81 | try commitWrite()
82 | } else {
83 | try self.write { try block(self) }
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/AppDock/DataCenter/ScheduledDatabase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScheduledDatabase.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/7/26.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol ScheduledDatabase: AnyObject {
11 | var rwQueue: RwQueue { get set }
12 | func write(_ execution: (Self) -> T) -> T
13 | func read(_ execution: (Self) -> T) -> T
14 |
15 | func `async`(write execution: @escaping (Self) -> Void)
16 | func `async`(read execution: @escaping (Self) -> Void)
17 | }
18 |
19 | extension ScheduledDatabase {
20 |
21 | public func write(_ execution: (Self) -> T) -> T {
22 | return rwQueue.syncWrite { execution(self) }
23 | }
24 |
25 | public func read(_ execution: (Self) -> T) -> T {
26 | return rwQueue.syncRead { execution(self) }
27 | }
28 |
29 | public func async(write execution: @escaping (Self) -> Void) {
30 | rwQueue.write { execution(self) }
31 | }
32 |
33 | public func async(read execution: @escaping (Self) -> Void) {
34 | rwQueue.read { execution(self) }
35 | }
36 | }
37 |
38 | // Realm doesn't need to conform to `ScheduledDatabase` because it's thread-safe with MVCC:
39 |
40 | /**
41 | - **Don't lock to read:**
42 | Realm Database's Multiversion Concurrency Control (MVCC) architecture eliminates the need to lock for read operations. The values you read will never be corrupted or in a partially-modified state. You can freely read from realms on any thread without the need for locks or mutexes. Unnecessary locking would be a performance bottleneck since each thread might need to wait its turn before reading.
43 | - **Avoid writes on the UI thread if you write on a background thread:**
44 | You can write to a realm from any thread, but there can be only one writer at a time. Consequently, write transactions block each other. A write on the UI thread may result in your app appearing unresponsive while it waits for a write on a background thread to complete. If you are using Device Sync, avoid writing on the UI thread as Sync writes on a background thread.
45 | - **Don't pass live objects, collections, or realms to other threads:**
46 | Live objects, collections, and realm instances are thread-confined: that is, they are only valid on the thread on which they were created. Practically speaking, this means you cannot pass live instances to other threads. However, Realm Database offers several mechanisms for sharing objects across threads.
47 | */
48 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Algo/Stack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stack.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2023/4/7.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct Stack: Sequence {
11 | private var elements: [Element]
12 |
13 | /// The elements will be popped in the order they appear in the stack
14 | public init(_ sequence: S) where S: Sequence, S.Element == Element {
15 | elements = sequence.reversed()
16 | }
17 |
18 | public init() {
19 | elements = []
20 | }
21 |
22 | public mutating func push(_ element: Element) {
23 | elements.append(element)
24 | }
25 |
26 | public mutating func pop() -> Element? {
27 | return elements.popLast()
28 | }
29 |
30 | public __consuming func makeIterator() -> Stack.Iterator {
31 | return Iterator(iterator: elements.reversed().makeIterator())
32 | }
33 |
34 | public struct Iterator: IteratorProtocol {
35 | private var iterator: Array.Iterator
36 | init(iterator: Array.Iterator) {
37 | self.iterator = iterator
38 | }
39 |
40 | public mutating func next() -> Element? {
41 | return iterator.next()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Combine/CombineSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CombineSupport.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2023/7/12.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 |
12 | @dynamicMemberLookup
13 | public struct CombineContext {
14 | public let base: T
15 |
16 | public init(base: T) {
17 | self.base = base
18 | }
19 |
20 | public subscript(
21 | dynamicMember keyPath: ReferenceWritableKeyPath
22 | ) -> AnyPublisher where T: NSObject {
23 | base.publisher(for: keyPath).eraseToAnyPublisher()
24 | }
25 | }
26 |
27 | public protocol CombineCompatible {
28 | associatedtype CombineBase
29 |
30 | var combine: CombineContext { get set }
31 | static var combine: CombineContext.Type { get set }
32 | }
33 |
34 | public extension CombineCompatible {
35 |
36 | var combine: CombineContext {
37 | get { CombineContext(base: self) }
38 | set { }
39 | }
40 |
41 | static var combine: CombineContext.Type {
42 | get { CombineContext.self }
43 | set { }
44 | }
45 | }
46 |
47 | extension NSObject: CombineCompatible { }
48 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Concurrency/Actor+Support.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Actor+Support.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2024/1/4.
6 | //
7 |
8 | import Foundation
9 |
10 | // Copied from Realm
11 |
12 | @available(macOS 10.15, tvOS 13.0, iOS 13.0, watchOS 6.0, *)
13 | public extension Actor {
14 | func verifier() -> (@Sendable () -> Void) {
15 | #if swift(>=5.9)
16 | // When possible use the official API for actor checking
17 | if #available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, *) {
18 | return {
19 | self.preconditionIsolated()
20 | }
21 | }
22 | #endif
23 |
24 | // This exploits a hole in Swift's type system to construct a function
25 | // which is isolated to the current actor, and then casts away that
26 | // information. This results in runtime warnings/aborts if it's called
27 | // from outside the actor when actor data race checking is enabled.
28 | let fn: () -> Void = { _ = self }
29 | return unsafeBitCast(fn, to: (@Sendable () -> Void).self)
30 | }
31 |
32 | // Asynchronously invoke the given block on the actor. This takes a
33 | // non-sendable function because the function is invoked on the same actor
34 | // it was defined on, and just goes through some hops in between.
35 | nonisolated func invoke(_ fn: @escaping () -> Void) {
36 | let fn = unsafeBitCast(fn, to: (@Sendable () -> Void).self)
37 | Task {
38 | await doInvoke(fn)
39 | }
40 | }
41 |
42 | private func doInvoke(_ fn: @Sendable () -> Void) {
43 | fn()
44 | }
45 |
46 | // A helper to invoke a regular isolated sendable function with this actor
47 | func invoke(_ fn: @Sendable (isolated Self) async throws -> T) async rethrows -> T {
48 | try await fn(self)
49 | }
50 | }
51 |
52 | public extension MainActor {
53 | /// Explicitly mark this function is running in a asynchronized way.
54 | nonisolated static func `async`(fn: @escaping () -> Void) {
55 | MainActor.shared.invoke(fn)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/ConstValue&Function/Consts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 28/06/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | #if swift(<5.5.2)
13 | #error("SwiftyArchitecture only support Swift version >= 5.5.2 for concurrency support purpose.")
14 | #endif
15 |
16 | public struct Consts {
17 | // - Domains
18 | /// The domain prefix of this framework, used in any identifier like networking, queue etc.
19 | public static let domainPrefix = "com.mioke.swiftyarchitecture"
20 | /// The global used or non-specific domain identifier.
21 | public static let defaultDomain = domainPrefix + ".default"
22 | /// The domain of networking kit in this kit.
23 | public static let networkingDomain = domainPrefix + ".networking"
24 |
25 | // - Time
26 | public static let milisecondsPerSecond: UInt64 = 1_000
27 | public static let microsecondsPerSecond: UInt64 = 1_000_000
28 | public static let nanosecondsPerSecond: UInt64 = 1_000_000_000
29 | }
30 |
31 | extension Consts {
32 | public static let kitName = "MIOSwiftyArchitecture"
33 | }
34 |
35 | internal func todo_error() -> NSError {
36 | return NSError(domain: "", code: 777, userInfo: nil)
37 | }
38 |
39 | public class KitErrors {
40 |
41 | public struct Info {
42 | let code: KitErrors.Codes
43 | let message: String?
44 | }
45 |
46 | public static func error(domain: String, info: KitErrors.Info) -> NSError {
47 | return .init(
48 | domain: domain,
49 | code: info.code.rawValue,
50 | userInfo: [NSLocalizedDescriptionKey: info.message ?? ""]
51 | )
52 | }
53 | }
54 |
55 | extension KitErrors {
56 |
57 | public enum Codes: Int {
58 | // general
59 | case noDelegate
60 | case unknown
61 | case deallocated
62 | case todo = 777
63 |
64 | // networking
65 | case responseError = 1000
66 | case apiConstructionFailed
67 |
68 | // persistance
69 | case notFound = 2000
70 |
71 | // app dock
72 | case alreadyAuthenticated = 3000
73 |
74 | // componentize
75 | case graphCycle = 4000
76 | }
77 | }
78 |
79 |
80 | // MARK: - Intnernal errors
81 |
82 | public extension KitErrors {
83 |
84 | private static var deallocatedInfo: Info {
85 | .init(code: .deallocated, message: "The instance has already been deallocated.")
86 | }
87 | static let deallocated: NSError = error(domain: Consts.defaultDomain, info: KitErrors.deallocatedInfo)
88 |
89 | private static var todoInfo: Info {
90 | .init(code: .todo, message: "The author is too lazy to complete this.")
91 | }
92 | static let todo: NSError = error(domain: Consts.defaultDomain, info: KitErrors.todoInfo)
93 |
94 |
95 | private static var unknownErrorInfo: Info {
96 | return .init(code: .unknown, message: "Unknown error")
97 | }
98 |
99 | static var unknown: NSError {
100 | return error(domain: Consts.networkingDomain, info: unknownErrorInfo)
101 | }
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/ConstValue&Function/Path.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Path.swift
3 | // FileMail
4 | //
5 | // Created by yizhong zhuang on 2017/5/15.
6 | // Copyright © 2017年 xlvip. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Foundation
11 |
12 | class Path: NSObject {
13 | static var homePath: String {
14 | return NSHomeDirectory()
15 | }
16 |
17 | static var tempPath: String {
18 | return NSTemporaryDirectory()
19 | }
20 |
21 | static var docPath: String {
22 | let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
23 | return paths.first ?? NSHomeDirectory() + "/Documents"
24 | }
25 |
26 | static var appPath: String {
27 | return Bundle.main.bundlePath
28 | }
29 |
30 | static var libPath: String {
31 | let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
32 | return paths.first ?? NSHomeDirectory() + "/Library"
33 | }
34 |
35 | static var cachePath: String {
36 | let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
37 | return paths.first ?? NSHomeDirectory() + "/Library/Cashes"
38 | }
39 |
40 |
41 | // 创建目录,如果已存在则返回true,不存在创建目录成功返回true,失败返回false
42 | static func createDirIfNeeded(at dirPath: URL) -> Bool {
43 | var isDir = ObjCBool(false)
44 | let exist = FileManager.default.fileExists(atPath: dirPath.absoluteString, isDirectory: &isDir)
45 | if exist && isDir.boolValue { // dir exists
46 | return true
47 | }
48 | do {
49 | try FileManager.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
50 | } catch let error as NSError {
51 | debugPrint("create file dir error:\(error.localizedDescription), url:\(dirPath)")
52 | return false
53 | }
54 |
55 | return true
56 | }
57 |
58 | static func copyFilesFile(from url: String, to dir: String) throws -> Void {
59 | var err: NSError? = nil
60 | copyFilesFile(url: URL(fileURLWithPath: url), toDir: dir) { (error) in
61 | err = error as NSError?
62 | }
63 | if let error = err {
64 | throw error
65 | }
66 | }
67 |
68 | fileprivate static func copyFilesFile(url: URL, toDir: String, completion: (Error?) -> Void) -> Void {
69 | var error: NSError?
70 | let fileCoordinator = NSFileCoordinator.init()
71 | if url.startAccessingSecurityScopedResource() {
72 | url.stopAccessingSecurityScopedResource()
73 | return
74 | }
75 | fileCoordinator.coordinate(
76 | readingItemAt: url,
77 | options: .withoutChanges,
78 | error: &error,
79 | byAccessor: { (url) in
80 | debugPrint("待拷贝文件:\(url.absoluteString)")
81 | let toUrl = URL.init(fileURLWithPath: toDir.appending("/\(url.lastPathComponent)"))
82 | do {
83 | try FileManager.default.copyItem(at: url, to: toUrl)
84 | completion(nil)
85 | } catch let error {
86 | debugPrint("拷贝文件失败:\(error)")
87 | completion(error)
88 | }
89 | debugPrint("stopAccessingSecurityScopedResource:\(url.stopAccessingSecurityScopedResource())")
90 | })
91 | if let err = error {
92 | debugPrint("拷贝files中文件出错:\(err)")
93 | debugPrint("stopAccessingSecurityScopedResource:\(url.stopAccessingSecurityScopedResource())")
94 | completion(err)
95 | }
96 | }
97 | }
98 |
99 | extension FileManager {
100 |
101 | func createDiractoryIfNeeded(at url: URL) throws {
102 | var isDirectory: ObjCBool = false
103 | if fileExists(atPath: url.path, isDirectory: &isDirectory) {
104 | if !isDirectory.boolValue {
105 | // let this throw a `file exists error`
106 | try createDirectory(at: url, withIntermediateDirectories: true)
107 | }
108 | } else {
109 | try createDirectory(at: url, withIntermediateDirectories: true)
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/ConstValue&Function/PublicFunctions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // publicFunctions.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/11/30.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Dispatch
11 |
12 | /**
13 | 代码区块区分.
14 |
15 | - parameter name: 区块功能描述
16 | - parameter closure: 执行功能
17 | */
18 | public func scope(_ name: String, closure: () -> ()) -> Void {
19 | closure()
20 | }
21 |
22 | public func guardOnMainQueue(sync: Bool = false, _ block: @escaping () -> ()) -> Void {
23 | if Thread.current.isMainThread {
24 | block()
25 | } else {
26 | sync ? DispatchQueue.main.sync(execute: block) : DispatchQueue.main.async(execute: block)
27 | }
28 | }
29 |
30 | public protocol DataConvertible {
31 | func data() throws -> Data
32 | static func object(from data: Data) throws -> Self
33 | }
34 |
35 | public extension DataConvertible where Self: Codable {
36 | func data() throws -> Data {
37 | return try JSONEncoder().encode(self)
38 | }
39 |
40 | static func object(from data: Data) throws -> Self {
41 | return try JSONDecoder().decode(Self.self, from: data)
42 | }
43 | }
44 |
45 | public final class WeakObject {
46 | public weak var reference: T?
47 |
48 | public init(referencing: T) {
49 | self.reference = referencing
50 | }
51 |
52 | public var isEmpty: Bool { reference == nil }
53 | }
54 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/ConstValue&Function/UIRelevance.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIRelevance.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/11/30.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// UI相关类,可以用extension添加App UI属性
12 | final public class UI {
13 | /* 缓存相关属性,减少调用方法的次数 */
14 | fileprivate static let screenHeight: CGFloat = UIScreen.main.bounds.size.height
15 | fileprivate static let screenWidth: CGFloat = UIScreen.main.bounds.size.width
16 |
17 | public class var SCREEN_HEIGHT: CGFloat {
18 | get {
19 | return screenHeight
20 | }
21 | }
22 | public class var SCREEN_WIDTH: CGFloat {
23 | get {
24 | return screenWidth
25 | }
26 | }
27 | /// For changing the default font.
28 | public static var _defaultFont: UIFont = UIFont.systemFont(ofSize: 12)
29 | /**
30 | Default font of application
31 |
32 | - parameter size: Size of the font
33 | */
34 | public class func defaultFont(ofSize size: CGFloat) -> UIFont {
35 | return self._defaultFont.withSize(size)
36 | }
37 | }
38 |
39 |
40 | public protocol ReusableView { }
41 | public protocol NibLoadableView: AnyObject { }
42 |
43 | extension ReusableView where Self: UITableViewCell {
44 | public static var reusedIdentifier: String {
45 | return String(describing: self.self)
46 | }
47 | }
48 |
49 | extension NibLoadableView where Self: UIView {
50 | public static var NibName: String {
51 | return String(describing: self.self)
52 | }
53 | }
54 |
55 | extension UITableView {
56 | public func registerNib(_: T.Type) -> Void where T: ReusableView, T: NibLoadableView {
57 | let nib = UINib(nibName: T.NibName, bundle: nil)
58 | self.register(nib, forCellReuseIdentifier: T.reusedIdentifier)
59 | }
60 |
61 | public func register(_ type: T.Type) -> Void where T: ReusableView, T: UITableViewCell {
62 | self.register(type, forCellReuseIdentifier: T.reusedIdentifier)
63 | }
64 |
65 | public func dequeReusableCell(forIndexPath ip: IndexPath) -> T where T: ReusableView {
66 | guard let cell = self.dequeueReusableCell(withIdentifier: T.reusedIdentifier, for: ip) as? T else {
67 | fatalError("couldn't deque cell with identifier: \(T.reusedIdentifier)")
68 | }
69 | return cell
70 | }
71 | }
72 |
73 | public extension UIApplication {
74 |
75 | /// Get all windows from all scenes.
76 | static var availableWindows: [UIWindow] {
77 | if #available(iOS 13, *) {
78 | return UIApplication.shared.connectedScenes.flatMap { scene -> [UIWindow] in
79 | guard let windowScene = scene as? UIWindowScene else { return [] }
80 | return windowScene.windows
81 | }
82 | } else {
83 | return UIApplication.shared.windows
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/CollectionType+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollectionType+Extension.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/4.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Collection where Iterator.Element: Comparable {
12 |
13 | // var isSorted: Bool {
14 | //
15 | // if self.count < 3 { return true }
16 | //
17 | // var compare: Int?
18 | //
19 | // for i in self.startIndex ..< self.endIndex {
20 | // if compare == nil {
21 | // if self[i] > self[Collection.index(after: i)] { compare = 0 }
22 | // else { compare = 1 }
23 | // continue
24 | // }
25 | // if compare! == 1 {
26 | // if self[i] > self[Collection.index(after: i)] { return false }
27 | // } else {
28 | // if self[i] < self[Collection.index(after: i)] { return false }
29 | // }
30 | // }
31 | // return true
32 | // }
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/Dictionary+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Extension.swift
3 | // FileMail
4 | //
5 | // Created by jiangkelan on 24/05/2017.
6 | // Copyright © 2017 xlvip. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary {
12 |
13 | static func +(lhs: Dictionary, rhs: Dictionary) -> Dictionary {
14 | var rst = lhs
15 | for pair in rhs {
16 | rst.updateValue(pair.value, forKey: pair.key)
17 | }
18 | return rst
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/Double+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+Extension.swift
3 | // FileMail
4 | //
5 | // Created by jiangkelan on 22/05/2017.
6 | // Copyright © 2017 xlvip. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Double {
12 | /// Rounds the double to decimal places value
13 | func roundTo(places: Int) -> Double {
14 | let divisor = pow(10.0, Double(places))
15 | return (self * divisor).rounded() / divisor
16 | }
17 |
18 | /*
19 | // Formate to file size string with KB, MB, GB
20 | func fileSizeFormate() -> String {
21 | var totalBytes = self / 1024.0
22 | var multiplyFactor = 0
23 | let tokens = ["KB","MB","GB","TB"]
24 | while totalBytes > 1024 {
25 | totalBytes /= 1024.0
26 | multiplyFactor += 1
27 | }
28 | return "\(String(format:"%.2f",totalBytes))\(tokens[multiplyFactor])"
29 | }
30 | */
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/NSDate+Extentsions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSDate+Extentsions.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 5/16/16.
6 | // Copyright © 2016 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | var dateFormatter: DateFormatter? = nil
12 |
13 | extension Date {
14 |
15 | public var year: Int {
16 | get {
17 | return (Calendar.current as NSCalendar).component(NSCalendar.Unit.year, from: self)
18 | }
19 | }
20 | public var month: Int {
21 | get {
22 | return (Calendar.current as NSCalendar).component(NSCalendar.Unit.month, from: self)
23 | }
24 | }
25 | public var day: Int {
26 | get {
27 | return (Calendar.current as NSCalendar).component(NSCalendar.Unit.day, from: self)
28 | }
29 | }
30 |
31 | public var weekday: Int {
32 | get {
33 | return (Calendar.current as NSCalendar).ordinality(of: NSCalendar.Unit.weekday, in: NSCalendar.Unit.weekOfYear, for: self)
34 | }
35 | }
36 |
37 | public var weekOfMonth: Int {
38 | get {
39 | return (Calendar.current as NSCalendar).component(NSCalendar.Unit.weekOfMonth, from: self)
40 | }
41 | }
42 |
43 | public func offsetMonth(_ offset: Int) -> Date {
44 | guard offset != 0 else { return self }
45 | var comps = DateComponents()
46 | comps.month = offset
47 | return (Calendar.current as NSCalendar).date(byAdding: comps, to: self, options: NSCalendar.Options.wrapComponents)!
48 | }
49 |
50 | public func offsetDay(_ offset: Int) -> Date {
51 | guard offset != 0 else { return self }
52 | var comps = DateComponents()
53 | comps.day = offset
54 | return (Calendar.current as NSCalendar).date(byAdding: comps, to: self, options: NSCalendar.Options.wrapComponents)!
55 | }
56 |
57 | public func offsetWeek(_ offset: Int) -> Date {
58 | guard offset != 0 else { return self }
59 | var comps = DateComponents()
60 | comps.weekOfYear = offset
61 | return (Calendar.current as NSCalendar).date(byAdding: comps, to: self, options: NSCalendar.Options.wrapComponents)!
62 | }
63 |
64 | public static func numberOfDaysInMonth(_ date: Date) -> Int {
65 | return (Calendar.current as NSCalendar).range(of: NSCalendar.Unit.day, in: NSCalendar.Unit.month, for: date).length
66 | }
67 |
68 | public var firstWeekdayInMonth: Int {
69 | get {
70 | var comps = (Calendar.current as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month], from: self)
71 | comps.day = 1
72 | if let newDate = Calendar.current.date(from: comps) {
73 | return newDate.weekday
74 | }
75 | assert(false, "NSDate.firstWeekdayInMonth error, can't generate the first day in month")
76 | return 0
77 | }
78 | }
79 |
80 | public func isMonthEqualToDate(_ date: Date) -> Bool {
81 | let cal = Calendar.current
82 | let selfComps = (cal as NSCalendar).components([NSCalendar.Unit.year, .month], from: self)
83 | let other = (cal as NSCalendar).components([.year, .month], from: date)
84 |
85 | return selfComps.year == other.year && selfComps.month == other.month
86 | }
87 |
88 | public func isDayEqualToDate(_ date: Date) -> Bool {
89 | let cal = Calendar.current
90 | let selfComps = (cal as NSCalendar).components([NSCalendar.Unit.year, .month, .day], from: self)
91 | let other = (cal as NSCalendar).components([.year, .month, .day], from: date)
92 |
93 | return selfComps.year == other.year && selfComps.month == other.month && selfComps.day == other.day
94 | }
95 |
96 | public func isWeekEqualToDate(_ date: Date) -> Bool {
97 | let cal = Calendar.current
98 | let selfComps = (cal as NSCalendar).components([NSCalendar.Unit.year, .weekOfYear, .yearForWeekOfYear], from: self)
99 | let other = (cal as NSCalendar).components([.year, .month, .yearForWeekOfYear], from: date)
100 |
101 | return selfComps.yearForWeekOfYear == other.yearForWeekOfYear && selfComps.weekOfYear == other.weekOfYear
102 | }
103 |
104 | public var originTimeOfDay: TimeInterval {
105 | get {
106 | let comps = (Calendar.current as NSCalendar).components([.year, .month, .day], from: self)
107 | return Calendar.current.date(from: comps)!.timeIntervalSince1970
108 | }
109 | }
110 |
111 | public func stringWithFormat(_ format: String) -> String {
112 |
113 | if dateFormatter == nil { dateFormatter = DateFormatter() }
114 |
115 | let formatter = dateFormatter!
116 | formatter.dateFormat = format
117 | return formatter.string(from: self)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/2.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /**
14 | Be able to use range to get substring, e.x.: "abced"[0..<1] = "a"
15 | */
16 | public subscript (r: Range) -> String {
17 | get {
18 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
19 | let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
20 |
21 | let substring = self.prefix(upTo: endIndex).suffix(from: startIndex)
22 | return String.init(substring)
23 | }
24 | }
25 | /**
26 | Be able to use range to get substring, e.x.: "abced"[0...1] = "ab"
27 | */
28 | public subscript (r: ClosedRange) -> String {
29 | get {
30 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
31 | let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
32 |
33 | let substring = self.prefix(through: endIndex).suffix(from: startIndex)
34 | return String.init(substring)
35 | }
36 | }
37 | /// length of String, number of characters -- Swift 2.0
38 | public var length: Int {
39 | return self.count
40 | }
41 |
42 | /*
43 | /// MD5 of string, need to #import in bridge file
44 | var MD5: String {
45 |
46 | func another() {
47 | let cString = self.cString(using: String.Encoding.utf8)
48 | let length = CUnsignedInt(
49 | self.lengthOfBytes(using: String.Encoding.utf8)
50 | )
51 | let result = UnsafeMutablePointer.allocate(
52 | capacity: Int(CC_MD5_DIGEST_LENGTH)
53 | )
54 |
55 | CC_MD5(cString!, length, result)
56 |
57 | return String(format:
58 | "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
59 | result[0], result[1], result[2], result[3],
60 | result[4], result[5], result[6], result[7],
61 | result[8], result[9], result[10], result[11],
62 | result[12], result[13], result[14], result[15])
63 | }
64 |
65 | let length = Int(CC_MD5_DIGEST_LENGTH)
66 | var digest = [UInt8](repeating: 0, count: length)
67 |
68 | if let d = self.data(using: String.Encoding.utf8) {
69 | _ = d.withUnsafeBytes { (body: UnsafePointer) in
70 | CC_MD5(body, CC_LONG(d.count), &digest)
71 | }
72 | }
73 |
74 | return (0.. UIColor {
14 |
15 | let hexString = hex.replacingOccurrences(of: "#", with: "")
16 | let scanner = Scanner(string: hexString)
17 |
18 | var rgbValue: UInt64 = 0
19 | scanner.scanHexInt64(&rgbValue)
20 |
21 | return UIColor(
22 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
23 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
24 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
25 | alpha: 1)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Extensions/UIView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extensions.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 5/12/16.
6 | // Copyright © 2016 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIView {
13 |
14 | public var x: CGFloat {
15 | set {
16 | var frame = self.frame
17 | frame.origin.x = newValue
18 | self.frame = frame
19 | }
20 | get {
21 | return frame.origin.x
22 | }
23 | }
24 |
25 | public var y: CGFloat {
26 | set {
27 | var frame = self.frame
28 | frame.origin.y = newValue
29 | self.frame = frame
30 | }
31 | get {
32 | return frame.origin.y
33 | }
34 | }
35 |
36 | public var height: CGFloat {
37 | get {
38 | return frame.size.height
39 | }
40 | set {
41 | var f = frame
42 | f.size.height = newValue
43 | frame = f
44 | }
45 | }
46 |
47 | public var width: CGFloat {
48 | get {
49 | return frame.size.width
50 | }
51 | set {
52 | var f = frame
53 | f.size.width = newValue
54 | frame = f
55 | }
56 | }
57 | }
58 |
59 |
60 | public func CGRectGetCenter(_ rect: CGRect) -> CGPoint {
61 | return CGPoint(x: rect.midX, y: rect.midY)
62 | }
63 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Kits/Atomic/RwQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RwQueue.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/6/16.
6 | //
7 |
8 | import Foundation
9 |
10 | /// The `RwQueue` is only be used in a simple way because it's a heavy work when enqueue actions and switch the threads.
11 | /// So if `RwQueue` contains huge amount of work to do, it may leads to unexpected behavior and bad performance.
12 | public class RwQueue {
13 |
14 | public let queue: DispatchQueue
15 |
16 | public init(qos: DispatchQoS = .default, label: String? = nil) {
17 | self.queue = .init(label: "com.mioke.swiftyarchitecture.rwqueue" + (!label.isEmpty ? ".\(label!)" : ""),
18 | qos: qos,
19 | attributes: .concurrent)
20 | }
21 |
22 | public func read(action: @escaping () -> ()) {
23 | queue.async(execute: action)
24 | }
25 |
26 | public func write(action: @escaping () -> ()) {
27 | queue.async(flags: .barrier, execute: action)
28 | }
29 |
30 | public func syncRead(_ closure: () throws -> T) rethrows -> T {
31 | return try queue.sync(execute: closure)
32 | }
33 |
34 | public func syncWrite(_ closure: () throws -> T) rethrows -> T {
35 | return try queue.sync(flags: .barrier, execute: closure)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Kits/SystemLog/LogInterface.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogInterface.swift
3 | // Alamofire
4 | //
5 | // Created by KelanJiang on 2022/6/7.
6 | //
7 |
8 | import Foundation
9 | import os
10 |
11 | public enum LogLevel {
12 | case error, info, verbose, debug
13 | }
14 |
15 | public class KitLogger {
16 | public static let instance: KitLogger = .init()
17 | private init() {}
18 |
19 | public typealias Event = (level: LogLevel, message: String, file: String, line: Int)
20 |
21 | public var events: ((Event) -> Void)?
22 |
23 | public static func log(level: LogLevel, message: String, file: String = #file, line: Int = #line) {
24 | if let events = instance.events {
25 | events((level: level, message: message, file: file, line: line))
26 | } else {
27 | NSLog("[\(level.tag())] \(URL(fileURLWithPath: file).lastPathComponent):\(line) ~> \(message)")
28 | }
29 | }
30 | }
31 |
32 | private extension LogLevel {
33 | func tag() -> String {
34 | switch self {
35 | case .debug:
36 | return "💉"
37 | case .info:
38 | return "📄"
39 | case .verbose:
40 | return "🛰"
41 | case .error:
42 | return "🧯"
43 | }
44 | }
45 | }
46 |
47 | public extension KitLogger {
48 | static func debug(_ message: String = #function, file: String = #file, line: Int = #line) {
49 | log(level: .debug, message: message, file: file, line: line)
50 | }
51 |
52 | static func info(_ message: String = #function, file: String = #file, line: Int = #line) {
53 | log(level: .info, message: message, file: file, line: line)
54 | }
55 |
56 | static func verbose(_ message: String = #function, file: String = #file, line: Int = #line) {
57 | log(level: .verbose, message: message, file: file, line: line)
58 | }
59 |
60 | static func error(_ message: String = #function, file: String = #file, line: Int = #line) {
61 | log(level: .error, message: message, file: file, line: line)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Assistance/Kits/TextInputLimiter/TextInputLimiter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextInputLimiter.swift
3 | // FileMail
4 | //
5 | // Created by jiangkelan on 20/05/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @objc public protocol Inputer: AnyObject {
12 | }
13 |
14 | extension UITextField: Inputer { }
15 | extension UITextView: Inputer { }
16 |
17 | public class InputLimiter: NSObject {
18 |
19 | public var maxCount: Int
20 | public var reachMaxHandler: (() -> Void)?
21 | public var contentDidChanged: ((String?) -> Void)?
22 |
23 | weak var inputer: T?
24 |
25 | public init(with inputer: T, maxCount: Int) {
26 | self.maxCount = maxCount
27 | self.inputer = inputer
28 |
29 | super.init()
30 | }
31 |
32 | @objc fileprivate func contentChanged() -> Void {
33 | guard let inputer = inputer else {
34 | return
35 | }
36 | if let textfield = inputer as? UITextField {
37 | self._inputerDidChanged(textfield)
38 | } else if let textView = inputer as? UITextView {
39 | self._inputerDidChanged(textView)
40 | }
41 | }
42 |
43 | // text field
44 | fileprivate func _inputerDidChanged(_ inputer: UITextField) -> Void {
45 |
46 | guard let text = inputer.text else {
47 | return
48 | }
49 |
50 | if let selected = inputer.markedTextRange,
51 | let _ = inputer.position(from: selected.start, offset: 0) {
52 |
53 | } else {
54 | if text.length > self.maxCount {
55 | inputer.text = text[0.. Void {
64 |
65 | guard let text = inputer.text else {
66 | return
67 | }
68 |
69 | if let selected = inputer.markedTextRange,
70 | let _ = inputer.position(from: selected.start, offset: 0) {
71 |
72 | } else {
73 | if text.length > self.maxCount {
74 | // let rangeIndex = (text as NSString).rangeOfComposedCharacterSequence(at: self.maxCount)
75 | // if rangeIndex.length == 1 {
76 | // inputer.text = text[0.. Void {
91 | inputer?.addTarget(self, action: #selector(contentChanged), for: .editingChanged)
92 | }
93 | public func unload() -> Void {
94 | inputer?.removeTarget(self, action: #selector(contentChanged), for: .editingChanged)
95 | }
96 | }
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Common/Consts+Componentize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Consts.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/6/2.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Consts {
11 | static let componentizeDomain = "com.mioke.swiftyarchitecture.componentize"
12 | }
13 |
14 | extension KitErrors {
15 | static var graphCycleInfo: Info {
16 | .init(code: .graphCycle, message: "Detacted graph circle.")
17 | }
18 | static let graphCycle: Error = error(domain: Consts.componentizeDomain, info: KitErrors.graphCycleInfo)
19 | }
20 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Module/SessionModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionModule.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by Mioke Klein on 2022/1/8.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol SessionModuleProtocol: ModuleProtocol {
11 | static func sessionDidAuthenticate(with userID: String)
12 | static func sessionDidDeauthenticate(with userID: String)
13 | }
14 |
15 | public struct UserSession {
16 | public let userID: String
17 | }
18 |
19 | public extension ModuleManager {
20 | func beginUserSession(with userID: String) {
21 | session = UserSession(userID: userID)
22 | moduleMap.values
23 | .compactMap { $0 as? SessionModuleProtocol.Type }
24 | .forEach { $0.sessionDidAuthenticate(with: userID) }
25 | }
26 |
27 | func endUserSession(with userID: String) {
28 | session = nil
29 | moduleMap.values
30 | .compactMap { $0 as? SessionModuleProtocol.Type }
31 | .forEach { $0.sessionDidDeauthenticate(with: userID) }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Navigation/LinkParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinkParser.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2023/4/6.
6 | //
7 |
8 | import Foundation
9 |
10 | /// @discussion: should provide the configuration key in url string?
11 | public struct ConfigKeys {
12 | public static let presentationMode: String = "_pm"
13 | public static let animated: String = "_a"
14 |
15 | public enum PresentationModeValue: String {
16 | case push = "push"
17 | case present = "present"
18 | }
19 | }
20 |
21 | struct LinkParser {
22 |
23 | enum ParseError: Error {
24 | case cannotGenerateUrl
25 | case notCompatibleToConfig
26 | }
27 |
28 | var externalLinkScheme: String
29 | var inAppLinkScheme: String
30 | var host: String
31 |
32 | func parse(urlString: String) throws -> NavigationURL {
33 | guard let navigation = NavigationURL(from: urlString) else {
34 | throw ParseError.cannotGenerateUrl
35 | }
36 | try validate(url: navigation)
37 | return navigation
38 | }
39 |
40 | func validate(url: NavigationURL) throws {
41 | guard url.scheme == inAppLinkScheme || url.scheme == externalLinkScheme,
42 | url.host == host
43 | else {
44 | throw ParseError.notCompatibleToConfig
45 | }
46 | }
47 |
48 | func isExternalLink(url: NavigationURL) -> Bool {
49 | return url.scheme == externalLinkScheme
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Navigation/Navigation+PageGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Navigation+PageGroup.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2023/4/7.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Navigation {
11 |
12 | func validatePageGroupFunction() throws {
13 | if !enablePageGroup {
14 | throw NavigationError.pageGroupDisabled
15 | }
16 | }
17 |
18 | public func popCurrentPageGroup(animated: Bool) throws -> Void {
19 | try validatePageGroupFunction()
20 | guard let current = pageGroupStack.pop(), let root = current.beginIndex else {
21 | return
22 | }
23 | switch current.presentationMode {
24 | case .present(style: _):
25 | root.dismiss(animated: animated)
26 | case .push:
27 | guard let nav = root.navigationController,
28 | let index = nav.viewControllers.firstIndex(of: root),
29 | index > 0
30 | else { return }
31 | root.navigationController?.popToViewController(nav.viewControllers[index - 1], animated: animated)
32 | }
33 | }
34 |
35 | public func pop(to pageGroup: Navigation.PageGroup) throws {
36 | try validatePageGroupFunction()
37 | }
38 |
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Navigation/NavigationUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationUtils.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2023/4/4.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension URL {
12 | var getHost: String? {
13 | if #available(iOS 16.0, *) {
14 | return self.host()
15 | } else {
16 | return self.host
17 | }
18 | }
19 |
20 | var getPath: String {
21 | if #available(iOS 16.0, *) {
22 | return self.path()
23 | } else {
24 | return self.path
25 | }
26 | }
27 | }
28 |
29 | extension CharacterSet {
30 | /// Creates a CharacterSet from RFC 3986 allowed characters.
31 | ///
32 | /// RFC 3986 states that the following characters are "reserved" characters.
33 | ///
34 | /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
35 | /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
36 | ///
37 | /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
38 | /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
39 | /// should be percent-escaped in the query string.
40 | public static let fixedURLQueryAllowed: CharacterSet = {
41 | let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
42 | let subDelimitersToEncode = "!$&'()*+,;="
43 | let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
44 |
45 | return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters)
46 | }()
47 | }
48 |
49 | func escape(_ string: String) -> String {
50 | return string.addingPercentEncoding(withAllowedCharacters: .fixedURLQueryAllowed) ?? string
51 | }
52 |
53 | class NavigationUtils {
54 | static func topPresentingViewController() -> UIViewController? {
55 | guard let window = applicationWindow(), var controller = window.rootViewController else {
56 | return nil
57 | }
58 | while let presenting = controller.presentedViewController, !presenting.isBeingDismissed {
59 | controller = presenting
60 | }
61 | return controller
62 | }
63 |
64 | static func frontmostViewController() -> UIViewController? {
65 | guard let controller = topPresentingViewController() else { return nil }
66 |
67 | func _frontmost(of vc: UIViewController) -> UIViewController? {
68 | if let controller = vc as? UINavigationController {
69 | return controller.topViewController ?? controller
70 | } else if let controller = vc as? UITabBarController, let selected = controller.selectedViewController {
71 | return _frontmost(of: selected)
72 | } else {
73 | return vc
74 | }
75 | }
76 | return _frontmost(of: controller)
77 | }
78 |
79 | static func applicationWindow() -> UIWindow? {
80 | if #available(iOS 15.0, *),
81 | let windowScene = UIApplication.shared.connectedScenes.first(where: { $0 as? UIWindowScene != nil }) as? UIWindowScene,
82 | let key = windowScene.keyWindow {
83 | return key
84 | }
85 | return UIApplication.shared.delegate?.window ?? nil
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Componentize/Navigation/README-Navigation.md:
--------------------------------------------------------------------------------
1 | # Navigation
2 |
3 | There're some edge cases of the `Navigation` situations.
4 |
5 | ### Internal Link
6 |
7 | When dealing with internal links, the principle of the process logic is that we do additional logics as less as possible.
8 |
9 | - When current view controller hasn't got a navigation controller, it will generate an error when navigation want to push a new view controller.
10 | - When navigation want to present a new view controller and the controller is not a navigation controller, we don't automatically add one navigation controller for it.
11 |
12 | ### Universal Link
13 |
14 | When universal link doesn't explict mark the presentation mode:
15 |
16 | - When current view controller hasn't got a navigation controller, the translator will mark the presentation mode to present and add one navigation for it when the target generate a non-navigation controller.
17 |
18 |
19 | ### Other cases
20 |
21 | - When calling `navigate(to urlString: String, configuration: Navigation.Configuration)`, maybe `urlString` contains some configuration keys. We always use the configuration in the URL string and overwrite them into the configuration passed in the function varible.
22 | -
23 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/API/API+Concurrency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // API+Concurrency.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2024/1/3.
6 | //
7 |
8 | import Foundation
9 | #if canImport(_Concurrency)
10 |
11 | import _Concurrency
12 |
13 | public extension API {
14 |
15 | /// [Concurrency API] Send a request of GET or POST methods.
16 | /// - Parameter params: The request's parameters.
17 | /// - Returns: The request's success result model.
18 | func request(with params: T.RequestParam?) async throws -> T.ResultType {
19 | return try await withCheckedThrowingContinuation { continuation in
20 | sendRequest(with: params).response { api, data, error in
21 | if let error { continuation.resume(throwing: error) }
22 | if let data { continuation.resume(returning: data) }
23 | }
24 | }
25 | }
26 |
27 | }
28 |
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/API/ApiDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApiDelegate.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 05/09/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public typealias ApiDelegateResponse = (_ api: API, _ data: T.ResultType?, _ error: NSError?) -> Void
12 |
13 | open class ApiDelegate: NSObject {
14 |
15 | private var response: ApiDelegateResponse?
16 |
17 | open func response(_ resp: @escaping ApiDelegateResponse) -> Void {
18 | self.response = resp
19 | }
20 | }
21 |
22 | extension ApiDelegate: ApiCallbackProtocol {
23 |
24 | public typealias APIType = T
25 |
26 | public func API(_ api: API, finishedWithResult result: T.ResultType) {
27 | self.response?(api, result, nil)
28 | }
29 |
30 | public func API(_ api: API, failedWithError error: NSError) {
31 | self.response?(api, nil, error)
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/API/ApiRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIRouter.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/5/27.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ApiRouter: AnyObject {
11 | func route(api: API) throws -> Void
12 | }
13 |
14 | protocol APIRouterMokerDataSource: AnyObject {
15 | func construct(type: T.Type) -> ((T.RequestParam?) -> T.ResultType)? where T : ApiInfoProtocol
16 | }
17 |
18 | class ApiRouterContainer {
19 |
20 | static let shared: ApiRouterContainer = .init()
21 |
22 | private let builtinRouter = BuiltinApiRouter()
23 | private lazy var routerMocker = { () -> ApiRouterMocker in
24 | let mocker = ApiRouterMocker()
25 | mocker.datasource = self
26 | return mocker
27 | }()
28 |
29 | private var injectedTypesMap: [String: Any] = [:]
30 |
31 | func router(withType type: T.Type) -> ApiRouter {
32 | let name = String(reflecting: type)
33 | return injectedTypesMap[name] != nil ? routerMocker : builtinRouter
34 | }
35 |
36 | func injectAPI(with type: T.Type,
37 | customize: @escaping (_ param: T.RequestParam?) -> T.ResultType)
38 | -> Void where T: ApiInfoProtocol {
39 | injectedTypesMap[String(reflecting: type)] = customize
40 | }
41 |
42 | func remove(type: T.Type) {
43 | injectedTypesMap[String(reflecting: type)] = nil
44 | }
45 |
46 | func reset() {
47 | injectedTypesMap = [:]
48 | }
49 | }
50 |
51 | extension ApiRouterContainer: APIRouterMokerDataSource {
52 |
53 | func construct(type: T.Type) -> ((T.RequestParam?) -> T.ResultType)? where T : ApiInfoProtocol {
54 | let name = String(reflecting: type)
55 | return injectedTypesMap[name] as? (T.RequestParam?) -> T.ResultType
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/API/ResponseSerializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResponseSerializer.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2021/5/12.
6 | //
7 |
8 | import UIKit
9 | import Alamofire
10 | import ObjectMapper
11 |
12 | public typealias DataResponse = Alamofire.AFDataResponse
13 |
14 | public protocol ResponseSerializerProtocol {
15 | associatedtype SerializedObject
16 | func serialize(data: DataResponse) throws -> SerializedObject
17 | }
18 |
19 | open class ResponseSerializer: NSObject, ResponseSerializerProtocol {
20 | public func serialize(data: DataResponse) throws -> SerializedObject {
21 | throw todo_error()
22 | }
23 | }
24 |
25 | extension ResponseSerializerProtocol where SerializedObject: ApiInfoProtocol {
26 | public var api: API {
27 | return API()
28 | }
29 | }
30 |
31 | /// Placeholder for ProtocBuffer.
32 | open class ProtocResponseSerializer: ResponseSerializer {
33 |
34 | public override func serialize(data: DataResponse) throws -> SerializedObject {
35 | throw todo_error()
36 | }
37 | }
38 |
39 | /// A wrapper of new type `DecodableResponseSerializer` in Alamofire provided from 5.0
40 | final public class JSONCodableResponseSerializer: ResponseSerializer {
41 | public override func serialize(data: DataResponse) throws -> SerializedObject {
42 | let serializer = Alamofire.DecodableResponseSerializer()
43 | let result = try serializer.serialize(
44 | request: data.request,
45 | response: data.response,
46 | data: data.data,
47 | error: data.error)
48 | return result
49 | }
50 | }
51 |
52 | /// A serializer for models conformed to `Mappable`.
53 | final public class JSONMappableResponseSerializer: ResponseSerializer {
54 | public override func serialize(data: DataResponse) throws -> SerializedObject {
55 | if let resp = data.response, resp.statusCode != 200 {
56 | throw todo_error()
57 | }
58 | if let error = data.error {
59 | throw error
60 | }
61 | guard let data = data.data,
62 | let jsonString = String(data: data, encoding: .utf8),
63 | let result = T.init(JSONString: jsonString, context: nil)
64 | else {
65 | throw todo_error()
66 | }
67 | return result
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/Generators/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 16/06/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension KitErrors {
12 |
13 | static var responseErrorInfo: Info {
14 | return .init(code: .responseError, message: "Unexpected response value")
15 | }
16 |
17 | static var apiConstructionInfo: Info {
18 | return .init(code: .apiConstructionFailed, message: "Unexpected API construction result")
19 | }
20 |
21 | // Errors
22 |
23 | static var responseError: NSError {
24 | return error(domain: Consts.networkingDomain, info: responseErrorInfo)
25 | }
26 |
27 | static var apiConstructionError: NSError {
28 | return error(domain: Consts.networkingDomain, info: apiConstructionInfo)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/Generators/KMRequestGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KMRequestGenerator.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/7.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Alamofire
11 |
12 | /// Generator of reuqest
13 | public class KMRequestGenerator: NSObject {
14 |
15 | /// Session configuration for default HTTP request
16 | private static var defaultConfiguration: URLSessionConfiguration {
17 | let configuration = URLSessionConfiguration.af.default
18 | return configuration
19 | }
20 |
21 | /// Session configurtion for default HTTPS request
22 | private static var httpsConfiguration: URLSessionConfiguration {
23 | let configuration = URLSessionConfiguration.af.default
24 | return configuration
25 | }
26 |
27 | /// Default session manager for HTTP requests
28 | public static var defaultManager: Alamofire.Session = {
29 | return Alamofire.Session(configuration: defaultConfiguration)
30 | }()
31 |
32 | /// Default session manager for HTTPs request, you can change it for customized
33 | public static var httpsManager: Alamofire.Session = {
34 | return Alamofire.Session(configuration: httpsConfiguration)
35 | }()
36 |
37 | public class func generateRequest(
38 | withApi api: API,
39 | params: T.RequestParam?) throws -> DataRequest {
40 |
41 | let manager = T.server.supportHTTPS ? httpsManager : defaultManager
42 | manager.session.configuration.timeoutIntervalForRequest = api.timeoutInterval
43 |
44 | var parameters: Parameters? = nil
45 | if let params = params {
46 | let data = try JSONEncoder().encode(params)
47 | parameters = try JSONSerialization.jsonObject(with: data, options: []) as? Parameters
48 | }
49 |
50 | let req = manager.request(api.apiURL,
51 | method: T.httpMethod,
52 | parameters: parameters,
53 | encoding: T.encoding,
54 | headers: api.HTTPHeaders)
55 |
56 | // TODO: Do additional configuration or signature etc.
57 | KitLogger.log(
58 | level: .info,
59 | message: "Send request:\n\tRequest Info:\(req.description)\n\tRequest Headers:\(req.request?.allHTTPHeaderFields ?? [:])\n\tParam:\(parameters ?? [:])")
60 |
61 | return req
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/Protocols/ApiCallbackProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApiCallbackProtocol.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/7.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Describe callback of an api.
12 | public protocol ApiCallbackProtocol: NSObjectProtocol {
13 |
14 | associatedtype APIType: ApiInfoProtocol
15 |
16 | /// Success callback
17 | ///
18 | /// - Parameters:
19 | /// - apiManager: api manager which finished
20 | /// - result: result of response
21 | func API(_ api: API, finishedWithResult result: APIType.ResultType) -> Void
22 |
23 | /// If API returns error or undefined exception, will call this method in delegate.
24 | /// - ATTENTION: **DON'T** try to solve problems here, only do the reflection after error occured
25 | /// - Parameters:
26 | /// - api: API
27 | /// - error: The error occured
28 | func API(_ api: API, failedWithError error: NSError) -> Void
29 | }
30 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/Protocols/ApiInfoProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApiInfoProtocol.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/7.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | /**
13 | * Protocol that descripe what infomation should API provide,
14 | - attention: `apiVersion` and `apiName` __shouldn't begin and end with '/'__. Just like "v2", "user/login" is good. If you don't have a versioned-API, return an empty string.
15 | */
16 | public protocol ApiInfoProtocol: NSObjectProtocol {
17 |
18 | /// The version of api
19 | static var apiVersion: String { get }
20 |
21 | /// The name of api
22 | static var apiName: String { get }
23 |
24 | /// The HTTP method of this api
25 | static var httpMethod: Alamofire.HTTPMethod { get }
26 |
27 | /// The server for this api to request
28 | static var server: Server { get }
29 |
30 | /// Max counts of auto retry machanism, you can return different counts for different error.
31 | ///
32 | /// - Parameter code: Error's code
33 | /// - Returns: Counts of auto retry machanism
34 | static func autoRetryMaxCount(withErrorCode code: Int) -> Int?
35 |
36 | /// The time of retry interval between two request
37 | ///
38 | /// - Parameter code: Error's code
39 | /// - Returns: Time interval for the error
40 | static func retryTimeInterval(withErrorCode code: Int) -> UInt64?
41 |
42 | /// HTTP headers of request
43 | ///
44 | /// - Returns: HTTP headers
45 | static func headers() -> Alamofire.HTTPHeaders?
46 |
47 | /// Encoding function of api's parameters, default is `Alamofire.URLEncoding.default`(aka `methodDependent`)
48 | static var encoding: Alamofire.ParameterEncoding { get }
49 |
50 | associatedtype RequestParam: Codable
51 |
52 | associatedtype ResultType
53 | /// Response serializer type, default is .JSON, that request will call `responseJSON` method when get response.
54 | static var responseSerializer: ResponseSerializer { get }
55 | }
56 |
57 | extension ApiInfoProtocol {
58 |
59 | public static func autoRetryMaxCount(withErrorCode code: Int) -> Int? {
60 | return nil
61 | }
62 |
63 | public static func retryTimeInterval(withErrorCode code: Int) -> UInt64? {
64 | return nil
65 | }
66 |
67 | public static func headers() -> Alamofire.HTTPHeaders? {
68 | return nil
69 | }
70 |
71 | public static var encoding: Alamofire.ParameterEncoding {
72 | return URLEncoding.default
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Networking/Server/Server.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Server.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/7.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Server model
12 | open class Server: NSObject {
13 |
14 | /// The environment model, describe different running environment of the same server logic.
15 | public enum Environment: Hashable {
16 | case live
17 | case custom(String)
18 | }
19 |
20 | /// Initiate configuration of a server.
21 | public struct Configuration {
22 | public let liveURL: URL
23 | public var customURLs: [Server.Environment: URL]
24 | }
25 |
26 | /// The current environment option for this server.
27 | public private(set) var currentEnvironment: Server.Environment = .live {
28 | didSet {
29 | if oldValue != currentEnvironment {
30 | // need notification?
31 | }
32 | }
33 | }
34 |
35 | var configuratoin: Server.Configuration
36 |
37 | public init(configuation: Server.Configuration) {
38 | self.configuratoin = configuation
39 | }
40 |
41 | /// Get the url of current environment.
42 | public var url: URL {
43 | switch currentEnvironment {
44 | case .live:
45 | return configuratoin.liveURL
46 | case .custom(_):
47 | return configuratoin.customURLs[currentEnvironment]!
48 | }
49 | }
50 |
51 | public func `switch`(to customID: String) throws {
52 | let newEnv = Environment.custom(customID)
53 | guard let _ = configuratoin.customURLs[newEnv] else {
54 | throw todo_error()
55 | }
56 | currentEnvironment = newEnv
57 | }
58 |
59 | public func `switch`(to env: Server.Environment) throws {
60 | guard env == .live || configuratoin.customURLs[env] != nil else {
61 | throw todo_error()
62 | }
63 | currentEnvironment = env
64 | }
65 |
66 | }
67 |
68 | /// Customize data process operation of server
69 | public protocol ServerDataProcessProtocol {
70 |
71 | /// Handle the error data that may can handled uniformly. This function will be only called when the request data
72 | /// can't serialized to the type it expected.
73 | ///
74 | /// - Important: Only throw a error when the server can handle it.
75 | /// - Parameter data: The original response data.
76 | func handle(data: Data) throws -> Void
77 | }
78 |
79 | extension Server {
80 |
81 | /// Whether current environment support HTTPS, judged by the scheme of url is `https`.
82 | var supportHTTPS: Bool {
83 | self.url.scheme?.lowercased().contains("https") ?? false
84 | }
85 | }
86 |
87 | extension Server {
88 |
89 | /// Initialize method.
90 | /// - Parameters:
91 | /// - live: The server url of live environment.
92 | /// - customEnvironments: Custom environments urls.
93 | public convenience init(live: URL, customEnvironments: [Server.Environment: URL]) {
94 | let configuation: Server.Configuration = .init(liveURL: live, customURLs: customEnvironments)
95 | self.init(configuation: configuation)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Persistance/DatabaseManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabaseManager.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/1.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 | /// Real database command executor
13 | final public class DatabaseManager: NSObject {
14 |
15 | /// Query function
16 | public class func database(_ databaseQueue: FMDatabaseQueue, query: String, withArgumentsInArray args: [Any]?) -> Array<[AnyHashable: Any]> {
17 |
18 | var rstArray = Array<[AnyHashable: Any]>()
19 |
20 | databaseQueue.inTransaction { (db: FMDatabase?, roolback: UnsafeMutablePointer?) in
21 | guard let db = db else {
22 | return
23 | }
24 | if let rst = db.executeQuery(query, withArgumentsIn: args ?? []) {
25 | while rst.next() {
26 | if let dic = rst.resultDictionary {
27 | rstArray.append(dic)
28 | }
29 | }
30 | rst.close()
31 | }
32 | }
33 | return rstArray
34 | }
35 | /// execute with dictionary parameters
36 | @discardableResult
37 | public class func database(_ databaseQueue: FMDatabaseQueue, execute: String, withArgumentsInDictionary args: [String: Any]?) -> Bool {
38 |
39 | var isSuccess = false
40 | databaseQueue.inTransaction { (db: FMDatabase?, roolback: UnsafeMutablePointer?) in
41 | guard let db = db else {
42 | return
43 | }
44 | isSuccess = db.executeUpdate(execute, withParameterDictionary: args ?? [:])
45 | }
46 | return isSuccess
47 | }
48 | /// execute with array parameters
49 | @discardableResult
50 | public class func database(_ databaseQueue: FMDatabaseQueue, execute: String, withArgumentsInArray args: [Any]?) -> Bool {
51 | var isSuccess = false
52 | databaseQueue.inTransaction { (db: FMDatabase?, roolback: UnsafeMutablePointer?) in
53 | guard let db = db else {
54 | return
55 | }
56 | isSuccess = db.executeUpdate(execute, withArgumentsIn: args ?? [])
57 | }
58 | return isSuccess
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Persistance/DefaultDatabase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultDatabase.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/1.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FMDB
11 |
12 | /// Default database
13 | final public class DefaultDatabase: KMPersistanceDatabase, DatabaseManagerProtocol {
14 |
15 | public static let instance: KMPersistanceDatabase = DefaultDatabase()
16 |
17 | public var path: String
18 | public var database: FMDatabaseQueue
19 | public var databaseName: String
20 |
21 | public override init() {
22 |
23 | self.databaseName = "default.db"
24 | self.path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + "/" + self.databaseName
25 | self.database = FMDatabaseQueue(path: self.path) ?? FMDatabaseQueue()
26 |
27 | super.init()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Persistance/Protocols/DatabaseCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabaseCommand.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/10.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Database command generator.
12 | public class DatabaseCommand: NSObject {
13 |
14 | @discardableResult
15 | public class func createTable(with table: TableProtocol, inDatabase database: KMPersistanceDatabase) -> Bool {
16 |
17 | // TODO: If the table is already there, do nothing.
18 | // Now execute the sql will cause an error of "table existes"
19 | // To solve this problem, we can create a default table in every database to record what tables it has.
20 |
21 | let params = NSMutableArray()
22 |
23 | for key in table.tableColumnInfo.keys {
24 | params.add("'\(key)' \(table.tableColumnInfo[key]!)")
25 | }
26 |
27 | let sql = "create table if not exists '\(table.tableName)' (\(params.componentsJoined(by: ",")))"
28 |
29 | return database.execute(sql, withArgumentsInDictionary: nil)
30 | }
31 |
32 | public class func replaceCommand(with table: TableProtocol, record: RecordProtocol) -> String {
33 |
34 | guard let params = record.dictionaryRepresentation(in: table) else {
35 | assert(false, "DatabaseCommand REPLACE params should not be ampty")
36 | return ""
37 | }
38 | var sql = "replace into \(table.tableName) ("
39 |
40 | let content = NSMutableArray()
41 | let values = NSMutableArray()
42 |
43 | for key in params.keys {
44 | content.add("'\(key)'")
45 | values.add(":\(key)")
46 | }
47 | sql += "\(content.componentsJoined(by: ","))) values (\(values.componentsJoined(by: ",")))"
48 |
49 | print(sql)
50 |
51 | return sql
52 | }
53 |
54 | public class func queryCommand(with table: TableProtocol, select: String?, condition: DatabaseCommandCondition) -> String {
55 |
56 | let selectSql = select == nil ? "*" : "'\(select!)'"
57 | var sql = "select \(selectSql) from \(table.tableName)"
58 | condition.applyCondition(to: &sql)
59 |
60 | return sql
61 | }
62 |
63 | public class func deleteCommand(with table: TableProtocol, condition: DatabaseCommandCondition) -> String {
64 |
65 | var sql = "delete from \(table.tableName)"
66 | condition.applyCondition(to: &sql)
67 |
68 | return sql
69 | }
70 | }
71 |
72 | // MARK: - Command condition
73 |
74 | /// Database command condition object.
75 | public class DatabaseCommandCondition: NSObject {
76 |
77 | /// Where condition, like: `"uid=812, age>12"`
78 | public var whereConditions: String?
79 | // TODO: --- whereConditionsParams ---
80 | // var whereConditionsParams: [String: AnyObject]?
81 |
82 | /// For ordering result.
83 | public var orderBy: String?
84 |
85 | /// DESC or ASC
86 | public var isDESC: Bool?
87 |
88 | /// For limiting result
89 | public var limit: Int?
90 |
91 | /// For distinct result
92 | public var isDistinct: Bool?
93 |
94 | public func applyCondition(to command: inout String) {
95 |
96 | if self.whereConditions != nil/* && self.whereConditionsParams != nil */{
97 | command.append(" where \(self.whereConditions!)")
98 | }
99 | if self.orderBy != nil {
100 | command.append(" order by \(self.orderBy!)")
101 | }
102 | if self.isDESC != nil {
103 | command.append(" \(self.isDESC! ? "DESC" : "ASC")")
104 | }
105 | if self.limit != nil {
106 | command.append(" limit \(self.limit!)")
107 | }
108 | if let isDistinct = self.isDistinct, isDistinct {
109 | command.replaceSubrange(command.index(command.startIndex, offsetBy: 6) ..< command.index(command.startIndex, offsetBy: 6), with: " distinct")
110 | }
111 | }
112 | }
113 |
114 |
115 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/RxExtension/DelayWorker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DelayWorker.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/6/22.
6 | //
7 |
8 | import UIKit
9 | import RxSwift
10 |
11 | public class DelayWorker {
12 |
13 | public enum Event {
14 | case skip
15 | case value(Swift.Result)
16 | }
17 |
18 | public static func delay(
19 | work: Observable,
20 | interval: DispatchTimeInterval,
21 | onQueue: DispatchQueue = .global())
22 | -> Observable> {
23 | return .create { observer in
24 | let worker = { () -> Disposable in
25 | return work.subscribe { rst in
26 | observer.onNext(.value(.success(rst)))
27 | } onError: { error in
28 | observer.onNext(.value(.failure(error)))
29 | } onCompleted: {
30 | observer.onCompleted()
31 | }
32 | }
33 |
34 | if interval == .seconds(0) {
35 | // run this worker synchronizely.
36 | return worker()
37 | } else {
38 | observer.onNext(.skip)
39 | let disposable = CompositeDisposable.init()
40 | DispatchQueue.global().asyncAfter(deadline: .now() + interval) {
41 | _ = disposable.insert(worker())
42 | }
43 | return disposable
44 | }
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/RxExtension/ProducerQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProducerQueue.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/6/16.
6 | //
7 |
8 | import Foundation
9 | import RxSwift
10 |
11 | /// Enqueue *cold signals*, start them one by one, start one after the previous one sent completed.
12 | open class ProducerQueue {
13 |
14 | struct ProducerWrapper {
15 | let id: Int64
16 | let producer: Observable
17 | }
18 |
19 | struct ResultWrapper {
20 | let id: Int64
21 | let result: Swift.Result
22 | }
23 |
24 | private let idGen: IdGenerator = .init()
25 | private var producers: [ProducerWrapper] = []
26 | private let queue: DispatchQueue = .init(label: Consts.domainPrefix + ".producerqueue", qos: .utility)
27 | private let cancel: DisposeBag = .init()
28 | private let subject: PublishSubject = .init()
29 |
30 | private var isProcessing: Atomic = .init(value: false)
31 |
32 | public func enqueue(producer: Observable) -> Observable {
33 | let id = idGen.gen()
34 | let producerWrapper = ProducerWrapper(id: id, producer: producer)
35 | check(producerWrapper)
36 | return subject.asObservable()
37 | .filter { $0.id == id }
38 | .flatMapLatest({ (result) -> Observable in
39 | switch result.result {
40 | case .success(let value):
41 | return .just(value)
42 | case .failure(let error):
43 | return .error(error)
44 | }
45 | })
46 | }
47 |
48 | private func check(_ wrapper: ProducerWrapper? = nil) {
49 | queue.async { [weak self] in
50 | guard let self else { return }
51 | if let wrapper = wrapper {
52 | producers.append(wrapper)
53 | }
54 |
55 | guard !isProcessing.value, let next = producers.first else { return }
56 |
57 | let id = next.id
58 | isProcessing.swap(true)
59 | producers.removeFirst()
60 |
61 | next.producer
62 | // This place shouldn't change the queue, because producer may have it's own queue and we shouldn't change
63 | // the expectation of users.
64 | .subscribe({ [weak self] event in
65 | guard let self else { return }
66 | var result: ResultWrapper? = nil
67 | switch event {
68 | case .next(let value):
69 | result = ResultWrapper(id: id, result: .success(value))
70 | case .error(let error):
71 | result = ResultWrapper(id: id, result: .failure(error))
72 | isProcessing.swap(false)
73 | check()
74 | case .completed:
75 | isProcessing.swap(false)
76 | check()
77 | }
78 | if let result = result {
79 | subject.onNext(result)
80 | }
81 | })
82 | .disposed(by: cancel)
83 | }
84 | }
85 |
86 | }
87 |
88 | class IdGenerator {
89 | private let _lock: UnfairLock = .init()
90 | private var id: Int64 = 0
91 |
92 | func gen() -> Int64 {
93 | _lock.lock()
94 | defer {
95 | id += 1
96 | _lock.unlock()
97 | }
98 | return id
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/RxExtension/RxExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxExtension.swift
3 | // SAD
4 | //
5 | // Created by jiangkelan on 16/10/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 |
12 | public extension API {
13 | func rxSendRequest(with params: T.RequestParam?) -> Observable {
14 | return Observable.create { [weak self] observer in
15 | guard let self else {
16 | observer.onCompleted()
17 | return Disposables.create()
18 | }
19 | sendRequest(with: params).response({ (api, data, error) in
20 | if let error = error {
21 | observer.onError(error)
22 | }
23 | else if let data = data {
24 | observer.on(.next(data))
25 | observer.onCompleted()
26 | }
27 | })
28 | return Disposables.create(with: cancel)
29 | }
30 | }
31 | }
32 |
33 | public extension BehaviorSubject {
34 | var value: Element? {
35 | return try? value()
36 | }
37 | }
38 |
39 | public extension Observable {
40 | func mapToSignal() -> ObservableSignal {
41 | return self.map { _ in () }
42 | }
43 |
44 | func then(_ observale: Observable) -> Observable {
45 | return self.flatMapLatest { _ -> Observable in
46 | return observale
47 | }
48 | }
49 |
50 | func then(_ function: @escaping (Element) -> Observable) -> Observable {
51 | return flatMapLatest(function)
52 | }
53 | }
54 |
55 | /// When Observable's value is just a signal without usable value, we can just call it `ObservableSignal`.
56 | public typealias ObservableSignal = Observable
57 |
58 | public extension ObservableSignal {
59 | /// Send a signal with meaning `success`,`ok`,`notify` etc., a wrapper of `.just(())` and make it more readable.
60 | static var signal: ObservableSignal { .just(()) }
61 | }
62 |
63 | extension Observable {
64 | static var deallocatedError: Observable {
65 | return .error(KitErrors.deallocated)
66 | }
67 | }
68 |
69 | public extension AnyObserver where Element == Void {
70 | func signal() -> Void {
71 | self.onNext(())
72 | }
73 | }
74 |
75 | public extension Reactive where Base: AnyObject {
76 |
77 | var lifetime: DisposeBag {
78 | guard let disposeBag = objc_getAssociatedObject(self.base, #function) as? DisposeBag else {
79 | let disposeBag = DisposeBag.init()
80 | objc_setAssociatedObject(self.base, #function, disposeBag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
81 | return disposeBag
82 | }
83 | return disposeBag
84 | }
85 | }
86 |
87 | #if canImport(_Concurrency)
88 |
89 | @available(iOS 13, *)
90 | public extension UnsubscribeToken {
91 |
92 | /// Extern lifetime corresponding to an object.
93 | /// - Parameter object: An object.
94 | func bindLifetime(to object: T) {
95 | object.rx
96 | .deallocating
97 | .subscribe(onNext: { [weak self] _ in
98 | self?.unsubscribe()
99 | })
100 | .disposed(by: object.rx.lifetime)
101 | }
102 | }
103 |
104 | #endif
105 |
106 | public extension Observable {
107 |
108 | /// Create a `Observable` using a throwing subscribe function.
109 | static func throwingCreate(
110 | _ subscribe: @escaping (AnyObserver) throws -> Disposable
111 | ) -> Observable {
112 | return create { observer in
113 | do {
114 | return try subscribe(observer)
115 | } catch {
116 | observer.onError(error)
117 | return Disposables.create()
118 | }
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Testable/ModuleManager+Testable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModuleManager+Testable.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | extension ModuleBridge {
11 | public func inject(instance: ModuleProtocol) -> Bool {
12 | self.injected[type(of: instance).moduleIdentifier] = instance
13 | return true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Testable/Networking/API+Testable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModuleManager+Testable.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/5/23.
6 | //
7 |
8 | import Foundation
9 |
10 | public class APIMocker {
11 |
12 | public static func mock(type: T.Type, customize: @escaping (T.RequestParam?) -> T.ResultType) -> Void {
13 | ApiRouterContainer.shared.injectAPI(with: T.self, customize: customize)
14 | }
15 |
16 | public static func recover(type: T.Type) -> Void {
17 | ApiRouterContainer.shared.remove(type: T.self)
18 | }
19 |
20 | public static func reset() {
21 | ApiRouterContainer.shared.reset()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Tests/APITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APITests.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/5/30.
6 | //
7 |
8 | import Foundation
9 | @testable import MIOSwiftyArchitecture
10 | import XCTest
11 | import RxSwift
12 | import Alamofire
13 |
14 | class APITests: XCTestCase {
15 |
16 | var cancel: DisposeBag = .init()
17 |
18 | override func setUp() {
19 | super.setUp()
20 | }
21 |
22 | override func tearDown() {
23 | super.tearDown()
24 | }
25 |
26 | let testApi = API()
27 |
28 | func testMock() {
29 | APIMocker.mock(type: UserAPI.self) { params in
30 | return User(userId: "1", name: "Klein")
31 | }
32 |
33 | let expect = XCTestExpectation(description: "Api test mock")
34 |
35 | testApi.rxSendRequest(with: ["id": "1"])
36 | .subscribe { event in
37 | print(event)
38 | guard case .next(_) = event else { return }
39 | expect.fulfill()
40 | }
41 | .disposed(by: cancel)
42 |
43 | wait(for: [expect], timeout: 1)
44 | }
45 |
46 | func testRemoveMock() {
47 | APIMocker.recover(type: UserAPI.self)
48 |
49 | let expect = XCTestExpectation(description: "RemoveMock")
50 | testApi.rxSendRequest(with: ["id": "1"])
51 | .subscribe { event in
52 | print(event)
53 | guard case .error(_) = event else { return }
54 | expect.fulfill()
55 | }
56 | .disposed(by: cancel)
57 | wait(for: [expect], timeout: 5)
58 | }
59 | }
60 |
61 | struct User: Codable {
62 | var userId: String
63 | var name: String
64 | }
65 |
66 | let MioDemoServer: Server = .init(live: URL(string: "https://www.baidu.com")!,
67 | customEnvironments: [
68 | .custom("Dev"): URL(string: "https://www.baidu.com")!,
69 | .custom("Staging"): URL(string: "https://www.baidu.com")!,
70 | ])
71 |
72 | final class UserAPI: NSObject, ApiInfoProtocol {
73 | typealias RequestParam = [String: String]
74 | typealias ResultType = User
75 |
76 | static var apiVersion: String {
77 | get { return "" }
78 | }
79 | static var apiName: String {
80 | get { return "s" }
81 | }
82 | static var server: Server {
83 | get { return MioDemoServer }
84 | }
85 | static var httpMethod: Alamofire.HTTPMethod {
86 | get { return .get }
87 | }
88 |
89 | static var responseSerializer: MIOSwiftyArchitecture.ResponseSerializer {
90 | return MIOSwiftyArchitecture.JSONCodableResponseSerializer()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Tests/InitiatorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModuleManager+Testable.swift
3 | // MIOSwiftyArchitecture
4 | //
5 | // Created by KelanJiang on 2022/5/23.
6 | //
7 |
8 | import Foundation
9 | @testable import MIOSwiftyArchitecture
10 | import XCTest
11 |
12 | class InitiatorTestCase: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testDemo() {
23 | let graph: DirectedGraph = .init()
24 |
25 | let a = DirectedGraphNode(value: 1)
26 | let b = DirectedGraphNode(value: 2)
27 | let c = DirectedGraphNode(value: 3)
28 | let d = DirectedGraphNode(value: 4)
29 |
30 | a.out = [b]
31 | b.out = [c, d]
32 | c.out = [a]
33 |
34 | a.in = [c]
35 | b.in = [a]
36 | c.in = [b]
37 | d.in = [b]
38 |
39 | graph.nodes = [a, b, c, d]
40 |
41 | XCTAssertTrue(graph.checkCircle())
42 | }
43 |
44 | func testNoCircle() {
45 | let graph: DirectedGraph = .init()
46 |
47 | let a = DirectedGraphNode(value: 1)
48 | let b = DirectedGraphNode(value: 2)
49 | let c = DirectedGraphNode(value: 3)
50 | let d = DirectedGraphNode(value: 4)
51 |
52 | a.out = [b]
53 | b.out = [c, d]
54 | c.out = [d]
55 |
56 |
57 | b.in = [a]
58 | c.in = [b]
59 | d.in = [b, c]
60 |
61 | graph.nodes = [a, b, c, d]
62 |
63 | XCTAssertFalse(graph.checkCircle())
64 | }
65 |
66 | func testVisit() {
67 | let graph: DirectedGraph = .init()
68 |
69 | let a = DirectedGraphNode(value: 1)
70 | let b = DirectedGraphNode(value: 2)
71 | let c = DirectedGraphNode(value: 3)
72 | let d = DirectedGraphNode(value: 4)
73 |
74 | a.out = [b]
75 | b.out = [c, d]
76 | c.out = [d]
77 |
78 |
79 | b.in = [a]
80 | c.in = [b]
81 | d.in = [b, c]
82 |
83 | graph.nodes = [a, b, c, d]
84 |
85 | var route: [Int] = []
86 | graph.dfsForEach { node in
87 | route.append(node.value)
88 | }
89 | print(route)
90 | XCTAssert(route.last == 1)
91 | }
92 |
93 | func testBuildTaskGraph() {
94 | let tasks: [Initiator.Task] = [
95 | .init(id: "1", dependencies: ["2", "3"], operation: {}),
96 | .init(id: "2", dependencies: ["4"], operation: {}),
97 | .init(id: "3", dependencies: ["4"], operation: {}),
98 | .init(id: "4", dependencies: [], operation: {}),
99 | ]
100 |
101 | let graph = TaskGraphBuilder.buildGraph(with: tasks)
102 | XCTAssertFalse(graph.checkCircle())
103 |
104 | var path: [String] = []
105 | graph.dfsForEach { node in
106 | path.append(node.value.identifier)
107 | }
108 | print(path)
109 |
110 | XCTAssert(path.count == 4)
111 | XCTAssert(path.first == "4" && path.last == "1")
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Base/Tests/RxTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RxTests.swift
3 | // MIOSwiftyArchitecture-Unit-Tests
4 | //
5 | // Created by KelanJiang on 2022/6/9.
6 | //
7 |
8 | import Foundation
9 | @testable import MIOSwiftyArchitecture
10 | import XCTest
11 | import RxSwift
12 |
13 | class RxTestCase: XCTestCase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | override func tearDown() {
20 | super.tearDown()
21 | }
22 |
23 | var cancel: DisposeBag = .init()
24 |
25 | func testProducerQueue() {
26 | let queue = ProducerQueue()
27 | let expect = XCTestExpectation()
28 |
29 | let ob1 = Observable.create { observer in
30 | observer.onNext(1)
31 | observer.onNext(2)
32 | observer.onCompleted()
33 | return Disposables.create()
34 | }
35 |
36 | let ob2 = Observable.create { observer in
37 | observer.onNext(3)
38 | observer.onCompleted()
39 | return Disposables.create()
40 | }
41 |
42 | queue.enqueue(producer: ob1).subscribe { event in
43 | print(event)
44 | }.disposed(by: cancel)
45 |
46 | queue.enqueue(producer: ob2).subscribe { event in
47 | print(event)
48 | expect.fulfill()
49 | }.disposed(by: cancel)
50 |
51 | wait(for: [expect], timeout: 5)
52 | }
53 |
54 | func testDelayWorker() {
55 | let work = Observable.create { observer in
56 | observer.onNext(1)
57 | observer.onCompleted()
58 | return Disposables.create()
59 | }
60 |
61 | let expect = XCTestExpectation()
62 |
63 | DelayWorker.delay(work: work, interval: .seconds(3))
64 | .debug(#function + "1", trimOutput: true)
65 | .do(onCompleted: {
66 | expect.fulfill()
67 | })
68 | .subscribe()
69 | .disposed(by: cancel)
70 |
71 | DelayWorker.delay(work: work, interval: .microseconds(0))
72 | .debug(#function + "2", trimOutput: true)
73 | .subscribe()
74 | .disposed(by: cancel)
75 |
76 | wait(for: [expect], timeout: 5)
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ApiManager/BaseAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseAPI.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/3/16.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ApplicationBasedApiInfoProtocol: ApiInfoProtocol { }
12 |
13 | extension ApplicationBasedApiInfoProtocol where ResultType: Decodable {
14 | static var responseSerializer: ResponseSerializer {
15 | return MIOSwiftyArchitecture.JSONCodableResponseSerializer()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ApiManager/TestAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestAPI.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 29/12/2016.
6 | // Copyright © 2016 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Alamofire
11 | import MIOSwiftyArchitecture
12 |
13 | let MioDemoServer: Server = .init(live: URL(string: "https://www.baidu.com")!,
14 | customEnvironments: [
15 | .custom("Dev"): URL(string: "https://www.baidu.com")!,
16 | .custom("Staging"): URL(string: "https://www.baidu.com")!,
17 | ])
18 |
19 | struct EmptyRequestParam: Codable { }
20 |
21 | class TestAPI: NSObject, ApplicationBasedApiInfoProtocol {
22 | typealias RequestParam = EmptyRequestParam
23 | typealias ResultType = TestAPI.Result
24 |
25 | static var apiVersion: String {
26 | get { return "" }
27 | }
28 | static var apiName: String {
29 | get { return "s" }
30 | }
31 | static var server: Server {
32 | get { return MioDemoServer }
33 | }
34 | static var httpMethod: Alamofire.HTTPMethod {
35 | get { return .get }
36 | }
37 | static func headers() -> HTTPHeaders? {
38 | return ["Cookie": "uid=123456"]
39 | }
40 | }
41 |
42 | extension TestAPI {
43 |
44 | struct Result: Codable {
45 | let isFinal: Bool
46 | let objects: [TestObj]
47 |
48 | enum CodingKeys: String, CodingKey {
49 | case isFinal = "is_final"
50 | case objects = "objs"
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ApiManager/TestFailureAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestFailureAPI.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2020/4/8.
6 | // Copyright © 2020 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | //class TestFailureAPI: API {
12 | //
13 | //}
14 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Appearance/Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Theme.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/6/21.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class ThemeUI {
13 | static var themeResourceDidChange: Notification.Name = .init(rawValue: "SwiftyArchitecture.ThemeUI.themeDidChange")
14 | static var current: Resource = DynamicResource() {
15 | didSet { NotificationCenter.default.post(name: themeResourceDidChange, object: nil) }
16 | }
17 | }
18 |
19 | protocol Colors {
20 | var text: UIColor { get }
21 | var background: UIColor { get }
22 | }
23 |
24 | protocol Icons {
25 | var appIcon: UIImage { get }
26 | }
27 |
28 | protocol Resource {
29 | var color: Colors { get }
30 | var icon: Icons { get }
31 | }
32 |
33 | class DynamicColors: Colors {
34 |
35 | unowned var resource: DynamicResource?
36 |
37 | init(resource: DynamicResource) {
38 | self.resource = resource
39 | }
40 |
41 | var text: UIColor {
42 | return create(light: .label, dark: .white)
43 | }
44 |
45 | var background: UIColor {
46 | return create(light: .white, dark: .black)
47 | }
48 |
49 | func create(light: UIColor, dark: UIColor) -> UIColor {
50 | return UIColor.init { [weak self] trait in
51 | guard let resource = self?.resource else { return light }
52 | switch resource.currentSetting {
53 | case .light:
54 | return light
55 | case .dark:
56 | return dark
57 | case .followSystem:
58 | return trait.userInterfaceStyle == .dark ? dark: light
59 | }
60 | }
61 | }
62 | }
63 |
64 | class DynamicIcons: Icons {
65 |
66 | unowned var resource: DynamicResource?
67 |
68 | init(resource: DynamicResource) {
69 | self.resource = resource
70 | }
71 |
72 | var appIcon: UIImage {
73 | return .init(named: "launch_swifty_icon")!
74 | }
75 | }
76 |
77 | class DynamicResource: Resource {
78 | lazy var color: Colors = DynamicColors.init(resource: self)
79 | lazy var icon: Icons = DynamicIcons.init(resource: self)
80 |
81 | enum Settings: Int {
82 | case followSystem = 0
83 | case light
84 | case dark
85 |
86 | func toStyle() -> UIUserInterfaceStyle {
87 | switch self {
88 | case .dark: return .dark
89 | case .light: return .light
90 | case .followSystem: return .unspecified
91 | }
92 | }
93 | }
94 |
95 | var currentSetting: Settings = .followSystem {
96 | didSet {
97 | UIView.animate(withDuration: 0.15, delay: 0, options: []) {
98 | UIApplication.availableWindows.forEach { window in
99 | window.overrideUserInterfaceStyle = self.currentSetting.toStyle()
100 | }
101 | }
102 | }
103 | }
104 | }
105 |
106 | // MARK: - example for other static resource
107 |
108 | class DefaultColors: Colors {
109 | var text: UIColor {
110 | return .label
111 | }
112 | var background: UIColor {
113 | return .white
114 | }
115 | }
116 |
117 | class DefaultIcons: Icons {
118 | var appIcon: UIImage {
119 | return .init(named: "launch_swifty_icon")!
120 | }
121 | }
122 |
123 | class DefaultResource: Resource {
124 | var color: Colors = DefaultColors()
125 | var icon: Icons = DefaultIcons()
126 | }
127 |
128 | class TestCaseResources {
129 | func testSwitchResource() -> Void {
130 | ThemeUI.current = DefaultResource()
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Appearance/ThemeTestViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeTestViewController.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/6/21.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import MIOSwiftyArchitecture
11 |
12 | class ThemeTestViewController: ViewController {
13 |
14 | @IBOutlet weak var tableView: UITableView!
15 | @IBOutlet weak var selection: UISegmentedControl!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | // Do any additional setup after loading the view.
21 |
22 | tableView.rowHeight = 44
23 | tableView.register(ThemeTestCell.self)
24 |
25 | tableView.reloadData()
26 |
27 | guard let dynamic = ThemeUI.current as? DynamicResource else { fatalError() }
28 | selection.selectedSegmentIndex = dynamic.currentSetting.rawValue
29 | }
30 |
31 | var dataSource: [String] = (0...100).map { "Messages \($0)" }
32 | /*
33 | // MARK: - Navigation
34 |
35 | // In a storyboard-based application, you will often want to do a little preparation before navigation
36 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
37 | // Get the new view controller using segue.destination.
38 | // Pass the selected object to the new view controller.
39 | }
40 | */
41 |
42 | @IBAction func selectionDidChange(_ sender: Any) {
43 | guard let segment = sender as? UISegmentedControl,
44 | let selection = DynamicResource.Settings.init(rawValue: segment.selectedSegmentIndex),
45 | let dynamic = ThemeUI.current as? DynamicResource
46 | else { return }
47 |
48 | dynamic.currentSetting = selection
49 | }
50 | }
51 |
52 | extension ThemeTestViewController: UITableViewDelegate, UITableViewDataSource {
53 |
54 | static var textAtrributes: [NSAttributedString.Key : Any] = [
55 | .foregroundColor: ThemeUI.current.color.text,
56 | .font: UIFont.systemFont(ofSize: 16)
57 | ]
58 |
59 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
60 | let cell = tableView.dequeReusableCell(forIndexPath: indexPath) as ThemeTestCell
61 | cell.label.attributedText = NSAttributedString(string: dataSource[indexPath.row], attributes: Self.textAtrributes)
62 | return cell
63 | }
64 |
65 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
66 | return dataSource.count
67 | }
68 |
69 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
70 | return 44
71 | }
72 |
73 | }
74 |
75 | extension ThemeTestViewController: NavigationTargetProtocol {
76 | static func createTarget(subPaths: [String], queries: [MIOSwiftyArchitecture.NavigationURL.QueryItem], configuration: Navigation.Configuration) -> [UIViewController] {
77 | KitLogger.info("Navigation action : \(subPaths), queries: \(queries)")
78 | return [ThemeTestViewController.init(nibName: nil, bundle: nil)]
79 | }
80 | }
81 |
82 | class ThemeTestCell: UITableViewCell {
83 |
84 | let label: UILabel
85 |
86 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
87 | self.label = .init(frame: .zero)
88 | super.init(style: style, reuseIdentifier: reuseIdentifier)
89 |
90 | self.contentView.addSubview(self.label)
91 | self.label.textColor = ThemeUI.current.color.text
92 | self.label.font = .systemFont(ofSize: 16)
93 |
94 | self.backgroundColor = ThemeUI.current.color.background
95 |
96 | self.label.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 16).isActive = true
97 | self.label.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
98 | self.label.translatesAutoresizingMaskIntoConstraints = false
99 | }
100 |
101 | required init?(coder: NSCoder) {
102 | fatalError("init(coder:) has not been implemented")
103 | }
104 |
105 | }
106 |
107 | extension ThemeTestCell: ReusableView { }
108 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Appearance/ThemeTestViewController.xib:
--------------------------------------------------------------------------------
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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/CommonModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonModule.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/4/6.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MIOSwiftyArchitecture
11 |
12 | public extension ModuleIdentifier {
13 | static let common: ModuleIdentifier = "com.klein.module.auth"
14 | }
15 |
16 | class CommonModule: ModuleProtocol {
17 | required init() { }
18 |
19 | static var moduleIdentifier: MIOSwiftyArchitecture.ModuleIdentifier {
20 | .common
21 | }
22 |
23 | func moduleDidLoad(with manager: MIOSwiftyArchitecture.ModuleManager) {
24 |
25 | }
26 | }
27 |
28 | extension CommonModule: ModuleInitiatorProtocol {
29 | static var identifier: String {
30 | .common
31 | }
32 |
33 | static var operation: MIOSwiftyArchitecture.Initiator.Operation = {
34 | navigation = .init(inAppLinkScheme: "sa-interal", externalLinkScheme: "sa", host: "com.mioke.swifty-architecture-demo")
35 | if let url = Bundle.main.url(forResource: "AppLinkRegistery", withExtension: ".plist") {
36 | try! navigation?.setRegisteryFile(at: url)
37 | }
38 |
39 | APIMocker.mock(type: UserAPI.self) { param in
40 | if let param = param {
41 | print(param)
42 | }
43 | let reply = UserAPI.Reply(users: [UserAPI.Reply.User(userId: "10025", name: "Klein")])
44 | return reply
45 | }
46 | }
47 |
48 | static var priority: MIOSwiftyArchitecture.Initiator.Priority {
49 | .high
50 | }
51 |
52 | static var dependencies: [String] {
53 | []
54 | }
55 | }
56 |
57 | public var navigation: Navigation? = nil
58 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/GlobalImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalImports.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/3/16.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | @_exported import MIOSwiftyArchitecture
10 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Model/Video.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Video.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2021/10/26.
6 | // Copyright © 2021 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct Video: Codable {
12 |
13 | enum State: String, Codable {
14 | case unknown, streaming, archived
15 | }
16 |
17 | let name: String
18 |
19 | // @CodableDefault(defaultValue: .unknown)
20 | var state: Video.State = .unknown
21 |
22 | init(from decoder: Decoder) throws {
23 | let container = try decoder.container(keyedBy: CodingKeys.self)
24 | self.name = try container.decode(String.self, forKey: .name)
25 | self.state = try container.decodeIfPresent(Video.State.self, forKey: .state) ?? self.state // not a good solution
26 | }
27 | }
28 |
29 | @propertyWrapper class CodableDefault: Codable {
30 | var defaultValue: T
31 |
32 | var value: T?
33 | var wrappedValue: T {
34 | get { return value ?? defaultValue }
35 | set { value = newValue }
36 | }
37 |
38 | init(wrappedValue: T? = nil, defaultValue: T) {
39 | value = wrappedValue
40 | self.defaultValue = defaultValue
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ModulesRegistery.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | modules
6 |
7 | SAD.CommonModule
8 | Application.ApplicationModule
9 | Auth.AuthModule
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Persistance/RealmTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RealmTests.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/4/10.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RealmSwift
11 | import RxRealm
12 | import MIOSwiftyArchitecture
13 | import RxSwift
14 |
15 | class RealmTests {
16 |
17 | static let shared: RealmTests = .init()
18 |
19 | let api = API.init()
20 | let disposables: DisposeBag = .init()
21 |
22 | func testObjectChanges() {
23 |
24 | let request = Request.init(params: .init(uids: ["10025"]))
25 |
26 | DataCenter.update(with: request)
27 | .subscribe { event in
28 | print(event)
29 | }
30 | .disposed(by: disposables)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Persistance/UserTable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserTable.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/12/10.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | //class UserTable: KMPersistanceTable, TableProtocol {
12 | //
13 | // var database: KMPersistanceDatabase {
14 | // return DefaultDatabase.instance
15 | // }
16 | //
17 | // var tableName: String {
18 | // return "user_table"
19 | // }
20 | //
21 | // var tableColumnInfo: [String: String] {
22 | // return [
23 | // "user_id": "Integer primary key",
24 | // "user_name": "text default NULL"
25 | // ]
26 | // }
27 | //}
28 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Services/NotificationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationService.swift
3 | // swiftArchitecture
4 | //
5 | // Created by jiangkelan on 03/01/2017.
6 | // Copyright © 2017 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UserNotifications
11 |
12 | class NotificationService: NSObject {
13 |
14 | private static let service: NotificationService = NotificationService()
15 | class func current() -> NotificationService {
16 | return NotificationService.service
17 | }
18 |
19 | private var requestCompletionblock: ((Bool, Error?) -> Void)?
20 | @available(iOS 10.0, *)
21 | func setRequestCompletion(block: ((Bool, Error?) -> Void)?) -> Void {
22 | self.requestCompletionblock = block
23 | }
24 |
25 | func requestAuthorization() -> Void {
26 | if #available(iOS 10.0, *) {
27 | let completion = self.requestCompletionblock ??
28 | { (granted: Bool, error :Error?) in
29 |
30 | }
31 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge],
32 | completionHandler: completion)
33 | } else {
34 | // Fallback on earlier versions
35 | let types: UIUserNotificationType = [.alert, .badge, .sound]
36 |
37 | let settings = UIUserNotificationSettings(types: types, categories: nil)
38 |
39 | UIApplication.shared.registerUserNotificationSettings(settings)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/Services/UserService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserService.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/11/24.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MIOSwiftyArchitecture
11 | import RxSwift
12 | import AuthProtocol
13 |
14 | class UserService: NSObject {
15 |
16 | static let shared: UserService = .init()
17 |
18 | @discardableResult
19 | func login() -> ObservableSignal {
20 | return AppContext.standard.triggerLogin()
21 | }
22 |
23 | func logout() -> ObservableSignal {
24 | AppContext.current.logout()
25 | }
26 |
27 | func genRandomToken() -> String {
28 | let len = 12
29 | let codec = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-="
30 |
31 | return (0.. Observable {
41 | return .throwingCreate { observer in
42 | if let authModule = try ModuleManager.default.bridge.resolve(.auth) as? AuthServiceProtocol {
43 | try authModule.authenticate { user in
44 | observer.onNext(user)
45 | observer.onCompleted()
46 | }
47 | }
48 | return Disposables.create()
49 | }
50 | }
51 |
52 | func shouldRefreshAuthentication(with user: UserProtocol, isStartup: Bool) -> Bool {
53 | // we can refresh authentication everytime when app startup.
54 | if isStartup {
55 | return true
56 | }
57 | // or we can trust the `expiration` of the user we stored.
58 | guard let user = user as? TestUser else {
59 | assert(false, "This application should use `TestUser` as the UserProtocol.")
60 | return true
61 | }
62 | guard let expiration = user.expiration else { return true }
63 | return expiration < Date()
64 | }
65 |
66 | func refreshAuthentication(with user: UserProtocol) -> Observable {
67 | guard let user = user as? TestUser else { return .error(KitErrors.unknown) }
68 |
69 | return .just(TestUser(id: user.id, age: user.age + 1, token: UserService.shared.genRandomToken()))
70 | .delay(.seconds(3), scheduler: SerialDispatchQueueScheduler.init(qos: .default))
71 | }
72 |
73 | func deauthenticate() -> ObservableSignal {
74 | return .signal
75 | }
76 | }
77 |
78 | enum UserServiceError: Error {
79 | case unknown
80 | }
81 |
82 | extension UserService : StandardAppContextStoreProtocol {
83 |
84 | func standardAppContext(_ context: AppContext, migrate userData: Data, from version: Int) -> UserProtocol? {
85 | if let jsonObj = try? JSONSerialization.jsonObject(with: userData), let dic = jsonObj as? [String: Any] {
86 | print(dic)
87 | }
88 | return nil
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Auth/AuthTestViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthTestViewController.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/9/2.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import MIOSwiftyArchitecture
11 | import RxCocoa
12 | import RxSwift
13 | import AuthProtocol
14 |
15 | class AuthTestViewController: UIViewController {
16 |
17 | @IBOutlet weak var displayLabel: UILabel!
18 | @IBOutlet weak var statusLabel: UILabel!
19 |
20 | var listeners: DisposeBag = .init()
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | // Do any additional setup after loading the view.
26 |
27 | refreshDisplay()
28 | }
29 |
30 | func refreshDisplay() -> Void {
31 | if let user = AppContext.current.user as? TestUser {
32 | displayLabel.text = user.customDebugDescription
33 |
34 | } else if AppContext.current == AppContext.standard {
35 | displayLabel.text = "Current is default user"
36 | }
37 |
38 | listeners = .init()
39 | AppContext.current.authState.map { "\($0)" }
40 | .asDriver(onErrorJustReturn: "Error")
41 | .debug("AuthVC")
42 | .drive(statusLabel.rx.text)
43 | .disposed(by: listeners)
44 | }
45 |
46 |
47 | @IBAction func onLogin(_ sender: Any) {
48 | UserService.shared
49 | .login()
50 | .subscribe { event in
51 | switch event {
52 | case .next():
53 | self.refreshDisplay()
54 | case .completed, .error:
55 | break
56 | }
57 | }
58 | .disposed(by: self.rx.lifetime)
59 | }
60 |
61 | @IBAction func onRefresh(_ sender: Any) {
62 |
63 | }
64 |
65 | @IBAction func onLogout(_ sender: Any) {
66 | UserService.shared.logout()
67 | .subscribe { [weak self] event in
68 | if case .next = event {
69 | self?.refreshDisplay()
70 | }
71 | }
72 | .disposed(by: rx.lifetime)
73 | }
74 |
75 | /*
76 | // MARK: - Navigation
77 |
78 | // In a storyboard-based application, you will often want to do a little preparation before navigation
79 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
80 | // Get the new view controller using segue.destination.
81 | // Pass the selected object to the new view controller.
82 | }
83 | */
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Code Block/CodeBlockEditorViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SnapKit
3 | import Combine
4 |
5 | class CodeBlockEditorViewController: UIViewController {
6 |
7 | var textView: SATextView = buildTextView()
8 | @IBOutlet weak var codeEnableSwitch: UISwitch!
9 |
10 | override func viewDidLoad() {
11 | super.viewDidLoad()
12 |
13 | view.addSubview(textView)
14 | view.sendSubviewToBack(textView)
15 | textView.snp.makeConstraints { make in
16 | make.edges.equalTo(view.safeAreaLayoutGuide)
17 | }
18 |
19 | textView.delegate = self
20 | textView.editType = .code
21 |
22 | textView.layoutManager.delegate = self
23 |
24 | codeEnableSwitch.addTarget(self, action: #selector(codeSwitchDidChange), for: .valueChanged)
25 | }
26 |
27 | static func buildTextView() -> SATextView {
28 | let storage = NSTextStorage()
29 |
30 | let container = SATextContainer(size: .zero)
31 | container.widthTracksTextView = true
32 | container.heightTracksTextView = true
33 |
34 | let layoutManager = SALayoutManager()
35 | storage.addLayoutManager(layoutManager)
36 | layoutManager.addTextContainer(container)
37 |
38 | return SATextView(frame: .zero, textContainer: container)
39 | }
40 |
41 | @objc
42 | func codeSwitchDidChange() {
43 | textView.editType = codeEnableSwitch.isOn ? .code : .plain
44 | }
45 | }
46 |
47 | extension CodeBlockEditorViewController: UITextViewDelegate {
48 |
49 | func textViewDidChange(_ textView: UITextView) {
50 | // print(textView.textContainer, textView.textContainer.layoutManager, textView.textContainer.layoutManager?.textContainers)
51 |
52 | }
53 | }
54 |
55 | extension CodeBlockEditorViewController: NSLayoutManagerDelegate {
56 |
57 | // func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
58 | // let charIndex = layoutManager.characterIndexForGlyph(at: glyphIndex)
59 | // print("#1", "g:\(glyphIndex)", "c:\(charIndex)", rect)
60 | //
61 | // if let theString = layoutManager.textStorage?.string {
62 | // let theChar = theString[theString.index(theString.startIndex, offsetBy: charIndex)]
63 | // print("#2 the char: \(theChar)")
64 | // }
65 | //
66 | // guard let storage = layoutManager.textStorage, storage.length > charIndex + 1 else { return 0.5 }
67 | //
68 | // let preAttribute = storage.attribute(.SACodeBlock, at: charIndex, effectiveRange: nil)
69 | // let afterAttribute = storage.attribute(.SACodeBlock, at: charIndex + 1, effectiveRange: nil)
70 | //
71 | // if preAttribute == nil, afterAttribute != nil {
72 | // return 20
73 | // }
74 | //
75 | // return 0.5
76 | // }
77 |
78 | func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingBeforeGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
79 | let charIndex = layoutManager.characterIndexForGlyph(at: glyphIndex)
80 | print("#1", "g:\(glyphIndex)", "c:\(charIndex)", rect)
81 |
82 | if let theString = layoutManager.textStorage?.string, layoutManager.textStorage?.length ?? 0 > charIndex {
83 | let theChar = theString[theString.index(theString.startIndex, offsetBy: charIndex)]
84 | print("#2 the char: \(theChar)")
85 | }
86 |
87 | guard let storage = layoutManager.textStorage, charIndex > 0 else { return 0.5 }
88 |
89 | let currentAttribute = storage.attribute(.SACodeBlock, at: charIndex, effectiveRange: nil)
90 | let preAttribute = storage.attribute(.SACodeBlock, at: charIndex - 1, effectiveRange: nil)
91 |
92 | if preAttribute == nil, currentAttribute != nil {
93 | return 20
94 | }
95 | return 0.5
96 | }
97 |
98 | func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
99 | let charIndex = layoutManager.characterIndexForGlyph(at: glyphIndex)
100 | print("#3", "g:\(glyphIndex)", "c:\(charIndex)", rect)
101 |
102 | if let theString = layoutManager.textStorage?.string, layoutManager.textStorage?.length ?? 0 > charIndex {
103 | let theChar = theString[theString.index(theString.startIndex, offsetBy: charIndex)]
104 | print("#4 the char: \(theChar)")
105 | }
106 | return 0.5
107 | }
108 |
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Code Block/CodeBlockEditorViewController.xib:
--------------------------------------------------------------------------------
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 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Code Block/SALayoutManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SALayoutManager.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/12/5.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SALayoutManager: NSLayoutManager {
12 |
13 | override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
14 | super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
15 | //
16 | // guard let nsstring = self.textStorage?.string as? NSString else { return }
17 | // print("JKL - prepare to show range: \(glyphsToShow), \n\ttext: \(nsstring.substring(with: glyphsToShow))")
18 | }
19 |
20 | override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
21 | super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
22 |
23 | // guard let nsstring = self.textStorage?.string as? NSString else { return }
24 | // print("JKL - prepare to draw background: \(glyphsToShow), \n at origin: \(origin),\n text: \(nsstring.substring(with: glyphsToShow))")
25 | }
26 |
27 | // override func fillBackgroundRectArray(_ rectArray: UnsafePointer, count rectCount: Int, forCharacterRange charRange: NSRange, color: UIColor) {
28 | //#if DEBUG
29 | // let rects = transfromArrayPointerToArray(rectArray, count: rectCount)
30 | // print(rects, charRange, color)
31 | //#endif
32 | //
33 | // guard let attributedString = self.textStorage?.attributedSubstring(from: charRange),
34 | //// let isCodeBlock = attributedString.attribute(.SACodeBlock, at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: attributedString.length)) as? Bool,
35 | // let isCodeBlock = attributedString.attribute(.SACodeBlock, at: 0, effectiveRange: nil) as? Bool,
36 | // isCodeBlock
37 | // else {
38 | // super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)
39 | // return
40 | // }
41 | //
42 | // var codeRect = CGRect(origin: rectArray.pointee.origin,
43 | // size: .init(width: UIScreen.main.bounds.width - rectArray.pointee.origin.x * 2, height: 0))
44 | // // find largest
45 | // var p = rectArray
46 | // (0 ..< rectCount - 1).forEach { _ in
47 | // p = p.pointee.maxY < p.successor().pointee.maxY ? p.successor() : p
48 | // }
49 | // codeRect.size.height = p.pointee.origin.y + p.pointee.size.height - rectArray.pointee.origin.y
50 | //
51 | //// let roundedRectPath = UIBezierPath(roundedRect: codeRect, cornerRadius: 8)
52 | //// roundedRectPath.stroke()
53 | //
54 | // withUnsafePointer(to: codeRect) { pointer in
55 | // p = pointer
56 | // }
57 | //
58 | // super.fillBackgroundRectArray(p, count: 1, forCharacterRange: charRange, color: color)
59 | // }
60 |
61 | }
62 |
63 |
64 | func transfromArrayPointerToArray(_ pointer: UnsafePointer, count: Int) -> [T] {
65 | var p: UnsafePointer = pointer
66 | return (0 ..< count).map { _ in
67 | defer { p = p.successor() }
68 | return p.pointee
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Code Block/SATextStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SATextStorage.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/12/21.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SATextStorage: NSTextStorage {
12 |
13 | }
14 |
15 |
16 | class SATextContainer: NSTextContainer {
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Code Block/SATextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SATextView.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2022/12/2.
6 | // Copyright © 2022 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SATextView: UITextView {
12 |
13 | /*
14 | // Only override draw() if you perform custom drawing.
15 | // An empty implementation adversely affects performance during animation.
16 | override func draw(_ rect: CGRect) {
17 | // Drawing code
18 | }
19 | */
20 | enum EditType {
21 | case plain
22 | case code
23 | }
24 |
25 | var editType: EditType = .plain {
26 | didSet { editTypeDidChanged() }
27 | }
28 |
29 | override init(frame: CGRect, textContainer: NSTextContainer?) {
30 | super.init(frame: frame, textContainer: textContainer)
31 | }
32 |
33 | required init?(coder: NSCoder) {
34 | super.init(coder: coder)
35 | }
36 |
37 | func editTypeDidChanged() {
38 | switch editType {
39 | case .code:
40 | let style = NSMutableParagraphStyle()
41 | // style.paragraphSpacing = 16
42 | // style.paragraphSpacingBefore = 16
43 |
44 | self.typingAttributes = [
45 | .SACodeBlock: true,
46 | .backgroundColor: UIColor.systemGroupedBackground,
47 | .paragraphStyle: style
48 | ]
49 | case .plain:
50 | self.typingAttributes = [:]
51 | }
52 | }
53 | }
54 |
55 | extension NSAttributedString.Key {
56 | static let SACodeBlock = NSAttributedString.Key.init(rawValue: "SACodeBlock")
57 | }
58 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Combine/Combine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Combine.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/6/27.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 |
12 |
13 | class TestCombineUsages {
14 |
15 | static let shared: TestCombineUsages = .init()
16 |
17 | var cancelables: [AnyCancellable] = []
18 |
19 | @Published var count: Int = 0
20 |
21 | private init() {
22 | foo2()
23 | }
24 |
25 | func foo() {
26 | let publisher: NotificationCenter.Publisher = .init(center: .default,
27 | name: UIResponder.keyboardWillShowNotification)
28 |
29 | publisher
30 | .receive(on: RunLoop.main)
31 | .sink { completion in
32 | print(completion)
33 | } receiveValue: { value in
34 | print(value)
35 | }
36 | .store(in: &cancelables)
37 |
38 | }
39 |
40 | var subscriber: AnySubscriber = AnySubscriber(
41 | receiveSubscription: { subscription in
42 | print(subscription)
43 | },
44 | receiveValue: { value in
45 | print(3, value)
46 | return .unlimited
47 | },
48 | receiveCompletion: { completion in
49 | print(completion)
50 | })
51 |
52 | func foo2() {
53 | //
54 | // $count
55 | // .handleEvents { subscription in
56 | // print("subscription", subscription)
57 | // } receiveOutput: { value in
58 | // print("output", value)
59 | // } receiveCancel: {
60 | // print("cancel")
61 | // } receiveRequest: { demand in
62 | // print("demand", demand)
63 | // }
64 | // .subscribe(subscriber)
65 |
66 | $count.subscribe(subscriber)
67 |
68 | $count
69 | .handleEvents { subscription in
70 | print("subscription", subscription)
71 | } receiveOutput: { value in
72 | print("output", value)
73 | } receiveCancel: {
74 | print("cancel")
75 | } receiveRequest: { demand in
76 | print("demand", demand)
77 | }
78 | .sink { value in
79 | print(1, value)
80 | }
81 | .store(in: &cancelables)
82 |
83 | $count
84 | .sink { value in
85 | print(2, value)
86 | }
87 | .store(in: &cancelables)
88 |
89 |
90 | }
91 |
92 | func foo3() {
93 | CTUser()
94 | .combine
95 | .name
96 | .sink { (value: String?) in
97 |
98 | }
99 | .store(in: &cancelables)
100 |
101 | }
102 | }
103 |
104 | class CTUser: NSObject {
105 | var name: String?
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Combine/CombineTestsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CombineTestsViewController.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/6/27.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CombineTestsViewController: UIViewController {
12 |
13 | let manager = TestCombineUsages.shared
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | // Do any additional setup after loading the view.
19 |
20 | }
21 |
22 | override func viewDidAppear(_ animated: Bool) {
23 | super.viewDidAppear(animated)
24 |
25 | manager.count += 1
26 | }
27 |
28 |
29 | /*
30 | // MARK: - Navigation
31 |
32 | // In a storyboard-based application, you will often want to do a little preparation before navigation
33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
34 | // Get the new view controller using segue.destination.
35 | // Pass the selected object to the new view controller.
36 | }
37 | */
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Combine/CombineTestsViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Concurrency/ConcurrencyTestViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/DataCenter/DataCenterTestViewController.xib:
--------------------------------------------------------------------------------
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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/DataCenter/TestObjModifyVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestObjModifyVC.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2020/4/17.
6 | // Copyright © 2020 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 | import RxRealm
13 | import RealmSwift
14 |
15 | class TestObjModifyVC: UIViewController {
16 |
17 | @IBOutlet weak var keyLabel: UILabel!
18 | @IBOutlet weak var valueTextField: UITextField!
19 |
20 | var objectKey: String = ""
21 |
22 | var disposeBag = DisposeBag()
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | // Do any additional setup after loading the view.
28 |
29 | bindData()
30 | valueTextField.delegate = self
31 | }
32 |
33 | func bindData() -> Void {
34 | let obj = AppContext.current.cache.object(with: objectKey, type: TestObj.self).compactMap { $0 }
35 |
36 | obj.map { $0.key }
37 | .bind(to: self.keyLabel.rx.text)
38 | .disposed(by: self.disposeBag)
39 |
40 | obj.map { "\($0.value)" }.asDriver(onErrorJustReturn: "")
41 | .drive(valueTextField.rx.text)
42 | .disposed(by: self.disposeBag)
43 |
44 | let editingEnd = valueTextField.rx
45 | .controlEvent(UIControl.Event.editingDidEnd)
46 | .map { [weak self] in self?.valueTextField.text }
47 |
48 | // editingEnd
49 | // .compactMap { (text) -> UITextField? in
50 | // if let text = text, let _ = Int(text) {
51 | // return nil
52 | // }
53 | // return self.valueTextField
54 | // }
55 | // .withLatestFrom(obj)
56 | // .subscribe(onNext: { (testObj) in
57 | // self.valueTextField.text = "\(testObj.value)"
58 | // })
59 | // .disposed(by: self.disposeBag)
60 |
61 |
62 | Observable
63 | .combineLatest(obj, editingEnd)
64 | .flatMapLatest { [weak self] (testObj, text) -> ObservableSignal in
65 | if let text = text, let value = Int(text) {
66 | return AppContext.current.store
67 | .context(.cache)
68 | .update { _ in
69 | testObj.value = value
70 | }
71 | } else {
72 | self?.valueTextField.text = "\(testObj.value)"
73 | return .signal
74 | }
75 | }
76 | .subscribe()
77 | .disposed(by: self.disposeBag)
78 |
79 | }
80 |
81 |
82 | /*
83 | // MARK: - Navigation
84 |
85 | // In a storyboard-based application, you will often want to do a little preparation before navigation
86 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
87 | // Get the new view controller using segue.destination.
88 | // Pass the selected object to the new view controller.
89 | }
90 | */
91 |
92 | }
93 |
94 | extension TestObjModifyVC: UITextFieldDelegate {
95 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
96 | textField.resignFirstResponder()
97 | return true
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/Navigation/HomeNavigationHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeNavigationHandler.swift
3 | // SAD
4 | //
5 | // Created by KelanJiang on 2023/4/4.
6 | // Copyright © 2023 KleinMioke. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MIOSwiftyArchitecture
11 |
12 | class HomeNavigationHandler: NavigationModuleHandlerProtocol {
13 | static func shouldNavigate(to url: MIOSwiftyArchitecture.NavigationURL,
14 | decisionHandler: @escaping (MIOSwiftyArchitecture.Navigation.Decision) -> Void) {
15 | KitLogger.info()
16 | if let firstPath = url.paths.first, firstPath == "messages" {
17 | // DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
18 | decisionHandler(.pass)
19 | // }
20 | } else {
21 | decisionHandler(.pass)
22 | }
23 | }
24 |
25 | static func willNavigate(to url: MIOSwiftyArchitecture.NavigationURL) {
26 |
27 | }
28 |
29 | static func didNavigate(to url: MIOSwiftyArchitecture.NavigationURL) {
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Demo/ViewController/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // swiftArchitecture
4 | //
5 | // Created by Klein Mioke on 15/11/23.
6 | // Copyright © 2015年 KleinMioke. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import MIOSwiftyArchitecture
11 |
12 | open class ViewController: UIViewController {
13 |
14 | open override func viewWillAppear(_ animated: Bool) {
15 | KitLogger.info("\(self) will appear")
16 | super.viewWillAppear(animated)
17 | }
18 |
19 | open override func viewDidAppear(_ animated: Bool) {
20 | KitLogger.info("\(self) did appear")
21 | super.viewDidAppear(animated)
22 | }
23 |
24 | open override func viewWillDisappear(_ animated: Bool) {
25 | KitLogger.info("\(self) will disappear")
26 | super.viewWillDisappear(animated)
27 | }
28 |
29 | open override func viewDidDisappear(_ animated: Bool) {
30 | KitLogger.info("\(self) did disappear")
31 | super.viewDidDisappear(animated)
32 | }
33 |
34 | deinit {
35 | KitLogger.info("\(self) deinit")
36 | }
37 | }
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | $(PRODUCT_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Editor
28 | CFBundleURLSchemes
29 |
30 | sa
31 |
32 |
33 |
34 | CFBundleVersion
35 | 1.0.0
36 | LSRequiresIPhoneOS
37 |
38 | NSAppTransportSecurity
39 |
40 | NSAllowsArbitraryLoads
41 |
42 |
43 | UILaunchStoryboardName
44 | LaunchScreen
45 | UIMainStoryboardFile
46 | Main
47 | UIRequiredDeviceCapabilities
48 |
49 | armv7
50 |
51 | UISupportedInterfaceOrientations
52 |
53 | UIInterfaceOrientationPortrait
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 | UISupportedInterfaceOrientations~ipad
58 |
59 | UIInterfaceOrientationPortrait
60 | UIInterfaceOrientationPortraitUpsideDown
61 | UIInterfaceOrientationLandscapeLeft
62 | UIInterfaceOrientationLandscapeRight
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/Resource/swiftArchitecture-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | //#import
6 | //#import
7 |
8 | //#import "FMDB.h"
9 | //#import
10 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // SecTrustTest
4 | //
5 | // Created by KelanJiang on 2022/6/13.
6 | //
7 |
8 | import UIKit
9 | import MIOSwiftyArchitecture
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let windowScene = (scene as? UIWindowScene) else { return }
21 |
22 | print(#function)
23 |
24 | let root = InternalTestVC()
25 | let nav = UINavigationController(rootViewController: root)
26 |
27 | self.window = {
28 | let window = UIWindow(windowScene: windowScene)
29 | window.rootViewController = nav
30 | window.makeKeyAndVisible()
31 | return window
32 | }()
33 | }
34 |
35 | func sceneDidDisconnect(_ scene: UIScene) {
36 | // Called as the scene is being released by the system.
37 | // This occurs shortly after the scene enters the background, or when its session is discarded.
38 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
39 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
40 |
41 | print(#function)
42 | }
43 |
44 | func sceneDidBecomeActive(_ scene: UIScene) {
45 | // Called when the scene has moved from an inactive state to an active state.
46 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
47 |
48 | print(#function)
49 | }
50 |
51 | func sceneWillResignActive(_ scene: UIScene) {
52 | // Called when the scene will move from an active state to an inactive state.
53 | // This may occur due to temporary interruptions (ex. an incoming phone call).
54 |
55 | print(#function)
56 | }
57 |
58 | func sceneWillEnterForeground(_ scene: UIScene) {
59 | // Called as the scene transitions from the background to the foreground.
60 | // Use this method to undo the changes made on entering the background.
61 |
62 | print(#function)
63 | }
64 |
65 | func sceneDidEnterBackground(_ scene: UIScene) {
66 | // Called as the scene transitions from the foreground to the background.
67 | // Use this method to save data, release shared resources, and store enough scene-specific state information
68 | // to restore the scene back to its current state.
69 |
70 | print(#function)
71 | }
72 |
73 |
74 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
75 | KitLogger.info("open \(URLContexts)")
76 |
77 | if navigation!.handle(open: URLContexts) {
78 | return
79 | }
80 |
81 | // do else handler.
82 | }
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/swiftArchitecture.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | _XCCurrentVersionName
6 | swiftArchitecture.xcdatamodel
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftyArchitecture/swiftArchitecture.xcdatamodeld/swiftArchitecture.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pod_publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Script.sh
4 | # swiftArchitecture
5 | #
6 | # Created by KelanJiang on 2020/4/9.
7 | # Copyright © 2020 KleinMioke. All rights reserved.
8 |
9 | pod lib lint --verbose --allow-warnings
10 |
11 | pod trunk push --verbose --allow-warnings
12 |
--------------------------------------------------------------------------------
/swiftArchitecture.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/swiftArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------