2.洋蔥開發(fā)法

大家好,我是K哥。一名獨(dú)立開發(fā)者,同時(shí)也是Swift開發(fā)框架【Aquarius】的作者,悅記愛尋車app的開發(fā)者。

Aquarius開發(fā)框架旨在幫助獨(dú)立開發(fā)者和中小型團(tuán)隊(duì),完成iOS App的快速實(shí)現(xiàn)與迭代。使用框架開發(fā)將給你帶來簡單、高效、易維護(hù)的編程體驗(yàn)。

Aquarius的開源地址是:https://github.com/JZXStudio/Aquarius,歡迎下載使用。


你的代碼是這樣的嗎?

無論你是用Objective-C還是用Swift編寫你的代碼,想一想是不是viewController中擁有大量的代碼,動(dòng)輒就幾千行

2025-09-29-19-01-17-image.png

試想一下,如果想在這幾千行代碼里進(jìn)行維護(hù)你應(yīng)該怎么辦呢?

是不是非常痛苦,痛苦的還不只是這幾千行的代碼。痛苦的地方在于,來了新的需求,你要把各個(gè)關(guān)聯(lián)代碼寫在不同的方法中,幾十上百個(gè)方法,再加上沒有注釋,怎么樣?是不是頭發(fā)開始哇哇的掉。

這些K哥也和大家一樣,經(jīng)歷過、體會(huì)過、痛苦過。

所以,K哥萌生要設(shè)計(jì)一款開發(fā)框架時(shí),第一個(gè)考慮的就是如何解決在成千上萬行代碼中能夠快速定位的問題,如何讓你的代碼能夠順利的過渡給其他小伙伴。

尤其在中小型團(tuán)隊(duì)中,大家編程的經(jīng)驗(yàn)都各不相同,如何讓團(tuán)隊(duì)的小伙伴順利接手其他人的代碼,是實(shí)現(xiàn)快速開發(fā)、快速迭代、后期易維護(hù)的最關(guān)鍵要素。

如何解決這些問題呢?

基于以上問題,于是洋蔥開發(fā)法誕生了。

洋蔥開發(fā)法

何為洋蔥開發(fā)法

大家不妨看一下下面這張圖片里洋蔥的樣子

6bb1e6128ed81268ff913a7d02c017c7f66a3d90.png

洋蔥具備了明顯的分層結(jié)構(gòu),各層最終組合到一起形成一個(gè)完整的洋蔥。

再想一想我們平時(shí)寫的代碼。

大家試想一下,viewController中的viewWillAppearpresentViewController這兩個(gè)方法有什么區(qū)別?

思考中……

K哥認(rèn)為viewWillAppear屬于職責(zé)型方法,它是在某些特定條件下執(zhí)行的,而presentViewController是功能性方法,它只負(fù)責(zé)完成某項(xiàng)功能的實(shí)現(xiàn)。

如果我們在開發(fā)過程中,加入具備類似viewWillAppearviewDidLoad這種職責(zé)型的方法,讓負(fù)責(zé)不同職責(zé)的代碼放在它對應(yīng)的職責(zé)型方法中,是不是swift文件變得非常易讀啦。

非常好。

下面我們再分析一下,我們平時(shí)在viewController中都完成了哪些功能呢?K哥來總結(jié)一下,如有遺漏,大家評(píng)論區(qū)補(bǔ)充哈。

  1. 自定義導(dǎo)航條

  2. 初始化界面中的各個(gè)UI控件

  3. 調(diào)整各個(gè)UI控件的參數(shù)

  4. 將這些UI控件放到view中

  5. 處理某些UI控件的Delegate,比如UITableView、UITextField

  6. 處理某些通知

……

大致這些吧,當(dāng)然根據(jù)不同人的喜好,可能還有負(fù)責(zé)其他功能的代碼。

有的同學(xué)可能做的比較好,會(huì)把處理UI控件的都放到view中,負(fù)責(zé)邏輯處理的放到單獨(dú)的文件中。

大體情況就是這些。

如果我們把這些職責(zé)進(jìn)行分層。就像前面說的,把設(shè)置UI的部分都統(tǒng)一放在一起,把設(shè)置Delegate的都放在一起。

我們的代碼是不是就具有了明顯的分層結(jié)構(gòu)啦。

這些分層結(jié)構(gòu)組合在一起,是不是就是你原來寫的亂七八糟的viewController了。

再看一下上面洋蔥的圖片,把各個(gè)分層組合在一起,是不是就類似一個(gè)完整的洋蔥了。

這就是洋蔥開發(fā)法的核心思想。

