This blog post is a compilation of some recent attempts at overall project architecture in the company’s newly launched Swift based language project.

Swift is a static and strongly typed language. Although you can develop in Cocoa framework and use Objective-C Runtime, in my opinion, since you have chosen a language with a new concept, you should follow the rules of this language to think about problems. Therefore, when I designed the project architecture at the very beginning, Is to try to avoid the principle of dynamic language characteristics.

However, I changed my mind when I saw this code in appdelegate. swift for a blank project created from a system template:

class AppDelegate: UIResponder.UIApplicationDelegate {... }Copy the code

UIResponder? It’s still an Objective-C class, and the parent of the door class of the entire App is an Objective-C subclass.

Runtime

The first thing that comes to mind is the AppDelegateExtensions THAT I wrote earlier in the various solutions for slimming the AppDelegate. Since the AppDelegate type is still NSObject, you can still use it in the project.

NOTE: If apple engineers ever re-implement the UIKIT framework using Swift, they’ll have to rethink the implementation.

In an Objective-C project, the recommended place to load the AppDelegateExtensions code is in the main() function:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        installAppDelegateExtensionsWithClass([AppDelegate class]);
        return UIApplicationMain(argc, argv, nil.NSStringFromClass([AppDelegate class])); }}Copy the code

There is no main() function in Swift project, so how to load it? In the official document search to a https://developer.apple.com/swift/blog/?id=7, so inside mentioned:

Application Entry Points and “main.swift”

You’ll notice that earlier we said top-level code isn’t allowed in most of your app’s source files. The exception is a Missack special file named “main. Swift”, which less like a playground file, But is built with your app’s source code. The “main.swift” file can contain top-level code, and the order-dependent rules apply as well. In effect, The first line of code to run in “main.swift” is implicitly defined as the main entrypoint for the program. This allows The minimal Swift program to be a single line — as long as that line is in “main. Swift”.

In Xcode, Mac templates default to include a “main.swift” file, but for iOS apps the default for new iOS project templates is to add @UIApplicationMain to a regular Swift file. This Causes the compiler to synthesize a main entry point for your iOS app, and eliminates the need for a “main.swift” file.

Ok, remove @UIApplicationMain from AppDelegate. swift and create main.swift, then execute our top-level code to load AppDelegateExtensions:

import AppdelegateExtension

installAppDelegateExtensionsWithClass(AppDelegate.self)

UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)),
    NSStringFromClass(MYApplication.self),
    NSStringFromClass(AppDelegate.self))Copy the code

I don’t need to say much about UIApplicationMain, so I pass a UIApplication subclass type to the third argument and let the system create my custom MYApplication instance, which I’ll use later.

With AppDelegateExtensions, we’ve solved the redundancy problem perfectly, but in Swift, where do you register notifications? Remember that there is no load method in Swift anymore.

There is no load method, so let’s create our own. In conjunction with the ModuleManager scheme mentioned in the previous blog, we declare a protocol called Module:

public protocol Module {
    static func load(a) -> Module
}
Copy the code

With Module, we need a management class:

class ModuleManager {
    
    static let shared = ModuleManager(a)private init() {

    }
    
    @discardableResult
    func loadModule(_ moduleName: String) -> Module {
        let type = moduleName.classFromString() as! Module.Type
        let module = type.load()
        self.allModules.append(module)
        return module
    }
    
    class func loadModules(fromPlist fileName: String) {
        let plistPath = Bundle.main.path(forResource: fileName, ofType: nil)!

        let moduleNames = NSArray(contentsOfFile: plistPath) as! [String]
        
        for(_, moduleName) in (moduleNames.enumerated()){
            self.shared.loadModule(moduleName)
        }
    }
    
    var allModules: [Module] = []}Copy the code

ModuleManager provides a loadModules(fromPlist fileName: String) method to load all modules provided in the PList file. So where is it appropriate to implement this method?

Here’s where our custom MYApplication comes in:

class MYApplication: UIApplication {
    override init() {
        super.init(a)ModuleManager.loadModules(fromPlist: "Modules.plist")}}Copy the code

UIApplication has just been created, and all system events have not yet started, so this is a very good time to load modules.

Now that the module loading mechanism is complete, add a module. In a normal project, if IB is not used, we would first delete main.storyboard and create a VC in the AppDelegate code, like this:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.window? .backgroundColor =UIColor.white
        let homeViewController = ViewController(a)let navigationController = UINavigationController(rootViewController: homeViewController)
        self.window? .rootViewController = navigationControllerself.window? .makeKeyAndVisible()return true
    }
Copy the code

Now use the architecture above to encapsulate the loading of the home page as a module too! Declare a HomeModule that follows the Module protocol:

class HomeModule: Module {
    static func load(a) -> Module {
        return HomeModule()}}Copy the code

The home page initialization code is then implemented in the HomeModule:

private init() {
        NotificationCenter.observeNotificationOnce(NSNotification.Name.UIApplicationDidFinishLaunching) { (notification) in
            self.window = UIWindow(frame: UIScreen.main.bounds)
            self.window? .backgroundColor =UIColor.white
            let homeViewController = ViewController(a)let navigationController = UINavigationController(rootViewController: homeViewController)
            self.window? .rootViewController = navigationControllerself.window? .makeKeyAndVisible() } }Copy the code

It is important to note that we have to monitor UIApplicationDidFinishLaunching notice after, can begin to load the home page, remember it, because the Module init method calls the timing is the initialization of the UIApplication, at this point is not to the UI operation timing. Here I wrote a observeNotificationOnce method, this method will at once see a notice, after listening to UIApplicationDidFinishLaunching notice, to implement the UI code.

Let’s go back to the AppDelegate:

import UIKit

class AppDelegate: UIResponder.UIApplicationDelegate{}Copy the code

Immaculate! Did you enjoy it? I’m having a good time.

conclusion

With this architecture, modules in a project that need to be loaded at startup can implement the Module protocol, control the loading order of modules through plist files, and listen to all events in the AppDelegate with The AppDelegateExtensions.

The Module protocol itself can add other methods, such as load now, and other lifecycle methods accordingly. More, it needs to be designed according to the characteristics of different businesses.

In addition, business modules can also be implemented through the Module protocol, some of the Module’s public content in this Module class for other modules to use, other modules do not need to pay attention to your Module exactly which pages/functions.

All of the above code examples are here.