Objective-C runtime을 이용하여 확장 가능한 대상 공장 센터 구축

6980 단어
대상 공장 센터는 내가 만든 단어로 전체 프로그램에서 유일한 대상 공장이 만드는 것을 가리킨다.
우리는 실제 장면으로 설명한다.만약에 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];
}
...

문제는 메시지 유형을 추가할 때마다 공장의 실현을 수정하고ifcase를 추가해야 한다는 것이다.이것은 확장에 대한 개방, 수정에 대한 폐쇄의 원칙에 부합되지 않는다.그리고 공장류는 모든 데이터 부류에 의존해야 한다.
서로 다른 소식은 서로 다른 모듈로 처리될 수 있다.만약에 어떤 업무 모듈이 메시지 유형을 추가해야 한다면 우리는 당연히 이런 업무 논리가 하층의 IM모듈에 침입하는 것을 원하지 않는다. 가장 좋은 것은 업무 모듈이 자신의 메시지 서브클래스를 관리하도록 하는 것이다. IM모듈은 메시지 서브클래스의 이름을 모르는 상황에서 메시지 대상을 정확하게 만들고 상부의 업무 모듈로 돌아가는 것이다.대상 초기화는 대상의 유형을 알아야 하는데 어떻게 해야 하나요?
Objective-C의 동적 특성을 활용할 수 있습니다.Objective-C에서 대상의 유형은 Class 대상에 의해 설명되며, Class에 alloc 메시지를 보내서 해당하는 대상을 만들 수 있습니다.또한 Objective-C runtime을 활용하면 Class와 NSString이 서로 바뀔 수 있습니다.
공장 클래스 내부에서 사전을 유지하고 type 필드와 대응하는 형식 이름 문자열을 저장하며 등록클래스 인터페이스를 개방합니다.상부 업무 모듈은 자신의 하위 클래스를 공장 클래스에 등록하면 type 필드를 통해 대응하는Class를 얻을 수 있고 대상을 정확하게 초기화할 수 있다.
실현을 시도하다.메시지 기본 클래스와 기본 TextMessage로 MessageBase를 정의합니다.메시지 팩토리로 MessageFactory를 정의합니다.
Message.h


#import 

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 
#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* 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

현재, 우리가 새로운 메시지 형식을 정의한 후, 프로그램이 시작될 때 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:에 로그를 추가하여 프로그램이 실행될 때 유형 등록이 발생하는 시간을 볼 수 있다.
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 스타일이 아니다.
스위프트의 클로즈업 특성을 활용하면 확장 가능한 대상 공장 센터도 가능하다.
MessageBase의 패킷 유형을 반환하는 매개 변수 없음을 먼저 정의합니다.
typealias MessageCreator = () -> MessageBase
MessageFactory에서 type 필드와 대응하는 MessageCreator 클립을 사전으로 기록하고 클라이언트 모듈은 메시지 하위 클래스의 생성을 클립에 써서 MessageFactory에 등록하면 됩니다.메시지를 받았을 때MessageFactory type 필드에 따라 클립을 꺼내 눈을 감고 호출하면 해당하는 메시지 대상을 생성할 수 있습니다.구체적인 실현은 독자 스스로 하도록 남겨 두어라.
온전한 데모 코드는 나의 Github에서 다운로드할 수 있다.

좋은 웹페이지 즐겨찾기