洋蔥開發(fā)法的優(yōu)點(diǎn)

下面我們看一下洋蔥開發(fā)法的優(yōu)點(diǎn)。

首先,給大家看一下使用洋蔥開發(fā)法開發(fā)的代碼長什么樣。

import UIKit
import Foundation

import Aquarius
import CommonFramework

class LoginVC: BaseVC {
    private let a_view: LoginView = LoginView()
    private let viewModel: LoginVM = LoginVM()

    override func a_UI() {
        super.a_UI()

        addRootView(view: a_view)
    }

    override func a_Layout() {
        super.a_Layout()

        a_view.equalScreenSize()
        a_view.equalZeroTopAndLeft()
    }

    override func a_Event() {
        super.a_Event()
        /*
        a_view.loginButton.addTouchUpInsideBlock { [weak self] control in
            self?.viewModel.login(email: self!.a_view.userTextField.text!)
            self?.dismiss(animated: true)
        }
         */
    }
}

整體的Swift文件具備明顯的分層結(jié)構(gòu)將添加UI控件的所有代碼都放到了a_UI中,將UI控件的布局方法都放到了a_Layout中,將所有的事件都放到了a_Event中。

那么使用洋蔥開發(fā)法后,明顯的變化是什么呢?

如果團(tuán)隊(duì)內(nèi)的小伙伴都使用洋蔥開發(fā)法開發(fā)的話,我們在看其他人代碼的時(shí)候就會(huì)很清楚,處理事件的代碼在哪里,處理通知的方法在哪里,處理UI控件布局的方法在哪里。你就能快速的知道出問題的地方在哪里。

再看下面的截圖

2025-09-29-20-21-51-image.png

這里顯示了整個(gè)類中都有哪些方法。

如果是基于洋蔥開發(fā)法的話,是不是我們就可以馬上知道這個(gè)類中沒有通知的處理,沒有處理Delegate。

怎么樣,是不是一目了然。

整理一下,使用洋蔥開發(fā)法的優(yōu)點(diǎn)如下:

  1. 降低了組內(nèi)多人協(xié)作開發(fā)的難度

  2. 提高了代碼易讀性

  3. 代碼更優(yōu)雅

  4. 后期維護(hù)難度大大降低

  5. 團(tuán)隊(duì)人員更迭時(shí),更易于順利交接

  6. 降低代碼出錯(cuò)率

  7. 提高代碼評(píng)審速度

  8. 提高了團(tuán)隊(duì)整體開發(fā)能力

Aquarius開發(fā)框架中的實(shí)現(xiàn)方案

Aquarius開發(fā)框架針對洋蔥開發(fā)法提供了大量的分層方法。包括:

  1. a_Preview:開始前執(zhí)行

  2. a_Begin:開始執(zhí)行時(shí)調(diào)用

  3. a_Navigaiton:定制化導(dǎo)航條

  4. a_UI:設(shè)置UI組件

  5. a_UIConfig:設(shè)置UI組件參數(shù)

  6. a_Layout:設(shè)置UI組件的布局

  7. a_Delegate:設(shè)置delegate

  8. updateThemeStyle:深色模式切換時(shí)調(diào)用

  9. a_Notification:設(shè)置通知

  10. a_Bind:設(shè)置數(shù)據(jù)綁定

  11. a_Observe:設(shè)置Observe

  12. a_Event:設(shè)置事件

  13. a_Other:設(shè)置其它內(nèi)容

  14. a_End:代碼末尾執(zhí)行

  15. a_Test:測試的代碼函數(shù),此函數(shù)只在debug模式下執(zhí)行,發(fā)布后不執(zhí)行

  16. a_Clear:銷毀時(shí)執(zhí)行

哪些類支持洋蔥開發(fā)法呢

那么Aquarius開發(fā)框架中哪些類支持洋蔥開發(fā)法中的分層方法呢?

  1. AViewController:所有Controller的基類

  2. AView:所有View的基類

  3. AViewModel:所有ViewModel的基類

  4. ATableViewCell:所有TableViewCell的基類

  5. ACollectionViewCell:所有CollectionViewCell的基類

  6. ANavigationController:所有NavigationController的基類

  7. ATableBarController:所有TabBarController的基類

  8. ATableViewHeaderFooterView:所有TableViewHeaderFooterView的基類

基于Aquarius開發(fā)框架如何使用呢

當(dāng)你將Aquarius開發(fā)框架導(dǎo)入你的工程后,當(dāng)你創(chuàng)建上面介紹的UI控件時(shí),請分別繼承Aquarius開發(fā)框架的基類,請放心使用它們。

舉個(gè)例子:

