分类、类扩展、继承的总结

因为最近在学习runtime,学习到关联对象的时候用到分类,所以顺便把分类复习了一下。我平时用继承多于分类,然后就很困惑的是,分类做的事情继承也能做,为什么要用分类呢?所以继续复习了一下继承。答案在后面,开始吧!

一.分类

详解:深入浅出理解分类(category)和类扩展(extension)
https://www.jianshu.com/p/9e827a1708c6

1.分类的特点

  1. 运行时决议;
  2. 可以为系统类添加分类;

2.分类中可以添加什么内容?

(面试考点)


分类、类扩展、继承的总结_第1张图片
分类的结构体
  1. 实例方法
  2. 类方法
  3. 协议
  4. 属性(@proterty修饰的)(未生成get、set方法,但是可以通过runtime添加get、set方法)。
    注意:分类不可以添加成员变量;
    分类可以访问原有类中.h的属性
分类、类扩展、继承的总结_第2张图片
效果
  1. 每个分类在编译的时候都是生成一个名叫catrgort_t的结构体;

3. Category的实现原理

  1. 每一个分类编译之后生成一个 struct category_t。里面存储着分类的对象方法、类方法、属性、协议信息;
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中;后面参与编译的Category数据,会在数组的前面。
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

或者说:

  1. Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
  2. 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

4. 分类的对象方法添加到类对象过程如下:

因为对象方法存储在类对象里面,所以分类的对象方法要添加到类对象里面。

  1. 有NSString+other2、NSString+other、NSString+other1三个分类,添加顺序如下图;
  2. 合并的时候:A数组里面放着NSString+other2、NSString+other、NSString+other1的对象方法。
  3. NSString的类对象里面,存放对象方法的list里面,先把NSString的对象方法挪到最后一个位置。再把A数组里面的方法,从后往前添加到对象方法列表里面。所以相同的方法名,最后一个分类里面的方法先被调用。


    分类、类扩展、继承的总结_第3张图片
    添加分类
分类、类扩展、继承的总结_第4张图片
分类添加的顺序

4.分类执行顺序

  1. 如果方法名和原有类里面的方法相同的话,会覆盖系统的方法;执行顺序:分类>本类>父类。所以尽量不要和原有类的方法重名。

  2. 如果不同的分类里面有相同的方法,调用的方法由编译器:targets->Build Phases->Complie Source里面添加的文件顺序决定,从上到下编译。调用这个方法时会执行最后一个参与编译的分类里面的方法;

如下:有NSString的三个分类,NSString+Other、NSString+Other1、NSString+Other2,假如三个分类里面都有方法A,调用方法A的时候。从上到下进行编译,会调用最后参与编译的NSString+Other1里面的方法A。

分类、类扩展、继承的总结_第5张图片
添加分类
  1. 名字相同的分类会引起报错

5.分类的使用

1.扩展现有类/添加非正式协议

2.分解体积庞大的类文件
分类可以将类的实现分散到不同文件或多个不同的框架当中。
1)减少单个文件的体积
2)把相同的功能放到同一个category中
3)按需加载想要的category
4)多个开发者可以一起完成一个类

3.声明私有方法/创建对私有方法的前向引用
定义在类名.h文件中的方法/属性一定是公开的;
而在类名.m中的类延展(Extension)中定义的方法/属性是私有的;
不在任何地方申明,只在类.m中写实现代码的方法都是私有的。

但是通过分类声明私有方法,就可以实现对私有方法的调用。

4.实现多继承
5.把Framework的私有方法公开

5.1扩展现有类

为现有类添加分类很普遍,不再展示;
补充的是非正式协议:
凡是NSObject或其子类的分类,都是非正式协议。

5.2分解体积庞大的类文件

分解体积庞大的类文件,就是把类的.h文件声明方法,类的实现放到分类里面。这样我们就可以按照需求把不同的实现放到不同分类里面。

实例如下:

  1. Teacher类的.h进行方法声明:read、teach方法
Teacher.h:
@interface Teacher : NSObject

@property(nonatomic,strong)NSString * teahcerName;

-(void)read;
-(void)teach:(NSString *)teachClass;

@end

Teacher.m:
@implementation Teacher

@end
  1. Teacher+Read实现Teacher的read方法:
Teacher+Read.h:
@interface Teacher (Read)

@end

Teacher+Read.m:
@implementation Teacher (Read)
//实现Teacher类的read方法
-(void)read{
    NSLog(@"read");
}
@end
  1. Teacher+Teach实现Teacher的teach方法:
