init:、initWithFrame: 调用选择

前段时间准备面试时,突然被问到一个问题:init:initWithFrame: 方法应该调用哪个?为什么?

其实这个问题涉及的是 Objective-C 语法的 指定初始化方法(Designated Initializer) 和 间接初始化方法(Secondary Initializer) 的相关知识。

指定初始化方法

类本身

一个类应该只有一个 Designated Initializer,该初始化方法提供所有参数以供初始化。其他初始化方法中应该尽可能地调用 指定初始化方法。

下面以知名开源库 AFNetworking 为例,验证下 Designated Initializer 与 Secondary Initializer 之间的关系。

@interface AFURLSessionManager : NSObject

- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

@end


@implementation AFURLSessionManager

- (instancetype)init {
    return [self initWithSessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }

    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    // 以下代码省略
    ....
}

...
@end

AFURLSessionManager 继承自 NSObject ,除了默认的 init: 方法外,还定义了 initWithSessionConfiguration: 指定初始化方法。

在其内部实现中,在 init: 方法中调用了 Designated Initializer。

其中 NS_DESIGNATED_INITIALIZER 是编译器指令 __attribute__((objc_designated_initializer)) 的宏定义。用来标记 指定初始化方法。

#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))

在一个类中,封装 API 时,保证一个Designated Initializer,Secondary Initializer 调用 Designated Initializer。

类继承

那么对于继承关系时,应该如何处理呢?Apple 有这么一句话:

the object should first invoke its superclass's designated initializer to initialize inherited state

在类继承时调用任何 Designated Initializer 都是合法的,而且应该保证父类的 Designated Initializer 能够得到调用,这样会使继承关系上的类都得以正确的初始化。

虽然没有这样的明确规定,但 Apple 的框架都遵守这个约定,我们也应该尽力遵守。

当定义一个新类 API 时,有时会有不同的方式:

  1. 不需要重写任何初始化方法
  2. 重写 Designated Initializer
  3. 定义一个新的 Designated Initializer

对于第一种,不需要再做其他操作,只需要规范调用父类 Designated Initializer 方法即可。

对于第二种,需要在重写方法里调用 super 方法以初始化父类参数,然后再进行特定初始化。

对于第三种,确保调用了父类的 Designated Initializer,并且本类的 Secondary Initializer 调用自己的 Designated Initializer。

依旧以 AFNetworking 为例。AFHTTPSessionManager 继承于 AFURLSessionManager,并定义了自己的 Designated Initializer。

@interface AFHTTPSessionManager : AFURLSessionManager 

+ (instancetype)manager;

- (instancetype)initWithBaseURL:(nullable NSURL *)url;

// 指定初始化方法,且与父类 AFURLSessionManager 的指定初始化方法不同
- (instancetype)initWithBaseURL:(nullable NSURL *)url
           sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

@end


@implementation AFHTTPSessionManager

+ (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
}

- (instancetype)init {
    return [self initWithBaseURL:nil];
}

- (instancetype)initWithBaseURL:(NSURL *)url {
    return [self initWithBaseURL:url sessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
}

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    // 调用了父类的 Designated Initializer
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // 省略一些代码
    ....

    return self;
}

#pragma mark - NSSecureCoding
- (instancetype)initWithCoder:(NSCoder *)decoder {
    ...

    self = [self initWithBaseURL:baseURL sessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

        ...

    return self;
}

#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
    AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];

    HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
    HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
    HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
    return HTTPClient;
}

...
@end

从这里可以看出:

  1. 子类虽然定义了新的 Designated Initializer,但是依然会调用父类的 Designated Initializer;
  2. 子类的 Secondary Initializer 调用了本类的 Designated Initializer。

间接初始化方法

对于间接初始化方法,应尽可能地调用 指定初始化方法,以保证最终调用的是 指定初始化方法。调用时对于一些参数提供默认值或 nil。

小结

  1. 指定一个 指定初始化方法,以尽可能多的初始化参数,避免出现意外;
  2. 对于一个类来说,间接初始化方法 的实现应调用 指定初始化方法;
  3. 对于继承时有三种情况:
    1. 不重写任何初始化方法

      正常调用

    2. 重写指定初始化方法

      调用父类指定初始化方法

    3. 自定义一个新的初始化方法

      调用父类指定初始化方法,并本类遵循“间接初始化方法 的实现应调用 指定初始化方法”

参考

https://github.com/oa414/objc-zen-book-cn/#designated-initializer

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html#//apple_ref/doc/uid/20000948-BCIHBJDE

https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MultipleInitializers.html

你可能感兴趣的:(init:、initWithFrame: 调用选择)