當(dāng)你創(chuàng)建一個(gè)UIViewSwift文件時(shí),你可以繼承Aquarius中的AView

舉個(gè)例子吧。

import UIKit
import Foundation

import Aquarius

class LoginView: AView {
    private let backButton: UIButton = A.ui.button
    public let titleLabel: UILabel = A.ui.label

    public let userTextField: UITextField = A.ui.textField
    private let userLeftView: UIView = A.ui.view
    private let userLeftImageView: UIImageView = A.ui.imageView

    private let passTextField: UITextField = A.ui.textField
    private let passLeftView: UIView = A.ui.view
    private let passLeftImageView: UIImageView = A.ui.imageView

    private let forgotPassButton: UIButton = A.ui.button
    public let loginButton: UIButton = A.ui.button

    private let signInWithLineView: UIView = A.ui.view
    private let signInWithLabel: UILabel = A.ui.label

    private let twitterButton: UIButton = A.ui.button
    private let facebookButton: UIButton = A.ui.button
    private let googleButton: UIButton = A.ui.button

    private let dontAccountLabel: UILabel = A.ui.label
    private let signUpButton: UIButton = A.ui.button

    private let bindID: String = "bindID"

    override func a_UI() {
        super.a_UI()

        addSubviews(views: [
            backButton,
            titleLabel,

            userTextField,
            passTextField,
            forgotPassButton,
            loginButton,

            signInWithLineView,
            signInWithLabel,
            twitterButton,
            facebookButton,
            googleButton,

            dontAccountLabel,
            signUpButton
        ])

        userLeftView.addSubview(userLeftImageView)
        userTextField.leftView = userLeftView

        passLeftView.addSubview(passLeftImageView)
        passTextField.leftView = passLeftView
    }

    override func a_UIConfig() {
        super.a_UIConfig()

        backButton.setImage("back.png".toNamedImage(), for: .normal)
        titleLabel.textLeftAlignment()
        titleLabel.font(28.0.toBoldFont)
        titleLabel.text = "Welcome back"

        userTextField.styleDesign(textFieldStyle)
        userTextField.placeholder = "Enter your Email"
        userLeftImageView.image = "email-icon.png".toNamedImage()

        passTextField.styleDesign(textFieldStyle)
        passTextField.placeholder = "Enter your password"
        passLeftImageView.image = "Pat.png".toNamedImage()

        forgotPassButton.setTitle("Forgot password?", for: .normal)
        forgotPassButton.titleLabel?.font = 17.toFont

        loginButton.layerCornerRadius(12.0)
        loginButton.setTitle("Login", for: .normal)
        loginButton.titleLabel?.font = 20.toBoldFont
        loginButton.liquid_ProminentClearGlass()

        signInWithLabel.font = 17.0.toFont
        signInWithLabel.textCenterAlignment()
        signInWithLabel.text = "  Sign In With  "

        facebookButton.setImage("facebook.png".toNamedImage(), for: .normal)
        facebookButton.layerCornerRadius(20.0)
        facebookButton.layerBorderWidth(1.0)

        //twitterButton.setImage("twetter.png".toNamedImage(), for: .normal)
        twitterButton.layerCornerRadius(20.0)
        //twitterButton.layerBorderWidth(1.0)

        twitterButton.liquid_ProminentClearGlass(image: "twetter.png".toNamedImage())

        googleButton.setImage("Google.png".toNamedImage(), for: .normal)
        googleButton.layerCornerRadius(20.0)
        googleButton.layerBorderWidth(1.0)

        dontAccountLabel.text = "Don't have an account?"
        dontAccountLabel.textRightAlignment()
        dontAccountLabel.font(17.0.toFont)

        signUpButton.setTitle("SIGN UP", for: .normal)
        signUpButton.titleLabel?.font(17.0.toFont)
    }

