YH_ModuleData.m 8.56 KB
//
//  YH_ModuleData.m
//  YH_WatchDog
//
//  Created by redding on 16/3/7.
//  Copyright © 2016年 YOHO. All rights reserved.
//

#import "YH_ModuleData.h"
#import "YH_WatchDog.h"
#import <objc/runtime.h>

@implementation YH_ModuleData
{
    NSString *_queueName;
    __weak YH_WatchDog *_bridge;
    NSLock *_instanceLock;
    BOOL _setupComplete;
}

@synthesize services = _services;
@synthesize instance = _instance;
@synthesize serviceQueue = _serviceQueue;

- (instancetype)initWithModuleClass:(Class)moduleClass
                             bridge:(YH_WatchDog *)bridge
{
    if ((self = [super init])) {
        _moduleClass = moduleClass;
        _bridge = bridge;
        
        _instanceLock = [NSLock new];
    }
    return self;
}

- (instancetype)initWithModuleInstance:(id<YH_BridgeModule>)instance
                                bridge:(YH_WatchDog *)bridge
{
    if ((self = [self initWithModuleClass:[instance class] bridge:bridge])) {
        _instance = instance;
    }
    return self;
}

#pragma mark - private setup methods

- (void)setBridgeForInstance
{
    NSAssert(_instance, @"setBridgeForInstance called before %@ initialized", self.name);
    if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
        @try {
            [(id)_instance setValue:_bridge forKey:@"bridge"];
        }
        @catch (NSException *exception) {
            NSLog(@"%@ has no setter or ivar for its bridge, which is not "
                        "permitted. You must either @synthesize the bridge property, "
                        "or provide your own setter method.", self.name);
        }
    }
}

- (void)finishSetupForInstance
{
    if (!_setupComplete) {
        _setupComplete = YES;
        [self setUpServiceQueue];
        [self services];
        [[NSNotificationCenter defaultCenter] postNotificationName:YH_DidInitializeModuleNotification
                                                            object:_bridge
                                                          userInfo:@{@"module": _instance}];
    }
}

- (void)setUpServiceQueue
{
    if (!_serviceQueue) {
        NSAssert(_instance, @"setUpMethodQueue called before %@ initialized", self.name);
        BOOL implementsServiceQueue = [_instance respondsToSelector:@selector(serviceQueue)];
        if (implementsServiceQueue) {
            _serviceQueue = _instance.serviceQueue;
        }
        if (!_serviceQueue) {
            
            // Create new queue (store queueName, as it isn't retained by dispatch_queue)
            _queueName = [NSString stringWithFormat:@"cn.yoho.watchdog.%@Queue", self.name];
            _serviceQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
            
            // assign it to the module
            if (implementsServiceQueue) {
                @try {
                    [(id)_instance setValue:_serviceQueue forKey:@"serviceQueue"];
                }
                @catch (NSException *exception) {
                    NSLog(@"%@ is returning nil for its methodQueue, which is not permitted. You must either return a pre-initialized queue, or @synthesize the methodQueue to let the bridge create a queue for you.", self.name);
                }
            }
        }
    }
}

#pragma mark - public getters

- (BOOL)hasInstance
{
    return _instance != nil;
}

- (id<YH_BridgeModule>)instance
{
    [_instanceLock lock];
    if (!_setupComplete) {
        if (!_instance) {
            _instance = [_moduleClass new];
        }
        // Bridge must be set before methodQueue is set up, as methodQueue
        // initialization requires it (View Managers get their queue by calling
        // self.bridge.uiManager.methodQueue)
        [self setBridgeForInstance];
    }
    [_instanceLock unlock];
    
    [self finishSetupForInstance];
    
    return _instance;
}

- (NSString *)name
{
    return YH_BridgeModuleNameForClass(_moduleClass);
}

