美文网首页iOS 实用技术iOS Developer
利用Objective-C runtime构造可扩展的对象工厂中

利用Objective-C runtime构造可扩展的对象工厂中

作者: 呼神护卫 | 来源:发表于2017-01-24 20:45 被阅读86次

    对象工厂中心是我生造的一个词,指的是整个程序中,一类对象由唯一的对象工厂创建。

    我们用一个实际的场景来说明。假设要实现一个IM模块,为App提供即时通讯支持。我们需要定义消息数据,并处理消息的发送和接收。在Objective-C的世界中,可以定义消息基类,然后定义一组子类表示不同类型的消息,比如文字,语音,URL链接消息等。而处理网络通信时,需要将对象数据序列化,就不得不用一个type字段来区别不同的消息类型。

    序列化很简单,在基类中定义抽象序列化方法,子类中重写它,输出相应的序列化字符串。但在序列化过程中,消息对象的多态性丢失了,反序列化就需要一个对象工厂,依据type字段来确定需要创建的消息对象的类型。

    最简单的工厂实现,就是一串if-else:

    if ([type isEqualToString:@"typeA"]){
        return [[ModelA alloc] init];
    }else if ([type isEqualToString:@"typeB"]){
        return [[ModelB alloc] init];
    }
    ...
    

    问题是,每增加一种消息类型,就需要修改工厂的实现,增加一个if case。这不符合对扩展开放,对修改关闭的原则。而且工厂类需要依赖所有的数据子类。

    不同的消息可能由不同的模块处理。如果某个业务模块需要增加一种消息类型,我们当然不希望这种业务逻辑入侵到下层的IM模块,最好能让业务模块管理自己的消息子类,IM模块在不知道消息子类名字的情况下正确创建消息对象,返回给上层的业务模块。而对象初始化必须知道对象的类型,怎么办呢?

    我们可以利用Objective-C的动态特性。在Objective-C中,对象的类型由Class对象来描述,可以给Class发送alloc消息来创建对应的对象。而且利用Objective-C runtime,Class和NSString可以相互转化。

    在工厂类内部维护一个字典,保存type字段和对应的类型名称字符串,并对外开放一个注册Class的接口。上层业务模块将自己的子类注册到到工厂类,这样就能够通过type字段得到对应的Class,进而正确地进行对象初始化。

    尝试实现一下。定义MessageBase作为消息基类,以及默认的TextMessage。定义MessageFactory作为消息工厂。

    Message.h

    
    
    #import <Foundation/Foundation.h>
    
    static NSString * const MessageTypeText = @"text";
    
    @interface MessageBase : NSObject
    
    @property(nonatomic, strong, readonly)NSString *messageType;
    
    - (instancetype)initWithJsonDict:(NSDictionary *)jsonDict;
    
    @end
    
    @interface TextMessage : MessageBase
    
    @property (nonatomic, strong, readonly)NSString *content;
    
    @end
    

    MessageFactory.h

    #import <Foundation/Foundation.h>
    #import "Message.h"
    
    @interface MessageFactory : NSObject
    
    + (void)registerMessageClass:(Class)messageClass forType:(NSString *)messageType;
    
    + (__kindof MessageBase *)messageByJsonDict:(NSDictionary *)jsonDict;
    
    @end
    

    MessageFactory.m

    #import "MessageFactory.h"
    #import "Message.h"
    
    static NSString * const MessageTypeKey = @"message_type";
    
    static NSMutableDictionary<NSString *, NSString *>* messageTypeDict;
    
    @implementation MessageFactory
    
    + (void)registerMessageClass:(Class)messageClass forType:(NSString *)messageType
    {
        if (!messageTypeDict) {
            messageTypeDict = [[NSMutableDictionary alloc] init];
        }
        NSString *className = NSStringFromClass(messageClass);
        [messageTypeDict setObject:className forKey:messageType];
        NSLog(@"register class %@ for type %@", className, messageType);
    }
    
    + (__kindof MessageBase *)messageByJsonDict:(NSDictionary *)jsonDict
    {
        NSString *type = jsonDict[MessageTypeKey];
        if (!type || ![type isKindOfClass:[NSString class]]) {
            return nil;
        }
        NSString *className = messageTypeDict[type];
        if (!className) {
            return nil;
        }
        MessageBase *message = [(MessageBase *)[NSClassFromString(className) alloc] initWithJsonDict:jsonDict];
        return message;
    }
    
    @end
    

    现在,我们定义新的message类型后,只需要在程序开始时调用registerMessageClass:forType:接口注册,MessageFactory就能用相应的字典数据初始化它了。

    但调用注册接口还是有些麻烦,也可能会忘记调用。好在NSObject有一个很方便的+(void)load方法。它会在一个类被加载到runtime时调用,父类的方法先被调用,然后子类和cateogory的方法被调用。

    我们可以在load方法中完成对象类型的注册。如果需要支持新的类型,只需要给MessageFactory写一个category,实现自己的load方法,在里面注册新的类型即可。比如我们要默认注册TextMessage,并增加对超链接消息LinkMessage的支持:

    MessageFactory.m

    + (void)load
    {
        if (!messageTypeDict) {
            messageTypeDict = [[NSMutableDictionary alloc] init];
        }
        [self registerMessageClass:[TextMessage class] forType:MessageTypeText];
    }
    

    MessageFactory+LinkMessage.m

    #import "MessageFactory+LinkMessage.h"
    #import "LinkMessage.h"
    
    @implementation MessageFactory (LinkMessage)
    
    + (void)load
    {
        [self registerMessageClass:[LinkMessage class] forType:MessageTypeLink];
    }
    
    @end
    

    我们在registerMessageClass:forType:方法、main函数和application: didFinishLaunchingWithOptions:中加了log,可以在程序运行时看到类型注册发生的时间:

    2016-11-28 00:29:53.718 FactoryDemo[49999:7451402] register class TextMessage for type text
    2016-11-28 00:29:53.723 FactoryDemo[49999:7451402] register class LinkMessage for type link
    2016-11-28 00:29:53.723 FactoryDemo[49999:7451402] main called
    2016-11-28 00:29:53.846 FactoryDemo[49999:7451402] application didFinishLaunchingWithOptions
    

    可以看到load调用发生在程序启动之前。

    简单测试一下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        NSString *textMessageJson = @"{\"message_type\":\"text\",\"content\":\"hello\"}";
        NSString *linkMessageJson = @"{\"message_type\":\"link\",\"link\":\"http://www.baidu.com\"}";
        NSDictionary *textMessageDict = [NSJSONSerialization JSONObjectWithData:[textMessageJson dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
        NSDictionary *linkMessageDict = [NSJSONSerialization JSONObjectWithData:[linkMessageJson dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
        TextMessage *textMessage = [MessageFactory messageByJsonDict:textMessageDict];
        NSLog(@"text message content:%@",textMessage.content);
        LinkMessage *linkMessage = [MessageFactory messageByJsonDict:linkMessageDict];
        NSLog(@"link message url:%@",linkMessage.linkUrl);
        
    }
    

    如我们所料,成功创建了TextMessageLinkMessage对象:

    2016-11-28 00:29:53.852 FactoryDemo[49999:7451402] text message content:hello
    2016-11-28 00:29:53.852 FactoryDemo[49999:7451402] link message url:http://www.baidu.com
    

    如果……Swift?

    在Swift中虽然也能继承NSObject来使用Objective-C的runtime,但这毕竟不是Swift style。

    利用Swift的闭包特性,也可以实现可扩展的对象工厂中心。

    首先定义一个无参数,返回MessageBase的闭包类型:

    typealias MessageCreator = () -> MessageBase
    

    MessageFactory中用字典记录type字段和对应的MessageCreator闭包,客户模块将消息子类的创建写在闭包中,注册给MessageFactory即可。收到消息时,MessageFactory根据type字段取出闭包,然后闭着眼睛调用即可生成相应的消息对象。具体的实现就留给读者自己去完成吧。

    完整的demo代码可以从我的Github下载。

    相关文章

      网友评论

        本文标题:利用Objective-C runtime构造可扩展的对象工厂中

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