    override func a_Layout() {
        super.a_Layout()

        backButton.size(sizes: [20, 17])
        backButton.left(left: 26.0)
        backButton.top(top: statusBarHeight()+10.0)

        titleLabel.equalTextSize()
        titleLabel.left(left: 37.0)
        titleLabel.alignTop(view: backButton, offset: 58.0)

        userTextField.equalScreenWidth(37.0*2)
        userTextField.height(height: 46.0)
        userTextField.left(left: 37.0)
        userTextField.alignTop(view: titleLabel, offset: 49.0)

        userLeftImageView.size(sizes: [16, 12])
        userLeftImageView.left(left: 15.0)
        userLeftImageView.top(top: (49-12)/2)

        userLeftView.width(width: 16+15*2)
        userLeftView.equalHeight(target: userTextField)
        userLeftView.equalZeroTopAndLeft()

        passTextField.equalSize(target: userTextField)
        passTextField.equalLeft(target: userTextField)
        passTextField.alignTop(view: userTextField, offset: 20.0)

        passLeftImageView.size(sizes: [16, 19])
        passLeftImageView.left(left: 15.0)
        passLeftImageView.top(top: (49-19)/2)

        passLeftView.equalSize(target: userLeftView)
        passLeftView.equalZeroTopAndLeft()

        forgotPassButton.size(size: forgotPassButton.titleLabel!.getTextSize())
        forgotPassButton.equalRight(target: userTextField)
        forgotPassButton.alignTop(view: passTextField, offset: 13.0)

        loginButton.equalSize(target: userTextField)
        loginButton.equalLeft(target: userTextField)
        loginButton.alignTop(view: forgotPassButton, offset: 49.0)

        signInWithLineView.equalScreenWidth(107.5*2)
        signInWithLineView.height(height: 1.0)
        signInWithLineView.left(left: 107.5)
        signInWithLineView.alignTop(view: loginButton, offset: 76.5)

        signInWithLabel.equalTextSize()
        signInWithLabel.left(left: (screenWidth()-signInWithLabel.width())/2)
        signInWithLabel.equalTop(target: signInWithLineView, offset: -signInWithLabel.height()/2)

        facebookButton.size(widthHeight: 40.0)
        facebookButton.left(left: (screenWidth()-facebookButton.width())/2)
        facebookButton.alignTop(view: signInWithLabel, offset: 28.0)

        twitterButton.equalSize(target: facebookButton)
        twitterButton.equalTop(target: facebookButton)
        twitterButton.equalLeft(target: facebookButton, offset: -(45.0+twitterButton.width()))

        googleButton.equalSize(target: facebookButton)
        googleButton.equalTop(target: facebookButton)
        googleButton.alignLeft(view: facebookButton, offset: 45.0)

        dontAccountLabel.equalTextSize()
        dontAccountLabel.left(left: 10.0)
        dontAccountLabel.equalBottom(target: self, offset: tabBarHeight()+safeAreaFooterHeight()+30.0)

        signUpButton.size(size: signUpButton.titleLabel!.getTextSize())
        signUpButton.equalTop(target: dontAccountLabel)

        dontAccountLabel.left(left: (screenWidth()-dontAccountLabel.width()-signUpButton.width()-8)/2)
        signUpButton.alignLeft(view: dontAccountLabel, offset: 8.0)
    }

    override func a_Bind() {
        super.a_Bind()

        let key: String = bindText(ui: userTextField)
        bindText(bindKey: key, ui: passTextField)
    }

    override func updateThemeStyle() {
        super.updateThemeStyle()

        titleLabel.textColor = 0x00B5BA.toColor
        userTextField.textColor = 0x3E3E3E.toColor
        userTextField.placeHolderColor = 0xB2BDC4.toColor
        userTextField.backgroundColor = 0xF6F6F6.toColor

        passTextField.textColor = 0x3E3E3E.toColor
        passTextField.placeHolderColor = 0xB2BDC4.toColor
        passTextField.backgroundColor = 0xF6F6F6.toColor

        forgotPassButton.setTitleColor(0x3E3E3E.toColor, for: .normal)
        loginButton.backgroundColor = 0x00B5BA.toColor
        loginButton.setTitleColor(.white, for: .normal)

        signInWithLineView.backgroundColor = 0xD2D2D2.toColor
        signInWithLabel.textColor = 0x7B7B7B.toColor
        signInWithLabel.whiteBackgroundColor()

        facebookButton.layerBorderColor(0xD2D2D2.toColor)
        twitterButton.layerBorderColor(0xD2D2D2.toColor)
        googleButton.layerBorderColor(0xD2D2D2.toColor)

        dontAccountLabel.textColor = 0x3E3E3E.toColor
        signUpButton.setTitleColor(0x00B5BA.toColor, for: .normal)
    }

    override func a_Event() {
        super.a_Event()

        loginButton.addTouchUpInsideBlock { [weak self] control in
            self?.passTextField.equalSize(target: self!.userTextField)
            self?.passTextField.equalLeft(target: self!.userTextField)
            self?.passTextField.alignTop(view: self!.userTextField, offset: 8.0)
        }
    }

