美文网首页
iOS多继承的实现及区别

iOS多继承的实现及区别

作者: wvqusrtg | 来源:发表于2018-11-21 15:42 被阅读14次

    简介

    多继承可以允许子类从多个父类派生,而Objective-C并不支持多继承,但我们仍可间接实现。

    Objective-C实现多继承主要可以通过协议、分类、消息转发来实现。我们来总结一下其使用以及优缺点。

    通过协议实现

    协议主要是用来提出类应遵守的标准,但其特性也可用来实现多继承。一个类可以遵守多个协议,也即实现多个协议的方法,以此来达到多继承的效果。

    概念上的单继承和多继承应该是继承父类的属性和方法,并且不经重写即可使用,但通过协议实现多继承有如下不同:

    a.子类需要实现协议方法
    b.由于协议无法定义属性,所以该方法只能实现方法的多继承

    下面来看一下示例代码:

    // 编程技能
    @protocol Program <NSObject>
    - (void)program;
    @end
    // 绘画技能
    @protocol Draw <NSObject>
    - (void)draw;
    @end
    // 歌唱技能
    @protocol Sing <NSObject>
    - (void)sing;
    @end
    
    // 原本一个什么也不会的程序员
    // 学会了多个技能
    @interface Programmer : NSObject <Draw, Sing>
    // 继承的协议方法自动公有,无须声明接口
    @end
    
    @interface Programmer () <Program>
    // 继承的协议方法自动私有,无须声明接口
    @end
    
    // 需要自行实现协议方法
    @implementation Programmer
    - (void)program {
        NSLog(@"I'm writing bugs!");
    }
    - (void)draw {
        NSLog(@"I can draw");
    }
    - (void)sing {
        NSLog(@"Lalalallalalala");
    }
    @end
    

    通过下面的协议遵守,该程序员类掌握了编程、唱歌、绘画多个技能。同时也可以根据遵守协议的位置绝对协议方法是否公开,上述代码中draw和sing方法公开,而program为私有方法(毕竟唱歌画画是要展示给大家的 :P

    通过类别实现

    下面就有请Objective-C的一大黑魔法——Catagory(分类)。

    相对于协议,它的Runtime特性造就了其一定优势:

    a.可以为类添加方法
    b.可以为类添加实例(通过Runtime),这是协议做不到的
    c.分类方便管理

    下面来看一下通过分类来实现多继承:

    /*      分类头文件       */
    #import "Programmer.h"
    
    @interface Programmer (Program)
    // 声明属性
    @property (nonatomic, assign) NSString *motto;
    // 声明公有方法
    - (void)draw;
    - (void)sing;
    @end
    
    
    /*      分类实现文件       */
    #import <objc/runtime.h>
    
    @implementation Programmer (Program)
    // 为Catagory添加属性
    static const char kMottoKey;
    - (void)setMotto:(NSString *)motto {
        objc_setAssociatedObject(self, &kMottoKey, motto, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)motto {
        return objc_getAssociatedObject(self, &kMottoKey);
    }
    // 私有方法
    - (void)program {
        NSLog(@"I'm writing bugs!");
    }
    // 实现公有方法
    - (void)draw {
        NSLog(@"I can draw");
    }
    - (void)sing {
        NSLog(@"Lalalalallalala");
    }
    @end
    

    通过添加分类,我们可以为程序员添加各种方法,同时通过Runtime这一黑魔法实现了动态添加属性,我们可以直接通过点语法为程序员设置座右铭。

    同时,分类的文件在管理上也比较方便,如果不想用直接删除#import即可,灵活性较强。

    关于Catagory添加属性的方式可以自行学习Runtime。

    通过消息转发

    消息转发也是Runtime的黑魔法,其中一个用处就是可以实现多继承,当发送消息后找不到对应的方法实现时,会经过如下过程:

    1.动态方法解析: 通过resolveInstanceMethod:方法,检查是否通过@dynamic动态添加了方法。
    2.直接消息转发: 不修改原本方法签名,直接检查forwardingTargetForSelector:是否实现,若返回非nil且非self,则向该返回对象直接转发消息。
    3.标准消息转发: 先处理方法调用再转发消息,重写methodSignatureForSelector:和forwardInvocation:方法,前者用于为该消息创建一个合适的方法签名,后者则是将该消息转发给其他对象。
    4.上述过程均未实现,则程序异常。

    我们现在尝试2和3两种方案:

    /*  Programmer实现文件  */
    @implementation Programmer
    
    // 通过消息转发实现多继承
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        Singer *singer = [[Singer alloc] init];
        Artist *artist = [[Artist alloc] init];
        
        if ([singer respondsToSelector:aSelector]) {
            return singer;
        }
        else if ([artist respondsToSelector:aSelector]) {
            return artist;
        }
        return nil;
    }
    @end
    
    /*  Artist代码  */
    @interface Artist : NSObject
    // 画家可以绘画
    - (void)draw;
    @end
    
    @implementation Artist
    - (void)draw {
        NSLog(@"I can draw");
    }
    @end
    
    /*  Singer代码  */
    @interface Singer : NSObject
    // 歌手会唱歌
    - (void)sing;
    @end
    
    @implementation Singer
    - (void)sing {
        NSLog(@"Lalalalalala");
    }
    @end
    

    通过直接转发消息到其他类型的对象,我们就实现了多继承。如下调用,结果能够完成唱歌和绘画

    Programmer *programmer = [[Programmer alloc] init];
    
    // 在performSelector中使用NSSelectorFromString会造成警告
    // 可以通过设置不检测performSelector内存泄露关闭警告
    [programmer performSelector:NSSelectorFromString(@"sing") withObject:nil];
    
    // 或者通过类型强转来实现,无警告
    [(Artist *)programmer draw];
    
    

    通过消息转发,我们实现了动态性,及真正的将方法交给其他类来实现,而非协议或者分类所需要自行实现。同时,消息转发也给我们了充分的灵活性,如上代码,我们可以在Programer类声明sing和draw方法,但也可不暴露这些接口而通过类型强转来调用这两个方法。

    标准消息转发

    标准消息转发相对于直接消息转发更加高级,可以由程序员控制转发的过程,同时也可以实现对多个对象的转发,直接消息转发仅能把该方法直接转发给其他某对象。

    标准消息转发代码如下

    @interface Programmer ()
    {
        // 由于要频繁用到,我们可以创建成员实例
        Singer *_singer;
        Artist *_artist;
    }
    @end
    
    @implementation Programmer
    
    - (instancetype)init {
        if (self = [super init]) {
            _singer = [[Singer alloc] init];
            _artist = [[Artist alloc] init];
        }
        return self;
    }
    
    // 在转发消息前先对方法重新签名
    - (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];
        }
    }
    
    @end
    
    /*  Singer和Artist的实现同上述代码  */
    
    

    调用后,正常执行。实际上在实现多继承方面并没有必要使用标准消息转发,它可以在我们需要对消息进行处理时发挥其优势。

    包含父类方法

    这个其实就是为Programmer添加Singer和Artist成员变量,并为前者提供接口调用后两者的方法。这种方式虽然在效果上实现了多继承,但是没有实现的意义,既然所有东西都要在子类实现一遍,那么也有悖于"继承"这一概念,在此不再赘述。

    几种多继承方式的对比

    方法 添加属性 添加方法 继承父类的实现
    协议(Protocol)
    分类(Catagory)
    消息转发

    对于上述的内容进行一些补充:

    a.分类的属性实现是通过Runtime关联对象,而消息转发的属性实现也是类似方法。
    b.分类和消息转发更接近于真正意义的多继承,也方便管理,添加删除父类方便。

    作者:Minecode
    链接:https://www.jianshu.com/p/9601e84177a3
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    FAQ Runtime待复习

    相关文章

      网友评论

          本文标题:iOS多继承的实现及区别

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