美文网首页iOS面试题+基础知识
分类、类扩展、继承的总结

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

作者: 蔚尼 | 来源:发表于2018-05-23 16:15 被阅读65次

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

    一.分类

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

    1.分类的特点

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

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

    (面试考点)


    分类的结构体
    1. 实例方法
    2. 类方法
    3. 协议
    4. 属性(@proterty修饰的)(未生成get、set方法,但是可以通过runtime添加get、set方法)。
      注意:分类不可以添加成员变量;
      分类可以访问原有类中.h的属性
    效果
    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数组里面的方法,从后往前添加到对象方法列表里面。所以相同的方法名,最后一个分类里面的方法先被调用。


      添加分类
    分类添加的顺序

    4.分类执行顺序

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

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

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

    添加分类
    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 <Foundation/Foundation.h>
    @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 <Foundation/Foundation.h>
    
    @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方法。先编译先调用。


    打印

    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))
    • 大型而复杂的类,可以把利用分类,把相关的方法放到多个单独的文件中,提高维护下和可读性;

    相关文章

      网友评论

        本文标题:分类、类扩展、继承的总结

        本文链接:https://www.haomeiwen.com/subject/ttirjftx.html