Swift代码规范

  • 推荐个规范代码的库SwiftLint, 有兴趣的同学可瞧瞧如何。
  • 相信我,关于代码规范,你需要仔细看看这篇文章最详尽的 Swift 代码规范指南,此文章笔者写的时候是Xcode7.3, 应该是Swift2.0时代.

关于SwiftLint

目前项目中的.swiftLint.yml

disabled_rules: # these rules produce error
  # will be enabled later:
  - dynamic_inline
  - empty_count
  - force_cast
  - force_try
  - variable_name
  - type_name
  # always disabled:
  - legacy_constructor
  - line_length
  - valid_docs
  - trailing_comma
  - operator_usage_whitespace

opt_in_rules:
  - closure_end_indentation
  - closure_spacing

# configurable rules
weak_delegate:
  error
operator_whitespace:
  error
control_statement:
  error
implicit_getter:
  error
closure_end_indentation:
  error
closure_parameter_position:
  error
closure_spacing:
  error
unused_closure_parameter:
  warning
empty_parentheses_with_trailing_closure:
  error

# custom rules
custom_rules:
  # 1
  lazy_position:
    name: "Lazy Position"
    included: ".*.swift"
    regex: "lazy\s+(open|public|internal|fileprivate|private)"
    match_kinds: # SyntaxKinds to match. optional.
      - attribute.builtin
    message: "put 'access control keyword' before 'lazy'"
    severity: error

前些天我们leader发怒了,于是乎“enable closure related rules.
disable type_name for now”, 然后就开始忙起来了,哈哈。

  • fix closure_spacing

Bad

 static var provider: RxMoyaProvider<T> {get}

Good

 static var provider: RxMoyaProvider<T> { get }

Bad

return words.reduce("") {$0 + $1[0...0]}

Good

return words.reduce("") { $0 + $1[0...0] }
  • autocorrect

Bad

// MARK:  relodUI

Good

// MARK: relodUI

Bad

weakSelf!.requestData(.LockCourseComenBack);

Good

weakSelf!.requestData(.LockCourseComenBack)

Bad

guruH = NoHeight; listH = NoHeight;
guruMargin = Margin10; listMargin = Margin0; commentMargin = Margin0;

Good

guruH = NoHeight; listH = NoHeight
guruMargin = Margin10; listMargin = Margin0; commentMargin = Margin0

Bad

func HexRGBAlpha(rgbValue : UInt,alpha : Float) -> UIColor { }

Good

func HexRGBAlpha(rgbValue: UInt, alpha: Float) -> UIColor { }

Bad

weak var delegate: PadArticleListDelegate? = nil

Good

   weak var delegate: PadArticleListDelegate?