- (NSDictionary<NSString *, id<YH_BridgeService>> *)services
{
    if (!_services) {
        NSMutableDictionary<NSString *, id<YH_BridgeService>> *moduleServices = [NSMutableDictionary dictionary];
        
        [self instance];
        
        unsigned int methodCount;
        Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
        
        for (unsigned int i = 0; i < methodCount; i++) {
            Method method = methods[i];
            SEL selector = method_getName(method);
            if ([NSStringFromSelector(selector) hasPrefix:@"__yh_export__"]) {
                IMP imp = method_getImplementation(method);
                NSArray<NSString *> *entries = ((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
                NSString *serviceName = entries[0];
                
                //检查服务是否需要生成队列
                SEL queueGetSelector = NSSelectorFromString([self queueGetSelector:serviceName]);
                SEL queueSetSelector = NSSelectorFromString([self queueSetSelector:serviceName]);
                NSOperationQueue *queue = nil;
                if ([_instance respondsToSelector:queueGetSelector] && [_instance respondsToSelector:queueSetSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                    queue = [_instance performSelector:queueGetSelector];
#pragma clang diagnostic pop
                    if (!queue) {
                        queue = [NSOperationQueue new];
                        queue.maxConcurrentOperationCount = 1;
                        queue.name = [NSString stringWithFormat:@"cn.yoho.watchdog.%@ServiceOperationQueue", serviceName];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                        [_instance performSelector:queueSetSelector withObject:queue];
#pragma clang diagnostic pop
                    }
                }
                
                //检查服务是否声明回调signal
                //已声明则存入service
                id signal = nil;
                NSString *signalSelector = [self signalSelector:serviceName];
                if ([_instance respondsToSelector:NSSelectorFromString(signalSelector)]) {
                    @try {
                        signal = [(NSObject *)_instance valueForKey:signalSelector];
                    }
                    @catch (NSException *exception) {
                        NSLog(@"%@ has no getter or ivar for service signal %@", self.name, signalSelector);
                    }
                }
                id<YH_BridgeService> moduleService = [[YH_ModuleService alloc] initWithServiceName:serviceName methodSignature:entries[1] moduleClass:_moduleClass callbackSignal:signal queue:queue];
                moduleServices[serviceName] = moduleService;
            }
        }
        
        free(methods);
        
        _services = [moduleServices copy];
    }
    return _services;
}

- (dispatch_queue_t)serviceQueue
{
    [self instance];
    return _serviceQueue;
}

/**
 * 根据serviceName生成对应service queue getter的SEL字符串
 * 如:serviceName:login
 *    signal:   loginOperationQueue
 */
- (NSString *)queueGetSelector:(NSString *)serviceName
{
    NSString *queueSelector = [NSString stringWithFormat:@"%@ServiceOperationQueue", serviceName];
    return queueSelector;
}

/**
 * 根据serviceName生成对应service setter queue的SEL字符串
 * 如:serviceName:login
 *    signal:   setLoginOperationQueue
 */
- (NSString *)queueSetSelector:(NSString *)serviceName
{
    NSString *queueSelector = [NSString stringWithFormat:@"set%@ServiceOperationQueue:", serviceName];
    return queueSelector;
}

/**
 * 根据serviceName生成对应service signal的SEL字符串
 * 如:serviceName:login
 *    signal:   loginSignal
 */
- (NSString *)signalSelector:(NSString *)serviceName
{
    NSString *signalSelector = [NSString stringWithFormat:@"%@Signal", serviceName];
    return signalSelector;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"<Module %@: %p;>", [self name], self];
}

- (NSString *)debugDescription
{
    NSMutableString *output = [NSMutableString string];
    [output appendFormat:@"Module:%@ %p; \n", [self name], self];
    if (_setupComplete) {
        [output appendFormat:@"Module already setup\n"];
    }  else {
        [output appendFormat:@"Module waiting for setup\n"];
    }
    [output appendFormat:@"Module services:\n"];
    for (id<YH_BridgeService> item in [_services allValues]) {
        [output appendFormat:@"  %@\n", [item debugDescription]];
    }
    [output appendFormat:@"\nModule serviceQueue:%@\n", _serviceQueue];
    
    return output;
}

@end