美文网首页iOS 面试专项
iOS 中几种多继承的实现方式

iOS 中几种多继承的实现方式

作者: 远方竹叶 | 来源:发表于2020-12-07 12:21 被阅读0次

    单继承与多继承概念

    继承是面向对象的基本特征之一,在具体语言的语法上设计有两种形式:多继承与单继承。

    单继承

    一个子类只有一个父类

    • 优点:类层次机结构清晰,设计上更容易把握
    • 缺点:在丰富度要求较高和较复杂的情况下,单继承从设计结构上无法满足

    多继承

    一个子类可以有多个父类

    • 优点:由于同时具备多个父类的特征,让子类拥有更高的丰富度
    • 缺点:会让继承的结构变得更复杂,而且会出现菱形继承的风险

    Objective-C 不支持多继承,但我们可以间接的实现(协议、分类、消息转发)

    通过协议实现多继承

    一个类可以遵守多个协议,需要实现多个协议的方法,以此来达到多继承的效果。概念上的多继承和多继承应该是继承父类的属性和方法,并且不需要重写即可使用,通过协议实现多继承有以下不同:

    • 子类需要实现协议方法
    • 由于协议无法定义属性(只是声明 setter/getter 方法),所以该方法只能实现方法的多继承

    通过一个示例来理解:创建一个 Person 类,继承自 NSObject,添加三个协议,如下:

    /**------.h------*/
    //游泳
    @protocol Swim <NSObject>
    
    - (void)swim;
    
    @end
    
    //吃饭
    @protocol Eat <NSObject>
    
    - (void)eat;
    
    @end
    
    //睡觉
    @protocol Sleep <NSObject>
    
    - (void)sleep;
    
    @end
    
    //添加游泳、吃饭技能,继承的协议方法自动公有
    @interface Person : NSObject<Swim, Eat>
    
    @end
    
    /**------.m------*/
    //添加睡觉技能,继承的协议方法自动私有
    @interface Person () <Sleep>
    
    @end
    
    @implementation Person
    
    //需要实现协议方法
    - (void)swim {
        NSLog(@"游泳");
    }
    
    - (void)eat {
        NSLog(@"吃饭");
    }
    
    - (void)sleep {
        NSLog(@"睡觉");
    }
    
    @end
    

    原本的 Person 类什么都没有,通过遵守协议,使 Person 类拥有了 游泳吃饭睡觉 三个功能。协议的位置决定协议方法是否公开,上述 Personswimeat 方法是公开的,sleep 方法是私有的。

    通过类别实现多继承

    相对于协议,分类方法有一定的优势

    • 可以为分类添加方法
    • 可以为分类添加实例(通过 runtime 的关联属性),这是协议做不到的
    • 分类方便管理

    同样的我们用分类实现上述协议实现的功能外再添加一个属性,创建一个 Person 的分类,如下:

    /**------.h------*/
    @interface Person (Ability)
    
    // 声明属性
    @property (nonatomic, copy) NSString *username;
    
    // 声明共有方法
    - (void)swim;
    - (void)eat;
    
    @end
    
    /**------.m------*/
    @implementation Person (Ability)
    
    // 为分类添加属性
    static const char *kUSerName = "kUserName";
    
    - (NSString *)username {
        return objc_getAssociatedObject(self, kUSerName);
    }
    
    - (void)setUsername:(NSString *)username {
        objc_setAssociatedObject(self, kUSerName, username, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    // 实现公有方法
    - (void)swim {
        NSLog(@"游泳");
    }
    
    - (void)eat {
        NSLog(@"吃饭");
    }
    
    // 私有方法
    - (void)sleep {
        NSLog(@"睡觉");
    }
    
    @end
    

    通过添加分类,我们可以添加各种方法,同时 Runtime 为我们实现了动态添加属性,可以直接通过点语法设置属性。而且分类文件在管理上也比较方便,灵活性更强。

    通过消息转发实现多继承

    关于消息转发的流程可以查看 objc_msgSend 流程分析(消息转发),这里我只针对 快速转发慢速转发 这两步来实现上述功能

    快速转发实现

    我们通过重写 forwardingTargetForSelector 方法来转发对象

    // Swim 类--游泳
    @interface Swim : NSObject
    
    - (void)swim;
    
    @end
    
    @implementation Swim
    
    - (void)swim {
        NSLog(@"游泳");
    }
    
    @end
    
    // Eat 类--吃饭
    @interface Eat : NSObject
    
    - (void)eat;
    
    @end
    
    @implementation Eat
    
    - (void)eat {
        NSLog(@"吃饭");
    }
    
    @end
    
    @implementation Person
    
    // 快速消息转发
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        Swim *swim = [[Swim alloc] init];
        Eat  *eat  = [[Eat alloc] init];
        
        if ([swim respondsToSelector:aSelector]) {
            return swim; // 转发给 swim 对象
        }
        else if ([eat respondsToSelector:aSelector]) {
            return eat; // 转发给 eat 对象
        }
        return nil;
    }
    
    @end
    

    通过消息转发给其他对象实现了多继承,调用如下

    Person *p = [[Person alloc] init];
    
    //1.在performSelector中使用NSSelectorFromString会造成警告,可以通过设置不检测performSelector内存泄露关闭警告
    [p performSelector:NSSelectorFromString(@"eat")];
    
    //2.类型强转
    [(Eat *)p eat];
    

    通过消息的快速转发,实现了动态性,真正的将方法交给其他类来实现,而非协议或者分类所需要自行实现。同时,消息转发也给我们了充分的灵活性,在不暴露这些接口的情况下通过类型强转来调用。

    慢速转发实现

    慢速转发由程序员控制转发的过程,同时也可以实现对多个对象的转发,快速转发只能把该方法直接转发给其他某对象

    @interface Person ()
    {
        // 创建成员实例因为后面会频繁用到
        Swim *_swim;
        Eat  *_eat;
    }
    
    @end
    
    @implementation Person
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            _swim = [[Swim alloc] init];
            _eat  = [[Eat alloc] init];
        }
        
        return self;
    }
    
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        
        // 尝试自行实现方法签名
        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
        if (methodSignature == nil) { // 若无法实现,尝试通过多继承得到的方法实现
            
            // 判断方法是哪个父类的,通过其创建方法签名
            if ([_swim respondsToSelector:aSelector]) {
                methodSignature = [_swim methodSignatureForSelector:aSelector];
            }
            else if ([_eat respondsToSelector:aSelector]) {
                methodSignature = [_eat methodSignatureForSelector:aSelector];
            }
        }
        return methodSignature;
    }
    
    // 为方法签名后,转发消息
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL sel = [anInvocation selector];
        
        // 判断哪个类实现了该方法
        if ([_swim respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:_swim];
        }
        else if ([_eat respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:_eat];
        }
    }
    
    @end
    

    通过上述慢速消息转发在效果上实现了多继承,但是没有实现的意义,因为所有的东西都要在子类实现一遍。

    分类和消息转发更接近于真正意义的多继承,也方便管理,添加删除父类方便

    相关文章

      网友评论

        本文标题:iOS 中几种多继承的实现方式

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