@(iOS 开发学习)[温故而知新]
[TOC]
一, Objective-C 基本套路
日常开发中遇到的问题:
在平常的项目开发中, 经常会遇到多人同时开发一个需求的场景. 同事 A 提供了自定义初始化方法, 但是同事 B 却调用了默认的初始化方法, 因为同事 A 在自定义初始化方法中做了一些特殊操作, 导致同事 B 使用默认初始化方法却没有达到预期的效果, 然后又浪费了很多精力与同事 A 进行沟通查找问题.
几乎大多数程序员都是与团队内的其他成员合作完成一个项目, 即使是自己独立开发一个项目, 一个模块调用另外一个模块, 都需要一个清晰明确的接口规范. 当面对多个初始化方法时, 外部调用者可能手无足措, 不知道哪一个才是正确的初始化方法. 为此苹果提供了两个关键字: NS_UNAVAILABLE 与 NS_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 修饰, 则改方法变成指定构造方法, 从父类继承来的指定构造方法则变成便利初始化方法. 子类没有重写父类的指定初始化方法会报类似警告:
- ,:Method override for the designated initializer of the superclass '-init' not found(没有找到父类的指定初始化方法)
- ,: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 https://www.jianshu.com/p/d96d27819679
Swift 初始化(Initialization) https://www.jianshu.com/p/c62cf8a8967d
Swift-init 初始化方法 https://www.jianshu.com/p/c25a45220e53
来源: https://juejin.im/post/5c4c64c3f265da611f0809f1