Teacher+Teach.h:
@interface Teacher (Teach)

@end

Teacher+Teach.m:
@implementation Teacher (Teach)

//实现Teacher类的teach方法;
-(void)teach:(NSString *)teachClass{
    
    NSLog(@"%@ teach:%@",self.teahcerName,teachClass);
}
@end

4.ViewController调用Teacher的read、teach方法。成功调用

#import "Teacher.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //分解体积较大的类文件
    Teacher * teacher = [[Teacher alloc] init];
    teacher.teahcerName = @"xp";
    [teacher read];
    [teacher teach:@"English"];
    
}
@end

5.3 声明私有方法/创建对私有方法的前向引用

类的.m文件进行私有方法实现,分类.h进行私有声明。实现外部对私有方法的使用。这也叫做对私有方法向前引用。

实例:假如Student类中有私有方法study,怎么在ViewController中调用study方法呢?

Student.h:

#import 
@interface Student : NSObject
@end

Student.m:
@implementation Student
-(void)study{
    NSLog(@"study");
}
@end

方法1:
使用performSelector在Viewcontroller中调用:

    Student * stu = [[Student alloc] init];
    [stu performSelector:@selector(study) withObject:nil];

方法2(私有方法前向引用):
添加Student的分类。如下,可以把分类的方法声明直接添加到Studnt的.h文件中,分类不需要实现方法。
注意分类可以添加到Student.h中,也可以新建分类文件。但是把分类添加到Student.m中仍旧调用不到Student的私有方法。

Student.h文件:
#import 

@interface Student : NSObject
@end

//添加分类
@interface Student(Study)
//声明私有方法
-(void)study;
@end

ViewController调用:

    //分类的前向引用
    Student * stu = [[Student alloc] init];
    [stu study];
    

总结:要运用类的私有方法,可以运用分类进行私有方法声明既可。
相当于类.m进行方法实现。分类.h进行方法声明。

5.4 实现多继承

在oc里面是没有多继承的。我们现在要间接实现多继承的功能。
继承,就是可以使用父类的内容;
多继承,就是可以使用多个父类的内容,比如调用多个类的方法。

现在,我们需要让ClassA调用ClassB的方法,也可以调用ClassC的方法。我们需要使用分类和消息转发。

如下:

  1. 有ClassA类进行消息转发,接收到方法后,ClassB和ClassC谁能执行对应的方法就让谁执行:
ClassA.h:
@interface ClassA : NSObject
@end

ClassA.m:
@implementation ClassA
//消息转发,ClassB和ClassC谁能执行对应的方法就返回谁
-(id)forwardingTargetForSelector:(SEL)aSelector{
    
    ClassB * b = [ClassB new];
    ClassC * c = [ClassC new];
    if ([b respondsToSelector:aSelector]) {
        return b;
    }else if ([c respondsToSelector:aSelector]){
        return c;
    }
    return nil;
}
@end

2.有ClassB类声明并实现methodB方法:

ClassB.h:

@interface ClassB : NSObject
-(void)methodB;
@end

ClassB.m:
@implementation ClassB
-(void)methodB{
    NSLog(@"methodB");
}
@end

3.有ClassC类声明并实现methodC方法:

ClassC.h:
@interface ClassC : NSObject
-(void)methodC;
@end

ClassC.m:
@implementation ClassC
-(void)methodC{
    NSLog(@"methodC");
}
@end

4.在Viewcontroller里面,让ClassA调用ClassB、ClassC的方法:
方法1:使用performSelector调用

    ClassA * a = [ClassA new];
    [a performSelector:@selector(methodB)];

方法2:把ClassA的对象强转为ClassB的

    ClassA * a = [ClassA new];    
    [(ClassB *)a methodB];

方法3: ClassA.h中增加ClassA的分类,声明ClassB和ClassC中的方法

ClassA.h文件:
//增加ClassA的分类;声明ClassB和ClassC中的方法
@interface ClassA(SuperForOtherClass)

-(void)methodB;
-(void)methodC;

@end

ViewController里面:

    ClassA * a = [ClassA new];
    [a methodB];
    [a methodC];

总结:运用消息转发确定执行的对象、分类引用方法。从而达到ClassA调用其他对象的方法。

5.5 把Framework的私有方法公开

6. load、initialize

6.1 load方法:

load的调用时机

  • +load方法会在runtime加载类、分类时调用
  • 每个类、分类的+load,在程序运行过程中只调用一次

load的调用顺序

  • 先调用类的+load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会先调用父类的+load

  • 再调用分类的+load
    按照编译先后顺序调用(先编译,先调用)

