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的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C继承与多态

Objective-C的封装继承与多态

Objective-C中的封装继承多态分类

继承与多态C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态

C#中的继承与多态还有接口

odoo使用委托继承产品模板,需要更改表单上categ_id的默认值