Bad

   public func timer(doing: (number: Int) -> (), endTime: () -> ()) {

Good(注意:明确闭包的返回值类型)

   public func timer(doing: (number: Int) -> Void, endTime: () -> Void) {

Bad

// MARK: --- private

Good

// MARK: - -- private
  • fix closure_end_indentation

Bad

searchBar.rx_searchButtonClicked.subscribeNext { [weak self]_ in
   self!.startSearch(self!.searchBar.text!)
   }.addDisposableTo(rx_disposeBag)

Good(注意:调用 Rx点语法的换行)

searchBar.rx_searchButtonClicked
    .subscribeNext { [weak self] in
        self!.startSearch(self!.searchBar.text!)
    }.addDisposableTo(rx_disposeBag)

Bad

searchBar.rx_searchButtonClicked.subscribeNext { _ in
               self.subject.onNext(self.searchBar.text)
           }.addDisposableTo(rx_disposeBag)

Good

searchBar.rx_searchButtonClicked
          .subscribeNext { _ in
              self.subject.onNext(self.searchBar.text)
          }.addDisposableTo(rx_disposeBag)

Bad

override func loadView() {
    super.loadView()
    self.view =
        SelectedScrollContentView.init(frame: self.view.bounds, targetViewController: self) {
            self.data.forEach { [weak self] item in
                let vc: EmploymentInformationViewController = EmploymentInformationViewController.init()
                vc.title = item.title
                vc.ID = item.id
                self?.addChildViewController(vc)
            }
        }!
}

Good(注意:闭包体括号括起来)

override func loadView() {
    super.loadView()
    self.view =
        (SelectedScrollContentView.init(frame: self.view.bounds, targetViewController: self) {
            self.data.forEach { [weak self] item in
                let vc: EmploymentInformationViewController = EmploymentInformationViewController.init()
                vc.title = item.title
                vc.ID = item.id
                self?.addChildViewController(vc)
            }
        }
    )!
}

Bad

 func requestLive() -> Observable<LiveSchedules?> {
    return LiveProvider.request(.List)
        .mapObject(ResultSingle<Live>).map { ret -> LiveSchedules? in
            xxx
        }
}

Good(注意:换行,以及将闭包体括起来)

func requestLive() -> Observable<LiveSchedules?> {
     return LiveProvider.request(.List)
         .mapObject(ResultSingle<Live>)
         .map ({ ret -> LiveSchedules? in
             xxx
         })
 }
  • fix shorthand_operator

Bad

balance = balance / 100

Good

balance /= 100

Bad

time = time - 1

Good

time -= 1
  • fix closure_parameter_position

Bad

backView.snp_makeConstraints {
         (make) in
         xxxxx
     }

Good

backView.snp_makeConstraints { (make) in
          xxxxx
      }

Bad

self.model?.cover.forEach({
         [weak self] cover in
        xxx
     })

Good

self.model?.cover.forEach({ [weak self] cover in
           xxx
        })
  • fix generic_position

Bad

NSMutableArray <CourseItem> *array = [NSMutableArray new];

Good

NSMutableArray<CourseItem> *array = [NSMutableArray<CourseItem> new];
  • �默认的访问控制修饰符是 internal, 如果需要使用internal 可以省略不写

Bad

public var verificationCode: RACReplaySubject = RACReplaySubject()

Good

var verificationCode: RACReplaySubject = RACReplaySubject()

Bad

public func getVerificationCode() { // xxx }

Good

func getVerificationCode() { // xxx }

(20170216)补充:

  • 善用 guard,会使你的逻辑更加清晰

    Bad

class MainRouter {
    static func jumpByType(naviVC: UINavigationController?, link: String?) {
        if let navi = naviVC {
            if let jumpUrl = link {
                let isValidUrl = StringHelper.verifyUrl(urlString: jumpUrl)
                if isValidUrl {
                    Logger.logInfo(message: "is valid url: \(isValidUrl)")
                        navi.pushViewController(WebViewController(url: link!, shareEnable: false), animated: true)
                }
            }
        }
    }
}

Good

class MainRouter {
    static func jumpByType(naviVC: UINavigationController?, link: String?) {
        guard let naviVC = naviVC, let link = link else {
            return
        }
        let isValidUrl = StringHelper.verifyUrl(urlString: link)
        if isValidUrl {
            Logger.logInfo(message: "is valid url: \(isValidUrl)")
            naviVC.pushViewController(WebViewController(url: link, shareEnable: false), animated: true)
        }
    }
}
  • 善用嵌套函数、访问修饰符、Extensions、 protocol,代码整体逻辑有没有更清晰。此控制器是VIPER架构中的V,有兴趣学习VIPER架构的同学可以看我的这篇文章

Bad

import UIKit
import PullToRefresh

class HomeViewController: UIViewController, HomeViewInput {

    var output: HomeViewOutput!

    let refresher = PullToRefresh()

    lazy var carsouselView: CarouselViewController = {
        return CarouselViewController(path: "app-dept3-carousel")
    }()

    lazy var wikiCardView: WikiCardView = {
        return WikiCardView()
    }()

    lazy var scrollView: UIScrollView = {
        return UIScrollView()
    }()

    override func loadView() {
        super.loadView()

        view.backgroundColor = UIColor.HexRGB(rgbValue: 0xf5f5f5)

        view.addSubview(scrollView)

        let screenWidth = UIScreen.main.bounds.width
        let screenHeight = UIScreen.main.bounds.height
        let statusBarHeight = UIApplication.shared.statusBarFrame.height

        scrollView.frame = CGRect(x: 0, y: statusBarHeight, width: screenWidth, height: screenHeight)
        scrollView.contentSize = CGSize(width: 0, height: screenHeight + 1)

        scrollView.addSubview(carsouselView.view)

        carsouselView.view.snp.makeConstraints { make in
            make.width.equalTo(scrollView.snp.width)
            make.height.equalTo(160)
            make.top.equalTo(scrollView)
        }

        scrollView.addSubview(wikiCardView)

        wikiCardView.snp.makeConstraints { make in
            make.width.equalTo(scrollView.snp.width)
            make.height.equalTo(333)
            make.top.equalTo(carsouselView.view.snp.bottom).offset(10)
        }

        configViews()

        setupPullToRefresh()

        output.viewIsReady()
    }

    func configViews() {
        let homeConfigurator = HomeModuleConfigurator()
        homeConfigurator.configureModuleForViewInput(viewInput: self)

        let carsouselConfigurator = CarouselModuleConfigurator()
        carsouselConfigurator.configureModuleForViewInput(viewInput: carsouselView)
    }

    func setupPullToRefresh() {
        scrollView.addPullToRefresh(refresher) { [weak self] in
            print("PullToRefresh")
            self!.reloadData()
        }
    }

    // MARK: HomeViewInput
    func setupInitialState() {

    }

    func refreshBanner(banner: Banner) {
        Logger.logInfo(message: "refresh banner")
        carsouselView.setBanner(banner: banner)
    }

    func refreshWiki(course: CourseData) {
        Logger.logInfo(message: "refresh wiki")
        wikiCardView.setData(courseData: course)
    }

    func loadDataSuccess() {
        Logger.logInfo(message: "load data success")
        scrollView.endRefreshing(at: Position.top)
    }

    func reloadData() {
        output.reloadData()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        initNaviBar()
    }

    func initNaviBar() {
        if let naviVC = self.navigationController {
            naviVC.setNavigationBarHidden(true, animated: false)
        }
    }

    // MARK: Life cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    deinit {
        if let topPullToRefresh = scrollView.topPullToRefresh {
            scrollView.removePullToRefresh(topPullToRefresh)
        }
    }

}

Good

import UIKit
import PullToRefresh

final class HomeViewController: UIViewController {

    // MARK: Properties

    var output: HomeViewOutput!

    private let refresher = PullToRefresh()

    fileprivate lazy var carsouselView: CarouselViewController = {
        return CarouselViewController(path: "app-dept3-carousel")
    }()

    fileprivate lazy var wikiCardView: WikiCardView = {
        return WikiCardView()
    }()

    fileprivate lazy var scrollView: UIScrollView = {
        return UIScrollView()
    }()


    // MARK: Life cycle

    override func loadView() {
        super.loadView()

        view.backgroundColor = UIColor().backGroudColor_grad

        func addSubviews() {
            view.addSubview(scrollView)
            scrollView.addSubview(carsouselView.view)
            scrollView.addSubview(wikiCardView)
        }

        func configViews() {
            let homeConfigurator = HomeModuleConfigurator()
            homeConfigurator.configureModuleForViewInput(viewInput: self)

            let carsouselConfigurator = CarouselModuleConfigurator()
            carsouselConfigurator.configureModuleForViewInput(viewInput: carsouselView)
        }

        func layoutSubViews() {
            let screenWidth = UIScreen.main.bounds.width
            let screenHeight = UIScreen.main.bounds.height
            let statusBarHeight = UIApplication.shared.statusBarFrame.height

            scrollView.frame = CGRect(x: 0, y: statusBarHeight, width: screenWidth, height: screenHeight)
            scrollView.contentSize = CGSize(width: 0, height: screenHeight + 1)

            carsouselView.view.snp.makeConstraints { make in
                make.width.equalTo(scrollView.snp.width)
                make.height.equalTo(160)
                make.top.equalTo(scrollView)
            }

            wikiCardView.snp.makeConstraints { make in
                make.width.equalTo(scrollView.snp.width)
                make.height.equalTo(333)
                make.top.equalTo(carsouselView.view.snp.bottom).offset(10)
            }
        }

        func setupPullToRefresh() {
            scrollView.addPullToRefresh(refresher) { [weak self] in
                print("PullToRefresh")
                func reloadData() {
                    self?.output.reloadData()
                }
                reloadData()
            }
        }

        addSubviews()

        layoutSubViews()

        configViews()

        setupPullToRefresh()

        output.viewIsReady()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        func initNaviBar() {
            if let naviVC = self.navigationController {
                naviVC.setNavigationBarHidden(true, animated: false)
            }
        }
        initNaviBar()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    deinit {
        if let topPullToRefresh = scrollView.topPullToRefresh {
            scrollView.removePullToRefresh(topPullToRefresh)
        }
    }
}

extension HomeViewController: HomeViewInput {
    func setupInitialState() {

    }

    func refreshBanner(banner: Banner) {
        Logger.logInfo(message: "refresh banner")
        carsouselView.setBanner(banner: banner)
    }

    func refreshWiki(course: CourseData) {
        Logger.logInfo(message: "refresh wiki")
        wikiCardView.setData(courseData: course)
    }

    func loadDataSuccess() {
        Logger.logInfo(message: "load data success")
        scrollView.endRefreshing(at: Position.top)
    }
}


欢迎大家修正,填补。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容