objcChannelImplementationString top-level property

String objcChannelImplementationString
getter/setter pair

Implementation

String objcChannelImplementationString = '''
#import "#{projectPrefix}ChannelManager.h"

#if defined(__has_include) && __has_include("MJExtension.h")
#import "MJExtension.h"
#else
#import <MJExtension/MJExtension.h>
#endif

NS_ASSUME_NONNULL_BEGIN

@interface #{projectPrefix}ChannelManager ()

@property (nonatomic, copy) NSString *channelName;
@property (nonatomic, strong, nullable) FlutterMethodChannel *methodChannel;
@property (nonatomic, strong) NSMutableDictionary<NSString *, id> *methodImplementations;

@end

@implementation #{projectPrefix}ChannelManager

+ (instancetype)sharedManager
{
    static #{projectPrefix}ChannelManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc] init];
    });

    return manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.channelName = @"com.example.channelname";
        self.methodImplementations = [NSMutableDictionary dictionary];
    }
    return self;
}

#pragma mark - Public Methods

- (void)initializeWithBinaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
{
    __weak typeof(self) weakSelf = self;
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:self.channelName binaryMessenger:messenger];
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if ([weakSelf.delegate respondsToSelector:@selector(manager:didReceviceMethodCall:)]) {
            [weakSelf.delegate manager:weakSelf didReceviceMethodCall:call];
        }
        NSArray *methodSubstring = [call.method componentsSeparatedByString:@"#"];
        if (methodSubstring.count < 3) {
            result(FlutterMethodNotImplemented);
            [weakSelf _handleMethodCall:call success:NO];
            return;
        }
        NSString *callClassString = methodSubstring[0];
        NSString *callClass = [NSString stringWithFormat:@"#{projectPrefix}%@", [callClassString componentsSeparatedByString:@"."].lastObject];
        NSString *callMethod = methodSubstring[1];
        NSArray *arguments = [methodSubstring[2] componentsSeparatedByString:@","];
        // assemble method name with arguments name
        for (NSString *argument in arguments) {
            if ([argument isEqualToString:arguments.firstObject]) {
                callMethod = [callMethod stringByAppendingString:@":"];
            } else {
                callMethod = [callMethod stringByAppendingFormat:@"%@:", argument];
            }
        }
        if (callClass.length > 0) {
            id implementation = weakSelf.methodImplementations[callClass];
            if (implementation && [implementation respondsToSelector:NSSelectorFromString(callMethod)]) {
                // generate method invocation
                SEL selector = NSSelectorFromString(callMethod);
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[implementation methodSignatureForSelector:selector]];
                [invocation setSelector:selector];
                [invocation setTarget:implementation];
                [call.arguments enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    if ([obj isKindOfClass:[NSNumber class]]) {
                        NSInteger value = [obj integerValue];
                        [invocation setArgument:&value atIndex:idx + 2];
                    } else {
                        id arg = [weakSelf _convertObjectToNativeReadableIfNeeded:obj];
                        [invocation setArgument:&(arg) atIndex:idx + 2];
                        [invocation retainArguments];
                    }
                }];
                BOOL hasCallback = [arguments containsObject:@"callback"];
                if (hasCallback) {
                    void(^completion)(id _Nullable object) = ^(id _Nullable object) {
                        object = [weakSelf _convertClassToFlutterReadableIfNeeded:object];
                        result(object);
                    };
                    [invocation setArgument:&(completion) atIndex:arguments.count + 1];
                    [invocation retainArguments];
                }
                [invocation invoke];
                if (!hasCallback) {
                    result(@YES);
                    [weakSelf _handleMethodCall:call success:YES];
                }
            } else {
                result(FlutterMethodNotImplemented);
                [weakSelf _handleMethodCall:call success:NO];
            }
        }

    }];
}

- (void)addMethodImplementation:(id)implementation withName:(NSString *)name
{
    self.methodImplementations[name] = implementation;
}

- (void)invokeMethod:(NSString *)method args:(nullable NSArray *)args fromClass:(Class)classType completion:(nullable void(^)(__nullable id result))completion
{
    NSString *methodString = self.channelName;
    NSString *protocolName = [NSStringFromClass(classType) stringByReplacingOccurrencesOfString:@"Imp" withString:@""];
    protocolName = [protocolName stringByReplacingOccurrencesOfString:@"#{projectPrefix}" withString:@""];
    methodString = [methodString stringByAppendingFormat:@".%@", protocolName];
    methodString = [methodString stringByAppendingFormat:@"#%@", method];
    [self.methodChannel invokeMethod:methodString arguments:args result:^(id  _Nullable result) {
        if (completion) {
            completion(result);
        }
    }];
}


#pragma mark - Private Methods

/// Convert custom class into flutter readable class.
/// Custom class can not pass through method channel without messages codec,
/// so we convert custom class into string, then dart side will convert it back to class,
/// this custom class transformation will later be replaced by messages codec.
/// @param object The object needs to convert
- (id)_convertClassToFlutterReadableIfNeeded:(nullable id)object
{
    if (!object) {
        return [NSNull null];
    }
    if ([object isKindOfClass:[NSArray class]]) {
        NSMutableArray *array = [NSMutableArray array];
        for (id value in (NSArray *)object) {
            [array addObject:[self _convertClassToFlutterReadableIfNeeded:value]];
        }
        return array;
    } else if ([object isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [((NSDictionary *)object) enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            dict[[self _convertClassToFlutterReadableIfNeeded:key]] = [self _convertClassToFlutterReadableIfNeeded:obj];
        }];
        return dict;
    } else if ([NSStringFromClass([object class]) hasPrefix:@"#{projectPrefix}"]) {
        // custom class
        NSString *className = [NSStringFromClass([object class]) stringByReplacingOccurrencesOfString:@"#{projectPrefix}" withString:@""];
        NSString *properties = ((NSObject *)object).mj_JSONString;
        return [NSString stringWithFormat:@"%@___custom___%@", className, properties];
    } else if ([object isKindOfClass:[NSData class]]) {
        return [FlutterStandardTypedData typedDataWithBytes:(NSData *)object];
    }
    return object;
}

/// Convert a object into native readable class
/// custom class can not pass through method channel without messages codec,
/// so dart side convert custom class into string, then native side will convert it back to class,
/// this custom class transformation will later be replaced by messages codec.
/// @param object The object needs to convert
- (nullable id)_convertObjectToNativeReadableIfNeeded:(id)object
{
    id arg = object;
    if ([object isKindOfClass:[NSString class]]) {
        NSString *string = (NSString *)object;
        // custom class in flutter will convert into string like: '___custom___"className"{"properties"}'
        if ([string containsString:@"___custom___"]) {
            NSString *className = [NSString stringWithFormat:@"#{projectPrefix}%@", [string substringToIndex:[string rangeOfString:@"___custom___"].location]];
            NSInteger propertiesBegin = [string rangeOfString:@"{"].location;
            Class customClass = NSClassFromString(className);
            arg = [customClass new];
            if (propertiesBegin != NSNotFound) {
                // get properties
                NSString *properties = [string substringWithRange:NSMakeRange(propertiesBegin, string.length - propertiesBegin)];
                NSData *data = [properties dataUsingEncoding:NSUTF8StringEncoding];
                NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                for (NSString *key in json.allKeys) {
                    [arg setValue:json[key] forKey:key];    // perform setter method
                }
            }
        }
    } else if ([object isKindOfClass:[NSArray class]]) {
        NSMutableArray *array = [NSMutableArray array];
        for (id value in (NSArray *)object) {
            [array addObject:[self _convertObjectToNativeReadableIfNeeded:value]];
        }
        return array;
    } else if ([object isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [(NSDictionary *)object enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            dict[[self _convertObjectToNativeReadableIfNeeded:key]] = [self _convertObjectToNativeReadableIfNeeded:obj];
        }];
        return dict;
    } else if ([object isKindOfClass:[FlutterStandardTypedData class]]) {
        return [(FlutterStandardTypedData *)object data];
    } else if ([object isKindOfClass:[NSNull class]]) {
        return nil;
    }
    return arg;
}

- (void)_handleMethodCall:(FlutterMethodCall *)methodCall success:(BOOL)success
{
    if ([self.delegate respondsToSelector:@selector(manager:didHandleMethodCall:success:)]) {
        [self.delegate manager:self didHandleMethodCall:methodCall success:success];
    }
}

@end

NS_ASSUME_NONNULL_END
''';