美文网首页Swift开发RXSwift
Swift代码规范 2022-02-24 周四

Swift代码规范 2022-02-24 周四

作者: 勇往直前888 | 来源:发表于2022-03-27 08:33 被阅读0次

    语言规约

    命名规范

    • 【强制】Swift并不需要使用;结束一行代码。

    • 【推荐】变量命名多参考苹果库或者优秀的开源库的命名方式。比如Swift 3.0开始,枚举类型首字母都改成小写,去掉了冗余信息,比如UIColor.redColor变成UIColor.red。argument label也去掉了冗余信息,变得非常简洁。

    //Swift 2.3
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    
    //Swift 3.0,cellForRowAtIndexPath简化成cellForRowAt。
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    
    • 【强制】虽然Swift命名倾向于不加任何前缀,但是仍然强制所有的自定义类型加上一个统一的前缀,比如阿里云App统一使用ALY。

    • 【强制】extension跟Objective-C一样,函数必须加一个前缀,比如aly_loadImage,便于理解和使用。不同的模块给同一个类增加相同命名的扩展,编译链接都不会有问题。但是如果同时import这些模块,调用同名的扩展方法会报下面这个错误。

    • 【强制】extension跟Objective-C一样,函数必须加一个前缀,比如aly_loadImage,便于理解和使用。不同的模块给同一个类增加相同命名的扩展,编译链接都不会有问题。但是如果同时import这些模块,调用同名的扩展方法会报下面这个错误。

    main.swift:10:5: error: 'test' is inaccessible due to 'internal' protection level
    str.test()
    ^
    <unknown>:0: note: 'test' declared here
    <unknown>:0: note: 'test' declared here
    
    • 【强制】专有名词,如ECS,使用大写,即使出现在方法和属性中。

    代码组织

    • 【推荐】相同逻辑代码、同一个protocol函数的实现等,比如使用//MARK: ALYUIViewControllerRefreshDataProtocol标记,方便阅读代码。

    • 【推荐】类的属性使用lazy var实现,并且放到class的后面,方便阅读代码。

    lazy var textLabel : UILabel = {
        let label = UILabel()
        label.font = UIFont.aly_f10
        label.textColor = UIColor.aly_ct_2
        label.textAlignment = .center
        label.text = "添加"
        self.contentView.addSubview(label)
    
        return label
    }()
    

    最佳实践

    消除警告

    • 【强制】在Build Settings里面找到Swift Compiler - Warning Policies,将Treat Warning as Erros设置为Yes,Swift这个设置跟Objective-C不在一起,消除一切编译警告非常有必要。

    • 【强制】返回值不需要强制使用的,请使用@discardableResult关键字,否则会产生warning。

    @discardableResult
    func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
        return SessionManager.default.request(urlRequest)
    }
    

    避免严重的崩溃

    • 【推荐】不要强解Optional类型。强解非常危险,刚开始使用Swift开发非常容易在这块犯错误,导致crash率居高不下。可以通过guard let、if let、??来避免强解。
    //guard let适合后面有大量代码依赖foo有值
    guard let _foo = foo else {
        return
    }
    
    print(_foo)
    
    //if let比较灵活
    if let _foo = foo {
        //do something
    }
    
    //??更加灵活,但是一行代码使用过多??可能有性能问题。
    print(foo?? "hello world")
    

    避免内存泄露

    • 【强制】闭包使用weak或者unowned避免循环依赖。如果明确外部变量在执行代码的过程中,不会变成nil,那么使用unowned,比如视觉元素的lazy代码块中。网络接口的回调是异步的,回调发生时,页面可能已经不存在了,这种场景下,需要使用weak。
    let cell = ALYCommonCellObject.Builder()
        .title(title: "使用许可协议", color: UIColor.aly_black)
        .selectionAction(select: { [unowned self] (cell) in
            self?.utlogCounter("Setting", withMonitorPoint: "TermOfService")
        })
        .build()
    
    • 【强制】deinit里面要移除对所有通知和KVO的观察。

    合理选择数据类型

    • 【推荐】尽量使用Swift的类型,而非Objective-C的桥接类型,比如使用URL而非NSURL,IndexPath而非NSIndexPath。

    • 【参考】数据对象尽量使用struct,而非class。struct是Swift的基础类型,翻看苹果的基础库,可以发现所有的基础类型,比如Int、String等类型都是struct。

    • 【强制】Swift支持字符串枚举类型,表达清晰,不易犯错,是一个非常好用的功能。

    enum ALYVote : String {
        case approve = "approve"
        case clean   = "clean"
        case oppose  = "oppose"
    }
    
    • 【强制】Swift 3.0新增了Decimal类型,使用的便捷性比之前桥接的NSDecimalNumber有质的飞越,需要使用高精度的场景,比如跟钱有关系的,一定要使用Decimal。
    let foo : Decimal = 10.12373223423
    let bar : Decimal = 1.23423432432432
    
    print(foo+bar) //11.3579665585543176192
    print(foo-bar) //11.3579665585543176192
    print(foo*bar) //12.4950578137552000654026872358296354816
    

    合理选择修饰符

    • 【推荐】函数和类的声明采用最小够用使用原则,加上private、final、open、public、internal(默认)等修饰符。
      a. 函数使用final修饰会走静态分发,性能更好。
      b. private修饰则不向外暴露,编译器优化可做内联。final和private都可以减少Xcode自动提示的信息量,提高Xcode的反应速度,好处多多。
      c. 对于动态库暴露的类,open表示可以被继承的接口,public表示不能被继承的接口。明确不需要被外部继承使用的,请使用public关键字。

    • 【参考】关键的Swift代码,如果考虑未来需要打hotpatch,那么接口可以使用dynamic修饰,走Objective-C的动态派发。

    使用尾随闭包

    • 【强制】如果函数接受一个闭包作为参数,那么将闭包放在最后一个位置,方便用户采用最简方式调用。

    • 【推荐】使用闭包的最简调用方式。

    //最复杂版本
    let fullGreetings = guestList.map({(person: String) -> String in return "Hello, \(person)!"})
    
    //最简单版本
    let fullGreetings = guestList.map{ "Hello, \($0)!" }
    

    使用Swift的新方式

    • 【强制】统一使用下面这种单例的编写方式,非常简洁,混编的时候也能被Objective-C识别。
    class ALYXXX {
        static let sharedInstance = ALYXXX()
        private override init() {}
    }
    
    • 【推荐】多用lazy var声明属性,代码紧凑、好看、好维护。
    lazy var textLabel : UILabel = {
        let label = UILabel()
        label.font = UIFont.aly_f10
        label.textColor = UIColor.aly_ct_2
        label.textAlignment = .center
        label.text = "添加"
        self.contentView.addSubview(label)
    
        return label
    }()
    
    • 【推荐】foreach遍历数组非常简洁美观。map、filter能用的也尽量用起来吧。
    self.groupList.forEach { (id, name) -> Void in
        vc.groupList[id] = name
    }
    
    • 【推荐】defer可以简化异常处理逻辑,在作用域结束的时候,会执行defer代码块。
    if exists(filename) {
        let file = open(filename, O_READ)
    
        defer close(file)
    
        while let line = try file.readline() {
            //
        }
    }
    

    优秀的Swift开发资源

    • 【推荐】尽量采用Swift开源库,减少混编的场景。

    • 【推荐】Swift处理JSON不是一件容易的事情,推荐使用HandyJSON。我们从Swift 2.x一直用到3.0,非常稳定且好用。

    • 【推荐】ObjectMapper是比较传统的JSON解析方式。如果场景比较简单,也是不错的选择。

    • 【推荐】使用SnapKit写AutoLayout约束。

    Swift与Objective-C混编

    • 【强制】Swift不支持宏,所以要使用变量。
    //#define NW_NETWOEK_STATUS_NOTIFY @"TBNetworkStatusChangeNotify"
    extern NSString* const NW_NETWOEK_STATUS_NOTIFY;
    
    • 【强制】Objective-C使用typedef enum定义的枚举类型,Swift不能使用,需要使用NS_ENUM或者NS_OPTIONS。
    //typedef enum {
    typedef NS_ENUM(NSUInteger, NetworkStatus) {
        NotReachable = 0,
        ReachableViaWiFi,
        ReachableVia2G,
        ReachableVia3G,
        ReachableVia4G
    };
    //} NetworkStatus;
    
    • 【强制】构造函数务必返回instanceType。如果返回id,Swift必须要转型才能使用。
    //返回id,在Swift就必须要转型了。
    //+ (id)sharedInstantce;
    //(TBLoginCenter.sharedInstantce() as? LoginProtocol)
    
    //使用instanceType符合规范
    + (instanceType)sharedInstantce;
    
    • 【推荐】合理使用Nullability Annotations,让Swift更加理解Objective-C的语义。
    - (__nullable id)itemWithName:(NSString * __nonnull)name;
    
    //NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END将中间的代码都加上nonull,
    //只需要对nullable的属性和参数单独声明就好了。
    //iOS SDK惯用这种方法。可以多跳进去看看。
    NS_ASSUME_NONNULL_BEGIN
    @interface Foo : NSObject
    @property (nonatomic, copy, nullable) NSString *bar1;
    @property (nonatomic, copy) NSArray *bar2;
    @end
    NS_ASSUME_NONNULL_END
    

    显著的坑

    • open lazy var和WMOSwift 3.0上会冲突,出现编译报错。如果不需要被继承,使用public。如果需要被继承,不采用WMO或不使用open关键字。
    //可能会出现编译问题
    open lazy var promptTitleLabel : UILabel = {
        let label = UILabel()
    
        return label
    }()
    
    • weak delegate需要使用class关键字。否则会报如下的错误:'weak' cannot be applied to non-class type 'MyClassDelegate'。这是因为 Swift 的 protocol 是可以被除了 class 以外的其他类型遵守的,而对于像 struct 或是 enum 这样的类型,本身就不通过引用计数来管理内存,所以也不可能用 weak 这样的 ARC 的概念来进行修饰。
    protocol MyClassDelegate: class {
        func method()
    }
    
    class MyClass {
        weak var delegate: MyClassDelegate?
    }
    
    class ViewController: UIViewController, MyClassDelegate {
        // ...
        var someInstance: MyClass!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            someInstance = MyClass()
            someInstance.delegate = self
        }
    
        //...
    }
    
    • 用private修饰的类,如果使用KVC来给属性设置值,编译不会报错,运行时也不会报错,但就是设置不上。去掉private就好了。

    • Swift和OC混着写的时候,有时候会出现OC的类在CloudConsoleApp-Bridging-Header.h里面提供给Swift使用,但是这个类又需要引入CloudConsoleApp-Swift.h使用Swift的一些功能,这样就循环包含了,没法玩下去了。

    • Swift的二进制兼容做的尤其差,如果向外输出二进制库的话,增加、删除属性;新增、删除、调整接口顺序,都会导致二进制不兼容,需要更新主版本号。

    相关文章

      网友评论

        本文标题:Swift代码规范 2022-02-24 周四

        本文链接:https://www.haomeiwen.com/subject/gwszlrtx.html