iOS开发快速进阶与实战
上QQ阅读APP看书,第一时间看更新

1.2 类方法的self

引用1.1节中的内容,下面通过alloc和initWithName的方式来创建Person对象:

虽然禁用了init方法,但是通过super调用父类的init并不会有影响。如果经常使用到,这个方法仍然显得有些麻烦,可以通过类方法来使创建过程更加简捷:

     + (instancetype)personWithName:(NSString *)name;

既然有了类方法来创建,我们也就不需要对外提供-initWithName:方法了,按照1.1节中的方法,将其标注为不可用。

因为之前有-initWithName:的实现,我们希望在新加入的+personWithName:方法中可以直接调用,以下是错误代码。

报错的原因很简单,就是因为我们禁用了Person类的-initWithName:方法,解决办法如下。

将Person换成self调用就不会报错,但据我们所知,在类方法中使用这两者似乎并没有什么区别,实际上self是表示当前类,而不一定是Person类。使用类方法创建实例,用[self alloc]和[Person alloc]在基本使用上是没有任何区别和影响的,但是并不代表二者没有区别,甚至会有使用上的误区,更甚者会导致意料之外的错误发生。

为了方便解释,下面再举一个类似的例子,创建一个Animal类,且为Animal类添加一个类方法类创建实例,并将默认的init设置为不可用。

此时,如果将+animalWithName:方法中的[[self alloc]init]换成[[Animal alloc]init],则会报错,错误很明显,是因为我们将init方法设置为不可使用,编译器在此时会自动设置为该方法不可使用。那为什么用[[self alloc]init]就会不报错了呢?难道是编译器的bug?当然不是,请接着看。

我们要继承Animal来创建它的子类Dog:

     // Dog.h
     # import "Animal.h"
     @interface Dog : Animal
     @end

此时,如果使用下面这句代码来创建,仍然会报错。原因是init方法在父类Animal方法中被禁用,所以在子类中仍然不可使用。

     Dog * dog = [[Dog alloc] init];

假设在+animalWithName:方法中用的是Animal,那么用它来创建Dog实例肯定是不对的,因为当前类是Dog,如果再用Animal来创建肯定是错误的,等于是创建了一个Animal实例来赋给Dog对象。如果用self来创建的话则没有此问题,因为对于Dog类来说,self即指的是Dog,self代表具体当前是哪一个类,因此返回结果是Dog实例,这是我们所希望的,所以这也是为什么应该在Animal类中+animalWithName:方法中使用[self alloc]而不是[Animal alloc]。

     Dog * dog = [Dog animalWithName:@"Hachiko"];

在此基础上,假设一种很新奇的场景,因为Animal对我们来说是很概念性、很抽象的类,所以我们提供了+animalWithName:方法,而禁止-init方法,但Dog类是非常具体的,我们希望可以保留-init方法来为Dog类提供另外一种初始化方法,当然-init方法仍然是对Animal不可使用的。可以将-init方法在Dog的头文件中再声明一次,表示对该方法在此处可以破例使用。

     // Dog.h
     # import "Animal.h"
     @interface Dog : Animal
- (instancetype)init;
@end

此时,就可以在任意处使用-init方法来为Dog创建实例,当然-init方法对于Animal类来说仍然是不可用的。

然而,事情并没有我们想的那样容易,虽然在这种场景下的问题已经解决了,我们又有了另外一个新的问题,编译器报了一个没有实现-init方法的警告!虽然我们在Dog类中设置-init方法再次可用了,而且我们知道,-init方法在NSObject基类中是有默认实现的,此时此处报警告从主观意识上来说有一些不合情理,但是毕竟是负责任的编译器,为了防止我们会有这样的隐患,可以手动去除警告。

     // Dog.m
     # import "Dog.h"
     # pragma clang diagnostic push
     # pragma clang diagnostic ignored "- Wincomplete - implementation"
     @implementation Dog
     @end
     # pragma clang diagnostic pop

本节小结

(1)类方法中的self指的是当前类,而不是固定的某个类,还可能是这个类的子类;

(2)对于父类禁用的方法,需要在子类头文件中再次声明才可以使用,并需要在类实现文件中去除编译器警告。