阅读 2050

iOS开发规范篇:清晰的初始化方法

@(iOS开发学习)[温故而知新]

一、Objective-C基本套路

二、Objective-C细枝末节

三、Swift中的指定构造方法和便利初始化方法

  • 3.1、为何要初始化?

  • 3.2、类初始化的几种方法

    • 3.2.1、Designated
    • 3.2.2、convenience
    • 3.2.3、required

一、Objective-C基本套路

日常开发中遇到的问题:

在平常的项目开发中,经常会遇到多人同时开发一个需求的场景。同事A提供了自定义初始化方法,但是同事B却调用了默认的初始化方法,因为同事A在自定义初始化方法中做了一些特殊操作,导致同事B使用默认初始化方法却没有达到预期的效果,然后又浪费了很多精力与同事A进行沟通查找问题。


几乎大多数程序员都是与团队内的其他成员合作完成一个项目,即使是自己独立开发一个项目,一个模块调用另外一个模块,都需要一个清晰明确的接口规范。当面对多个初始化方法时,外部调用者可能手无足措,不知道哪一个才是正确的初始化方法。为此苹果提供了两个关键字:NS_UNAVAILABLENS_DESIGNATED_INITIALIZER来帮助我们约束对象的初始化方法,使得接口描述更加清晰。

  • NS_DESIGNATED_INITIALIZER用来将修饰的方法标记为指定构造器

  • NS_UNAVAILABLE禁止使用某个初始化方法

一般都希望外部调用接口的时候,传入一些基本的参数用来初始化。而不希望使用默认的初始化方法,因此我们可以这么做:

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithName:(NSString*)name NS_DESIGNATED_INITIALIZER;
@end

@implementation Person
- (instancetype)initWithName:(NSString *)name {
    if ( self = [super init] ) {
        self.name = name;
    }
    return self;
}
@end
复制代码

当创建一个Person对象的时候,不能使用NS_UNAVAILABLE修饰的[Person new][[Person alloc]init]方法,而应该使用NS_DESIGNATED_INITIALIZER修饰的- (instancetype)initWithName:(NSString*)name方法。

// Xcode报错:'new' is unavailable
Person* person1 = [Person new];
// Xcode报错:'init' is unavailable
Person* person2 = [[Person alloc]init];
// 正确
Person* person3 = [[Person alloc]initWithName:@"XiaoMing"];
复制代码

二、Objective-C细枝末节

当想让调用者调用自己的构造方法的时候,就可以在.h文件中将自己的构造方法使用NS_DESIGNATED_INITIALIZER修饰


当不想让调用者调用父类的构造函数的时候,就可以在.h文件中将父类的构造方法使用NS_UNAVAILABLE修饰


如果子类实现了NS_DESIGNATED_INITIALIZER修饰的指定初始化方法,没有使用NS_UNAVAILABLE修饰父类的初始化方法。则需要在子类重写父类的指定初始化方法,并且在里面调用子类自己的指定初始化方法。因为如果一个类的方法被 NS_DESIGNATED_INITIALIZED 修饰,则改方法变成指定构造方法,从父类继承来的指定构造方法则变成便利初始化方法。 子类没有重写父类的指定初始化方法会报类似警告:

1、⚠️:Method override for the designated initializer of the superclass '-init' not found(没有找到父类的指定初始化方法)

2、⚠️:Convenience initializer missing a 'self' call to another initializer(便利初始化方法需要调用另外一个初始化方法)

重写父类的指定初始化方法后,不能调用super相关的方法。否则会报类似警告:

⚠️:Convenience initializer should not invoke an initializer on 'super'(便利初始化方法不能调用super初始化方法)


避免使用new创建对象,从安全和设计角度来说我们应该对初始化所有属性,提高程序的健壮性和复用性。

不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。 ——以上摘自《The Object-Oriented Thought Process》 by Matt Weisfeld


术语区分构造方法 vs 初始化方法

严格意义上Objective-c是没有构造函数的,我们所说的都是初始化方法,创建对象(alloc)之后调用实例的初始化方法initWithXXX

构造方法的写法:类名(参数列表...)

构造方法是一种特殊的方法,它是一个与类同名且返回值类型为同名类类型的方法。对象的创建就是通过构造方法来完成,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。


Objective-C与swift的初始化顺序的区别:

Objective-C先调用父类的初始化方法,然后初始自己的成员变量

swift先初始化自己的成员变量,然后在调用父类的初始化方法

三、Swift中的指定构造方法和便利初始化方法

class People {
    var name: String?
    // 在swift中属性不是可选类型的都必须初始化
    var age: Int
    // 指定初始化方法前面不需要添加修饰
    init() {
        age = 0
    }
    // 便利初始化方法前面需要添加convenience修饰
    convenience init(name: String) {
        self.init()
        self.name = name
    }
}
class Man: People {
    var mustacheLength: Int
    // 子类重写父类的指定初始化方法,需要使用override修饰
    override init() {
        self.mustacheLength = 0
    }
    // 便利初始化方法一:内部调用指定初始化方法
    convenience init(mustacheLength: Int) {
        self.init()
        self.mustacheLength = mustacheLength
        // 修改父类的属性值必须在子类的便利初始化方法内部调用完指定初始化方法后
        self.age = 1
    }
    // 便利初始化方法二:内部调用其他便利初始化方法,但是最后一个便利初始化方法内部还是要调用指定初始化方法
    convenience init(mustacheLength: Int, name: String = "") {
        self.init(mustacheLength: mustacheLength)
    }
}
复制代码

3.1、为何要初始化?

  • 系统要求存储属性必须初始化
  • 结构体系统默认会添加初始化方法,当然自己也可以自定义

3.2、类初始化的几种方法

3.2.1、Designated

  • 可选值可以不用初始化,如果不初始化值,系统默认用nil初始化它。
  • 如果类中含有非可选的存储属性并且没有默认值,则必须实现指定初始化方法,并且初始化该属性。
  • 如果子类没有自己的初始化方法,系统默认使用父类的初始化方法,一旦有了自己的初始化方法,或者重写了父类的初始化方法,则父类的所有初始化不能被子类调用。
  • 你可以给子类添加和父类相同的初始化方法,但需要加上override修饰。

3.2.2、convenience

  • 在同一个类,使用convenience修饰的初始化方法必须调用一个其他初始化方法。
  • convenience必须最终调用一个指定的初始化方法。
  • 重写父类的convenience修饰的方便初始化方法,不需要加override关键字。

3.2.3、required

  • 子类必须重写父类用required修饰的方法
  • 可以和convenience组合使用

参考资料

NS_UNAVAILABLE 与 NS_DESIGNATED_INITIALIZER

Swift 初始化(Initialization)

Swift-init初始化方法