Objective-C中的继承与多态, Category, Extension
Posted __Sunshine_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C中的继承与多态, Category, Extension相关的知识,希望对你有一定的参考价值。
1、继承与多态
先要理解实例变量的作用域:
再看继承:
即:子类的方法和属性 = 从父类继承得到的方法和属性 + 子类新增的方法和属性
例子:
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject //其父类是NSObject
NSString *_name; //实例变量默认是@protected的
@private
int _age; //父类的私有实例变量
-(void)run; //声明run方法
@end
// Person.m
#import "Person.h"
float _salary; //定义一个父类的隐藏实例变量
@implementation Person
-(void)run //实现run方法
NSLog(@"person run!");
@end
// Student.h
#import "Person.h"
@interface Student : Person //Student类继承于Person类
-(void)printStu;
@end
// Student.m
#import "Student.h"
@implementation Student
-(void)printStu
NSLog(@"student run!");
NSLog(@"name: %@", _name); //使用继承自父类的实例变量
-(void)work
NSLog(@"s w");
@end
注意:
(1)父类的私有实例变量虽然能被子类继承,但不能被子类使用。如在Student类的printStu方法使用 Person类的private属性 _age则报错:
(2)父类的隐藏实例变量(在 .m文件定义的,也有的地方叫作私有变量,但觉得跟 private 的属性搞混)不能被子类继承。如如在Student类的printStu方法使用 Person类的隐藏实例变量_salary则报错:
(3)OC中的继承是单继承。即一个类至多只能有一个父类。
(4)子类可以重写父类的方法。当子类对从父类继承得到方法不满意时,可以改写之,只需要在子类的 .m文件定义一个返回类型、方法名、参数都与父类原方法相同的方法。如上例中父类Person类已经定义了run方法,Student类可以在其.m文件中添加定义:
-(void)run //重写run方法,覆盖父类的run方法
NSLog(@"student run!");
调用时,父类的对象调用父类的方法,子类的对象调用子类的方法,不引起冲突:
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
int main(int argc, const char * argv[])
@autoreleasepool
Person *person = [Person new]; //定义一个父类Person的对象
Student *student = [Student new]; //定义一个子类Student的对象
[person run]; //父类的对象调用父类的run方法
[student run]; //子类的对象调用子类的run方法
return 0;
//运行结果:
(5)继承体系中方法调用寻找顺序:
当前类 —(没找到)—> 父类 —(没找到)—> 父类的父类 —(没找到)—> … —-(没找到)–> NSObject —(没找到)—> 报错
多态
使用多态的条件:(1)有继承;(2)有方法的重写;(3)父类指针指向子类对象
如对于上面例子,Student类继承了Person类,现在再创建一个Teacher类也继承Person类,然后两个子类都重写了run方法:
// Teacher.h
#import "Person.h"
@interface Teacher : Person //继承了Person类
@end
// Teacher.m
#import "Teacher.h"
@implementation Teacher
-(void)run //重写run方法,覆盖父类的run方法
NSLog(@"teacher run!");
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
#import "Person+doSomething.h"
#import "Teacher.h"
int main(int argc, const char * argv[])
@autoreleasepool
Person *person = nil; //定义一个父类Person的对象
person = [Student new]; //此时person指针指向一个子类Student的对象
[person run]; //调用的是Student中的run方法
person = [Teacher new]; //此时person指针指向一个子类Teacher的对象
[person run]; //调用的是Teacher中的run方法
return 0;
多态的原理:
动态绑定:动态类型能使程序直到运行时才确定对象所属的类型。动态类型绑定能使程序直到运行时才确定要对象调用的方法。
注意:
(1)存在多态时,父类可以调用子类特有的方法,但要经过强制类型转换
(2)不存在多态时,父类不可以调用子类特有的方法,即使是强制类型转换也不行:
2、Category
OC特有,被翻译为:分类、类别、类目
作用:在不修改原有的类、不需要原有的类的代码的基础上增加新的方法(也只能是方法,不能增加实例变量)。
使用条件及好处:
(1)在分模块开发一个庞大的类时,有利于分工合作。
(2)替代子类的继承。因为继承可能影响原有的继承系统,而且只能单继承。而一个类可以有多个Category。
(3)一个Category中可以有多个方法,所以可以利用它来将方法归类,使得更好地更新和维护。
使用方法:声明 ——> 实现 ——> 使用。
如给上面例子的Person类新增Category:
// Person+doSomething.h 注意文件名为“原类名+Category名”
#import "Person.h"
@interface Person (doSomething) //Person为原有的类,doSomething为Category名
-(void)work; //声明方法
@end
// Person+doSomething.m
#import "Person+doSomething.h"
@implementation Person (doSomething) //形式与.h文件一样
-(void)work //实现方法
NSLog(@"person work");
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+doSomething.h" //导入声明文件
int main(int argc, const char * argv[])
@autoreleasepool
Person *person = [Person new];
//person可以像使用自身拥有的其他方法一样地调用Category为Person类新增的方法
[person work];
return 0;
注意:
(1)category只能给类增加方法,不能增加成员变量、@property(可能编译不报错,但运行有问题)。如:
// Person+doSomething.h
#import "Person.h"
@interface Person (doSomething)
@property int _height; //试图在category增加成员变量
-(void)work;
@end
(2)category 的 @implementation……@end 可以单独放在一个 .m文件,也可以放在对应的 .h文件中 @implementation……@end 之后。也可以两者都放在原有类的 .h文件的 @implementation……@end之后,直接声明、实现category。
(3)category可以使用原有类的私有实例变量,但原有类的隐藏实例变量仍对category不可见。如:
(4)当有多个分类和原有类都有同名方法时,原有类的方法被覆盖,而且执行最后编译的category文件的方法。如
// Person.m
#import "Person.h"
@implementation Person
-(void)run //在原有的Person类中实现run方法
NSLog(@"person run!");
@end
// Person+doSomething.m
#import "Person+doSomething.h"
@implementation Person (doSomething)
-(void)run //在第一个category doSomething中实现run方法
NSLog(@"person run at doSomething!");
@end
// Person+play.m
#import "Person+play.h"
@implementation Person (play)
-(void)run //在第二个category play中实现run方法
NSLog(@"person run at play!");
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+doSomething.h"
#import "Person+play.h"
int main(int argc, const char * argv[])
@autoreleasepool
Person *person = [Person new];
[person run]; //在主函数中调用run方法
return 0;
//输出结果:person run at doSomething!
因为在编译文件中,Person+doSomething.m 排在最后:
所以在 category 中是可以重写原有类的方法的,但系统会报警告:
而且也不推荐在 category 中重写原有类的方法,因为:
(1)一旦在category中重写了方法,则原有类的同名方法被覆盖掉,再也不能访问,所以也不能像子类那样发送消息给super就可以调用父类的同名方法,所以不能在原有方法的基础上增加功能,除非你想重复写那些功能。
(2)从上面的例子我们可以知道当多个类拥有同名的方法时,调用这个方法的结果貌似不是我们能控制的。
(3)在category中重写的方法不仅影响到原有的类,而且会影响原有类的子类,因为那些子类很可能继承并使用了这个方法,后果不言而喻。
了解了category的应用和优缺点,我们再来看下它的原理:
在Objective-C Runtime Reference中的简介:
Category
An opaque type that represents a category.
即Category是一种类型,这个类型代表了一个分类。
而在runtime.h中的定义:
typedef struct objc_category *Category;
所以Category是指向一个objc_category结构体的指针。而objc_category结构体的声明为:
struct objc_category
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
OBJC2_UNAVAILABLE;
分别存储了指向分类名、原有类的名称、对象方法列表的的结构体、类方法列表的结构体、遵守的协议列表的结构体的五个指针。
其中结构体objc_method_list定义如下:
struct objc_method_list
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
而我们定义的方法:
Method
An opaque type that represents a method in a class definition.
即也是一个类型,这个类型代表了在类定义中的一个方法。
在runtime.h中的定义:
typedef struct objc_method *Method;
所以Method是指向一个objc_method 结构体的指针。而 objc_method 结构体的声明为:
struct objc_method
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
所以一个 objc_method 结构体存储了单个方法的方法名、方法类型、实现方法的地址。其中IMP是“implementation”的缩写,定义和描述如下:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
Discussion
This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
其中:
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
对常量OBJC_OLD_DISPATCH_PROTOTYPES的解释:
Dispatch Function Prototypes
This macro indicates whether dispatch functions must be cast to an appropriate function pointer type.
亦即IMP是一个指针,指向了方法实现的函数的首地址,即类似C语言中的函数指针。这个实现函数有三个参数,第一个参数是对象自己(self),第二个是参数是方法选择器SEL,之后的参数是方法的参数。
所以,和调用类的方法一样,要调用category中的方法,也是要将方法名包装成SEL,然后在category中找含有方法数组列表的结构体,然后找单个方法,寻找的方法是将包装的SEL与每个方法的SEL型名称对比,两者一致为找到方法,然后通过IMP型指针找到实现方法的函数,调用该函数,即可完成调用category的方法。
(3)非正式协议(informal protocol)
是个特殊的category,特殊在于非正式协议是给根类 (root class,如 NSObject)增加方法。非正式协议一般不需要进行实现,一般在子类中进行方法的重写。
3、Extension
也是个特殊的category,特殊在与它没有名字!所以也叫匿名分类。
而它不仅可以给原有类增加方法,还可以增加成员变量。
如:
// Person_study.h extension的头文件,名字为“原有类名_extension名”
#import "Person.h"
@interface Person () //括号内没有名字
@private
int studyTime; //在extension中增加了成员变量
-(void)studyHard; //在extension中增加了study方法
@end
// Person.m
#import "Person.h"
#import "Person_study.h" //要导入extension的.h文件才能使用增加的成员变量
@implementation Person
-(void)studyHard //在原有类的.m文件中实现在extension study中声明的方法
NSLog(@"person study at %d", studyTime); //可以使用extension study中增加的成员变量
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person_study.h" //导入声明文件
int main(int argc, const char * argv[])
@autoreleasepool
Person *person = [Person new];
[person studyHard]; //调用在extension中增加的方法
return 0;
最后对比一下:
以上是关于Objective-C中的继承与多态, Category, Extension的主要内容,如果未能解决你的问题,请参考以下文章