如果分类写了和原本类相同的方法,调用的时候只调用分类的方法。但是load方法不是。
load方法的调用,直接是去找这个方法的地址,进行调用;不是消息机制调用(消息机制调用:通过isa指针,找到类对象的对象方法、元类对象的类方法),所以不是只调用分类的方法。

如下:Student:Person。有分类Person+Test1、Person+Test2、Student+Test1、Student+Test2。加载的时候:
1)先调用类的load方法、类里面先调用父类的load方法:所以先调用Person的load方法,再调用Student的load方法。
2)再调用分类的load方法。先编译先调用。


分类、类扩展、继承的总结_第6张图片
打印

Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

  • 有load方法
  • load方法在runtime加载类、分类的时候调用
  • load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

6.2 initialize方法:

initialize调用时机

+initialize方法会在类第一次接收到消息时调用
(通过消息机制调用,+initialize放在元类对象里面)

调用顺序

  • 先调用父类的+initialize,再调用子类的+initialize
    (先初始化父类,再初始化子类,每个类只会初始化1次)

+load和+initialize的区别:

1.调用方式:

  • +load是根据函数地址直接调用;
  • +initialize是通过消息机制调用;

2.调用时刻:

  • +load是在加载类、分类的时候调用,之后加载一次
  • +initialize是类第一次接收到消息的时候调用,每一个类之后+ initialize一次。但是父类的+ initialize会被调用多次。

3.调用顺序:

  • load
    1>load是先调用类的load
    先编译的先调用;
    如果有父类,先调用父类的load;
    2>再调用分类的load
    先编译的先调用;

  • initaialize:
    1>先初始化父类
    2>再初始化子类(可能最终调用的是父类的initaialize方法)
    3> 如果分类实现了+initialize,就覆盖类本身的+initialize调用

+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用

二.类扩展

类扩展的形式(其实平时我们在.m文件里面经常写):

@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end

总结:

  1. 可以添加属性、成员变量,都是私有的。(只能自身类里面使用,子类或者其他方法不可用)
  2. 可以添加方法,但是如果方法不实现的话会报警告;
  • 分类里面如果有方法未实现的话不会报警告。因为类扩展是在编译时期添加到类里面,编译时期发现未实现就可以警告了。分类的方法是在运行的时候添加到类里面的;
  1. 类扩展没有自己的实现部分(@implementation),必须依靠对应类的实现部分来实现;
  2. 定义在.m里面的类扩展的方法是私有的,定义在.h文件中的类扩展的方法为共有的。
分类 扩展
运行时决议 编译时决议
可以有声明、实现 只以声明的形式存在,多数情况下寄生于宿主类的.m中
可以为系统类添加分类 不可以为系统类添加扩展
是在运行时,才会将数据合并到类信息中 在编译的时候,它的数据就已经包含在类信息中

三.继承

详解:https://casatwy.com/tiao-chu-mian-xiang-dui-xiang-si-xiang-yi-ji-cheng.html

使用继承应该满足以下3点:

  1. 父类只是提供了服务。父类和子类没有业务关系;(Object提供了基础服务,比如内存计数功能)
  2. 层级关系明显,子类和父类各做各的。如果一件事情在父类做了,子类又做了,那他们就是并列关系了。
  3. 父类进行了修改子类要有所体现,这时候父类和子类是耦合了的,要有耦合需求才行。(ApiManager里面判断了网络状态,所有的派生类都是需要的。此时,牵一发而动全身,是适合继承的)。

回答开头的问题:如果继承就可以实现的,为什么要用分类呢?

因为继承容易造成代码耦合,再抽取出某一个功能的时候难。特别是第三层继承的时候就已经属于继承滥用了。
继承可以实现代码复用,但是要满足了以上三条才可使用。

四.继承和分类的区别

继承 分类
继承可以添加属性,并且自动生成属性的get、set方法 分类添加属性后,需要使用runtime的关联对象生成get、set方法
继承的方法名如果和父类的一样,可以把父类的实现也添加上(super) 分类的方法名和系统的一样,会把系统原的方法覆盖,无法添加上系统的实现;
  • 继承要满足三大要求后使用,否则会发生高耦合;
  • 如果只是增加某一个方法,可以使用分类。(eg:针对系统的一些类进行扩展(例如,NSString, NSArray, NSNumber))
  • 大型而复杂的类,可以把利用分类,把相关的方法放到多个单独的文件中,提高维护下和可读性;

你可能感兴趣的:(分类、类扩展、继承的总结)