    override func a_Test() {
        super.a_Test()

        //[backButton, titleLabel].debugMode()

        let testView: UIView = A.ui.view
        testView.size(widthHeight: 50.0)
        testView.equalLeft(target: twitterButton)
        testView.alignTop(view: twitterButton, offset: 16.0)
        testView.redBackgroundColor()
        if #available(iOS 26.0, *) {
            testView.cornerConfiguration = .corners(topLeftRadius: 10, topRightRadius: 20, bottomLeftRadius: 30, bottomRightRadius: 40)
        }
        addSubView(testView)
    }
}

是不是覺得上面的代碼中,有很多代碼你都不認(rèn)得,這還是Swift嗎?

請不要在意它們,在接下來的文章中,我都會(huì)詳細(xì)介紹。

你只需要關(guān)心這個(gè)UIView中,洋蔥開發(fā)法是如何實(shí)現(xiàn)的即可。

看完以上代碼有何感覺?

是不是代碼變得特別清晰,特別有層次。

試想一下

如果你是一名iOS開發(fā)工程師的話,你和你的小伙伴都會(huì)使用洋蔥開發(fā)法開發(fā),你再看他的代碼,或者他看你的代碼時(shí)是不是很快就能上手繼續(xù)開發(fā)新的功能,或者維護(hù)這些代碼了。

如果你是一名開發(fā)經(jīng)理的話,如果你手下的小伙伴都會(huì)基于洋蔥開發(fā)法開發(fā)的話,項(xiàng)目的開發(fā)難度是不是會(huì)大大的降低呢?

如果你是一名項(xiàng)目經(jīng)理的話,如果你團(tuán)隊(duì)的小伙伴都會(huì)洋蔥開發(fā)法的話,是不是你的開發(fā)進(jìn)度會(huì)大大的提前呢?

如果你是一家中小型公司的老板的話,如果你的手下都會(huì)洋蔥開發(fā)法的話,是不是會(huì)幫你節(jié)約一大部分開發(fā)成本呢?無論是人員的變動(dòng)還是招納新的開發(fā)人員,是不是會(huì)洋蔥開發(fā)法將是你的首要要求了呢?

如果你是一名獨(dú)立開發(fā)者,當(dāng)你的產(chǎn)品越來越復(fù)雜,你勢必有一天會(huì)將你的代碼托管給其他小伙伴來幫你完成。如果你和他都使用洋蔥開發(fā)法的話,那么交接的難度是不是就會(huì)大大的降低了呢?

注意

需要注意的是,Aquarius開發(fā)框架提供的洋蔥開發(fā)法方案,Controller是運(yùn)行在viewDidLoad方法中的,其余基本都運(yùn)行在初始化的方法中。

如果你想在現(xiàn)有的類中使用洋蔥開發(fā)法的話,要注意這一點(diǎn)。

另外需要注意的一點(diǎn)是,Aquarius開發(fā)框架中提供的分層方法都是有實(shí)際職責(zé)的,如果你的類中沒有相關(guān)的功能,請不要編寫這個(gè)方法。

舉個(gè)例子,如果你的UIView中,所有的UI控件都不需要添加Delegate,那么你在這個(gè)類文件中就不要添加a_Delegate方法,如果添加的話,就會(huì)對整個(gè)代碼造成污染。后期無論是你自己review或其他小伙伴review的時(shí)候就會(huì)造成困擾。

如果,對Aquarius開發(fā)框架的洋蔥開發(fā)法感興趣的話,可以下載源碼,在源碼中查看如何實(shí)現(xiàn)。

尾聲

好了,今天的內(nèi)容就介紹到這了。

洋蔥開發(fā)法Aquarius開發(fā)框架提供的最核心的開發(fā)理念。

有了洋蔥開發(fā)法,你的代碼將變得非常易讀、易維護(hù),尤其在團(tuán)隊(duì)開發(fā)中,非常適合代碼交接、協(xié)作開發(fā)。

獨(dú)立開發(fā)者來說,洋蔥開發(fā)法是非常有意義的。無論是你自己獨(dú)立完成,還是將來將你的代碼交接給其他小伙伴幫你完成。你們的工作交接過程將變得非常nice。也會(huì)節(jié)約你大量的開發(fā)時(shí)間,提高你的開發(fā)效率。


目前,K哥已經(jīng)使用Aquarius開發(fā)框架成功上架了兩款A(yù)PP,分別是悅記 - 愉悅記錄,快樂生活愛尋車 - 找車、停車、健忘癥、停車費(fèi)用提醒

你也可以下載這兩款A(yù)PP,更深入的了解框架的使用情況。

如果你也采用Aquarius開發(fā)框架開發(fā)了自己的APP,并上架AppStore也請聯(lián)系K哥,K哥會(huì)把你的APP加入到Aquarius開發(fā)框架的GitHub首頁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容