今日碰到一个很有意思的问题:OC如何重写(override)
- (id)copyWithZone:(NSZone *)zone
(P.S. 对原因不感兴趣的读者可以直接跳到文章最后看正确实现方法。)
1. 问题
举个栗子:
TL;DR
简单来说我们有Person
,Student
两个类,其中后者是前者的子类,都需要实现NSCopying
。
@interface Person : NSObject <NSCopying>
@property (nonatomic, strong) NSString *userId;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
Person *p = [[Person alloc] init];
p.userId = self.userId;
return p;
}
@end
@interface Student : Person <NSCopying>
@property (nonatomic, strong) NSString *studentId;
@end
2. 错误实现
大部分人首先想到的实现会是下面这样的:
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
Student *s = [super copyWithZone:zone];
s.studentId = self.studentId;
return s;
}
@end
但如果此时运行代码,Runtime将会报错:
Unrecognized selector setStudentId sent to instance xxx ...
问题出在哪里了呢?
这里:Student *s = [super copyWithZone:zone];
由于右边的返回值是一个父类Person
的实例,s便不是Student
。所以当调用 s.studentId = self.studentId
这个setter
时,便返回了以上错误。
3. 正解
理解了上面的错误之后,正确的解法应该是让[super copyWithZone:zone]
返回一个Student
实例。如下:
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
Person *p = [[[self class] alloc] init]; // <== 注意这里
p.userId = self.userId;
return p;
}
@end
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
Student *s = [super copyWithZone:zone];
s.studentId = self.studentId;
return s;
}
@end
4. 讨论
这个问题的根源在于对OC Runtime的理解不深,是个从其他OO编程语言过来的程序员常踩的坑。而且由于修正的地方在父类,一旦父类的源码不能或者很难被更改时,将会对子类造成很大的限制。其实要怪只能怪OC,OC这个语言本身对inheritance支持就很鸡肋。。。想要了解更多关于OC语言设计的读者,可以直接搜OC动态语言或者OC Runtime相关的文章,笔者就不赘述了。