iOS开发基础-继承和多继承

85 阅读6分钟

iOS开发基础-继承和多继承

概述

首先明确一点,OC是不支持多继承的。因为继承依赖于消息机制,selector的获取是在运行时而非编译时。但是在现有情况下,如果在声明了一个A类,同时想要这个A类继承B类和C类的方法去调用,一般需要使用别的方式来实现

实现方法

1 组合

在A类中引入且声明B和C的实例,可以通过调用B和C的实例来执行B和C中的方法,这种也是最常见的一种。

2 代理 delegate

在B和C中各自声明一份协议,A去遵守这两份协议,同时在A类中实现B和C协议中的方法和属性

3 消息转发 (消息转发越到后面代价越大 一般不建议)

运行时来通过消息转发,将当前消息转发给某一个类.到达一个类执行执行多个类的方法的目的. 当向某个对象发送消息,但runtime system在当前类以及父类中都找不到对应方法的实现时,runtime system并不会立即报错使程序崩溃,而是依次执行下列步骤

  1. 本类及父类中都找不到需要调用的方法
  2. 尝试动态解析方法 (向当前类发送 resolveInstanceMethod: 信号,检查是否动态向该类添加了方法)
  3. 解析不到 尝试进行快速消息转发 (检查该类是否实现了 forwardingTargetForSelector: 方法,若实现了则调用这个方法,若该方法返回nil或者非self,则向该返回对象重新发送消息)
  4. 快速转发失败,尝试标准消息转发 (runtime发送methodSignatureForSelector:消息获取Selector对应的方法签名。返回值非空则通过forwardInvocation:转发消息,返回值为空则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出)
  5. 标准转发失败 异常抛出(不处理会导致崩溃)

实现

实现快速消息转发需要重写: - (id)forwardingTargetForSelector:(SEL)aSelector 重写了方法以后一般有两种方法来实现

  • 强转类
  • 使用Category
强转方法实现

例如 需要在 A 类中 调用 B 类的方法,且两者没有继承关系

ClassA *a = [[ClassA alloc] init];
[(ClassB *)a methodB];

标准消息转发 需要在A中重写 - (void)forwardInvocation:(NSInvocation *)anInvocation- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

// 在转发消息前先对方法重新签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 尝试自行实现方法签名
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    // 若无法实现,尝试通过多继承得到的方法实现
    if (signature == nil) {
        // 判断该方法是哪个父类的,并通过其创建方法签名
        if ([_singer respondsToSelector:aSelector]) {
            signature = [_singer methodSignatureForSelector:aSelector];
        }
        else if ([_artist respondsToSelector:aSelector]) {
            signature = [_artist methodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
// 为方法签名后,转发消息
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    
    // 判断哪个类实现了该方法
    if ([_singer respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_singer];
    }
    else if ([_artist respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:_artist];
    }
}

或者直接消息转发 直接重写 - (id)forwardingTargetForSelector:(SEL)aSelector

- (id)forwardingTargetForSelector:(SEL)aSelector{
   ClassB *b = [[ClassB alloc] init];
   if([b respondsToSelector:aSelector]){
       return b;
   }
   return nil;
}
使用Category

Category又叫类别,分类等,能够在不改变原来类的内容基础上,为类增加一些方法,访问原有类中.h中的属性。如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类。多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。 Category有以下功能

  • 在不改变原来类的内容基础上,为类增加一些方法(只能为类增加方法,而不能添加属性)
Category
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *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; // 分类所实现的协议列表
}

Category 中有 实例方法列表,类方法类表,协议列表,但是没有属性列表。我们知道在一个类中用 @property 声明属性,编译器会自动帮我们生成 _成员变量名setter,getter方法,但是在分类的指针结构体中,根本就要没有属性类表,所以也就不能生成 _成员变量名setter,getter方法,因此我们的程序可以编译和运行都成功了,但是一旦有调用这个属性就会崩溃。

#import "Student+Category.h" 
#import <objc/runtime.h> 
static const char *schoolNameKey = "schoolNameKey"; 
@implementation Student (Category) 
- (void)setSchoolName:(NSString *)schoolName{ 
    objc_setAssociatedObject(self, &schoolNameKey, schoolName,OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 
- (NSString *)schoolName{ 
    return objc_getAssociatedObject(self, &schoolNameKey); 
} 
@end

OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法

  • 将类的实现分开写在几个分类里面
  • 声明私有的方法
  • 模拟多继承

引入扩展 《 Extension 》

Extension 是Category的一个特例,类扩展与分类相比只少了分类的名称以及.m文件,他常用的形式不是创建一个单独的文件,而是在实现文件中添加私有的成员变量、属性和方法。比如我们平时在创建一个类的时候我们会在.m文件中添加私有的成员变量、属性和方法,当然我们也可以在.h文件中添加,所以说Extension在我们平时的开发中是最常用的

  • Extension的作用是为一个类添加额外的私有成员变量,属性以及方法,只是属性默认的访问权限是 private
  • Extension是在编译阶段添加到类中,而Categor是在运行时添加到类中的
  • Extension不能像 Category 那样拥有独立的实现部分,他所声明的方法只能在其对应的类中实现
  • Extension定义在.m文件的方法是私有的,定义在.h文件的方法是公有的
  • Extension一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的Extension,所以对于系统一些类,如NSString,就无法添加类扩展,如果只是简单的创建Extension文件是能够创建成功的,但是如果你在文件中添加属性或者方法,在程序中一旦使用了该属性或者方法程序就会崩溃,会报找不到相对应的方法错误信息。

4 NSProxy 参考 iOS NSProxy详解 - 掘金 (juejin.cn)

NSProxy是和NSObject同级的一个类,但是更轻量级,可以说它是一个虚拟类抽象类,它只是实现了的协议,必须继承实例化其子类才能使用。NSProxy代理类专门负责代理对象转发消息的

OC是单继承的语言,但是基于运行时的机制,却有一种方法让它来实现一下"伪多继承",就是利用NSProxy这个类; 通过继承NSProxy,并重写这两个方法以实现消息转发到另一个实例。

    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

就是NSProxy不是继承NSProject,在运行过程中可以伪装成任何对象.其实也是消息转发