Skip to content

Commit

Permalink
refactor(ios): unify component and module method calling mechanism
Browse files Browse the repository at this point in the history
Fixed an issue where UI components does not support calling non-ID parameter type methods in hippy3
  • Loading branch information
wwwcg committed Mar 27, 2024
1 parent b38e990 commit 54f8144
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 277 deletions.
4 changes: 4 additions & 0 deletions framework/ios/base/bridge/HippyBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
- (id)moduleForName:(NSString *)moduleName;
- (id)moduleForClass:(Class)moduleClass;

/// Get ModuleData by name
/// - Parameter moduleName: JS name of module
- (nullable HippyModuleData *)moduleDataForName:(NSString *)moduleName;

/**
* Convenience method for retrieving all modules conforming to a given protocol.
* Modules will be sychronously instantiated if they haven't already been,
Expand Down
7 changes: 7 additions & 0 deletions framework/ios/base/bridge/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ - (id)moduleForClass:(Class)moduleClass {
return [_moduleSetup moduleForClass:moduleClass];
}

- (HippyModuleData *)moduleDataForName:(NSString *)moduleName {
if (moduleName) {
return _moduleSetup.moduleDataByName[moduleName];
}
return nil;
}

- (void)addImageProviderClass:(Class<HippyImageProviderProtocol>)cls {
HippyAssertParam(cls);
@synchronized (self) {
Expand Down
6 changes: 4 additions & 2 deletions framework/ios/base/modules/HippyModuleData.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@
* Returns the module methods. Note that this will gather the methods the first
* time it is called and then memoize the results.
*/
@property (nonatomic, copy, readonly) NSArray<id<HippyBridgeMethod>> *methods;
@property (nonatomic, readonly) NSArray<id<HippyBridgeMethod>> *methods;

@property (nonatomic, copy, readonly) NSDictionary<NSString *, id<HippyBridgeMethod>> *methodsByName;
/// Returns the module methods by name. Note that this will gather the methods the first
/// time it is called and then memoize the results.
@property (nonatomic, readonly) NSDictionary<NSString *, id<HippyBridgeMethod>> *methodsByName;

/**
* Returns YES if module instance has already been initialized; NO otherwise.
Expand Down
71 changes: 44 additions & 27 deletions framework/ios/base/modules/HippyModuleData.mm
Original file line number Diff line number Diff line change
Expand Up @@ -221,41 +221,58 @@ - (NSString *)name {
return HippyBridgeModuleNameForClass(_moduleClass);
}

- (NSArray<id<HippyBridgeMethod>> *)methods {
if (!_methods) {
NSMutableArray<id<HippyBridgeMethod>> *moduleMethods = [NSMutableArray new];

if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
[moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
- (void)collectAllModuleMethods {
NSMutableArray<id<HippyBridgeMethod>> *moduleMethods = [NSMutableArray new];
NSMutableDictionary<NSString *, id<HippyBridgeMethod>> *moduleMethodsByName = [NSMutableDictionary new];

if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
NSArray<id<HippyBridgeMethod>> *exportMethods = [self.instance methodsToExport];
[moduleMethods addObjectsFromArray:exportMethods];
for (id<HippyBridgeMethod> method in exportMethods) {
moduleMethodsByName[method.JSMethodName] = method;
}

unsigned int methodCount;
Class cls = _moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);

for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__hippy_export__"]) {
IMP imp = method_getImplementation(method);
NSArray<NSString *> *entries = ((NSArray<NSString *> * (*)(id, SEL)) imp)(_moduleClass, selector);
id<HippyBridgeMethod> moduleMethod = [[HippyModuleMethod alloc] initWithMethodSignature:entries[1] JSMethodName:entries[0]
moduleClass:_moduleClass];

[moduleMethods addObject:moduleMethod];
}
}

unsigned int methodCount;
Class cls = _moduleClass;
while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);

for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__hippy_export__"]) {
IMP imp = method_getImplementation(method);
NSArray<NSString *> *entries = ((NSArray<NSString *> * (*)(id, SEL)) imp)(_moduleClass, selector);
id<HippyBridgeMethod> moduleMethod = [[HippyModuleMethod alloc] initWithMethodSignature:entries[1] JSMethodName:entries[0]
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
moduleMethodsByName[moduleMethod.JSMethodName] = moduleMethod;
}

free(methods);
cls = class_getSuperclass(cls);
}

free(methods);
cls = class_getSuperclass(cls);
}
_methods = moduleMethods;
_methodsByName = moduleMethodsByName;
}

_methods = [moduleMethods copy];
- (NSArray<id<HippyBridgeMethod>> *)methods {
if (!_methods) {
[self collectAllModuleMethods];
}
return _methods;
}

- (NSDictionary<NSString *,id<HippyBridgeMethod>> *)methodsByName {
if (!_methodsByName) {
[self collectAllModuleMethods];
}
return _methodsByName;
}


- (void)gatherConstants {
if (_hasConstantsToExport && !_constantsToExport) {
(void)[self instance];
Expand Down
192 changes: 79 additions & 113 deletions framework/ios/base/modules/HippyModuleMethod.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ @implementation HippyModuleMethod {

@synthesize arguments = _arguments;

static void HippyLogArgumentError(
__unused HippyModuleMethod *method, __unused NSUInteger index, __unused id valueOrType, __unused const char *issue) {
HippyLogError(nil,
@"Argument %tu (%@) of %@.%@ %s", index, valueOrType, HippyBridgeModuleNameForClass(method->_moduleClass), method.JSMethodName, issue);
static void HippyLogArgumentError(HippyModuleMethod *method, NSUInteger index, id valueOrType, const char *issue) {
HippyLogError(@"Argument %tu (%@) of %@.%@ %s",
index, valueOrType, HippyBridgeModuleNameForClass(method->_moduleClass), method.JSMethodName, issue);
}

// returns YES if the selector ends in a colon (indicating that there is at
Expand Down Expand Up @@ -162,6 +161,23 @@ - (instancetype)initWithMethodSignature:(NSString *)methodSignature JSMethodName
return self;
}

static void enqueueBlockCallback(HippyBridge *bridge, HippyModuleMethod *moduleMethod, NSNumber *json, NSArray *args) {
if (!bridge || !moduleMethod) {
return;
}
BOOL shouldEnqueueCallback = YES;
if ([[bridge methodInterceptor] respondsToSelector:@selector(shouldCallbackBeInvokedWithModuleName:methodName:callbackId:arguments:)]) {
NSString *moduleName = HippyBridgeModuleNameForClass(moduleMethod.moduleClass);
shouldEnqueueCallback = [[bridge methodInterceptor] shouldCallbackBeInvokedWithModuleName:moduleName
methodName:[moduleMethod JSMethodName]
callbackId:json
arguments:args];
}
if (shouldEnqueueCallback) {
[bridge enqueueCallback:json args:args];
}
}

- (void)processMethodSignature {
NSArray<HippyMethodArgument *> *arguments;
_selector = HippyParseMethodSignature(_methodSignature, &arguments);
Expand Down Expand Up @@ -193,39 +209,14 @@ - (void)processMethodSignature {
CFBridgingRetain(value)

__weak HippyModuleMethod *weakSelf = self;
void (^addBlockArgument)(void) = ^{
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(NSArray *args) {
__strong HippyBridge *strongBridge = weakBridge;
__strong HippyModuleMethod *strongSelf = weakSelf;
if (!strongBridge || !strongSelf) {
return;
}
BOOL shouldEnqueueCallback = YES;
if ([[strongBridge methodInterceptor] respondsToSelector:@selector(shouldCallbackBeInvokedWithModuleName:methodName:callbackId:arguments:)]) {
NSString *moduleName = HippyBridgeModuleNameForClass(strongSelf->_moduleClass);
shouldEnqueueCallback =
[[strongBridge methodInterceptor] shouldCallbackBeInvokedWithModuleName:moduleName
methodName:[strongSelf JSMethodName]
callbackId:json
arguments:args];
}
if (shouldEnqueueCallback) {
[strongBridge enqueueCallback:json args:args];
}
});)
};


for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *objcType = [methodSignature getArgumentTypeAtIndex:i];
BOOL isNullableType = NO;
HippyMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
SEL selector = HippyConvertSelectorForType(typeName);
static const char *blockType = @encode(__typeof(^ {}));
if ([HippyConvert respondsToSelector:selector]) {
switch (objcType[0]) {
#define HIPPY_CASE(_value, _type) \
Expand Down Expand Up @@ -287,101 +278,78 @@ - (void)processMethodSignature {
}

default: {
static const char *blockType = @encode(__typeof(^ {
}));
if (!strcmp(objcType, blockType)) {
addBlockArgument();
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(NSArray *args) {
enqueueBlockCallback(weakBridge, weakSelf, json, args);
});)
} else {
HippyLogError(nil, @"Unsupported argument type '%@' in method %@.", typeName, [self methodName]);
HippyLogError(@"Unsupported argument type '%@' in method %@.", typeName, [self methodName]);
}
}
}
} else if ([typeName isEqualToString:@"HippyResponseSenderBlock"]) {
addBlockArgument();
} else if ([typeName isEqualToString:@"HippyResponseErrorBlock"]) {
HIPPY_ARG_BLOCK(

if (HIPPY_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(NSError *error) {
__strong HippyBridge *strongBridge = weakBridge;
__strong HippyModuleMethod *strongSelf = weakSelf;
if (!strongBridge || !strongSelf) {
return;
}
NSArray *errorArgs = @[HippyJSErrorFromNSError(error)];
BOOL shouldEnqueueCallback = YES;
if ([[strongBridge methodInterceptor] respondsToSelector:@selector(shouldCallbackBeInvokedWithModuleName:methodName:callbackId:arguments:)]) {
NSString *moduleName = HippyBridgeModuleNameForClass(strongSelf->_moduleClass);
shouldEnqueueCallback =
[[strongBridge methodInterceptor] shouldCallbackBeInvokedWithModuleName:moduleName
methodName:[strongSelf JSMethodName]
callbackId:json
arguments:errorArgs];
}
if (shouldEnqueueCallback) {
[strongBridge enqueueCallback:json args:errorArgs];
}
});)
} else if ([typeName isEqualToString:@"HippyPromiseResolveBlock"]) {
HippyAssert(i == numberOfArguments - 2, @"The HippyPromiseResolveBlock must be the second to last parameter in -[%@ %@]", _moduleClass, _methodSignature);
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(id result) {
__strong HippyBridge *strongBridge = weakBridge;
__strong HippyModuleMethod *strongSelf = weakSelf;
if (!strongBridge || !strongSelf) {
return;
}
NSArray *args = result ? @[result] : @[];
BOOL shouldEnqueueCallback = YES;
if ([[strongBridge methodInterceptor] respondsToSelector:@selector(shouldCallbackBeInvokedWithModuleName:methodName:callbackId:arguments:)]) {
NSString *moduleName = HippyBridgeModuleNameForClass(strongSelf->_moduleClass);
shouldEnqueueCallback =
[[strongBridge methodInterceptor] shouldCallbackBeInvokedWithModuleName:moduleName
methodName:[strongSelf JSMethodName]
callbackId:json
arguments:args];
}
if (shouldEnqueueCallback) {
[strongBridge enqueueCallback:json args:args];
}
Hippy_BLOCK_ARGUMENT(^(NSArray *args) {
enqueueBlockCallback(weakBridge, weakSelf, json, args);
});)
} else if ([typeName isEqualToString:@"HippyPromiseRejectBlock"]) {
HippyAssert(
i == numberOfArguments - 1, @"The HippyPromiseRejectBlock must be the last parameter in -[%@ %@]", _moduleClass, _methodSignature);
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
} else if ([typeName isEqualToString:@"HippyResponseErrorBlock"]) {
HIPPY_ARG_BLOCK(if (HIPPY_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a function");
return NO;
}
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(NSString *code, NSString *message, NSError *error) {
__strong HippyBridge *strongBridge = weakBridge;
__strong HippyModuleMethod *strongSelf = weakSelf;
if (!strongBridge || !strongSelf) {
return;
Hippy_BLOCK_ARGUMENT(^(NSError *error) {
NSArray *errorArgs = @[HippyJSErrorFromNSError(error)];
enqueueBlockCallback(weakBridge, weakSelf, json, errorArgs);
});)
} else if ([typeName isEqualToString:@"HippyPromiseResolveBlock"]) {
if (i != numberOfArguments - 2) {
HippyLogWarn(@"HippyPromiseResolveBlock should be the second to last parameter in -[%@ %@]", _moduleClass, _methodSignature);
}
HIPPY_ARG_BLOCK(
if (!json) {
HippyLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
return NO;
}
NSDictionary *errorJSON = HippyJSErrorFromCodeMessageAndNSError(code, message, error);
NSArray *args = @[errorJSON];
BOOL shouldEnqueueCallback = YES;
if ([[strongBridge methodInterceptor] respondsToSelector:@selector(shouldCallbackBeInvokedWithModuleName:methodName:callbackId:arguments:)]) {
NSString *moduleName = HippyBridgeModuleNameForClass(strongSelf->_moduleClass);
shouldEnqueueCallback =
[[strongBridge methodInterceptor] shouldCallbackBeInvokedWithModuleName:moduleName
methodName:[strongSelf JSMethodName]
callbackId:json
arguments:args];
id blockArg = nil;
if (![json isKindOfClass:[NSNumber class]]) {
// In Hippy3.0, Dom Nodes call function by method name directly,
// so it is not a Number anymore.
// See NativeRenderManager::CallFunction() for more.
blockArg = json;
} else {
__weak HippyBridge *weakBridge = bridge;
blockArg = ^(id result){
NSArray *args = result ? @[result] : @[];
enqueueBlockCallback(weakBridge, weakSelf, json, args);
};
}
if (shouldEnqueueCallback) {
[strongBridge enqueueCallback:json args:args];
Hippy_BLOCK_ARGUMENT(blockArg);
)
} else if ([typeName isEqualToString:@"HippyPromiseRejectBlock"]) {
HippyAssert(i == numberOfArguments - 1,
@"HippyPromiseRejectBlock must be the last parameter in -[%@ %@]", _moduleClass, _methodSignature);
HIPPY_ARG_BLOCK(
if (HIPPY_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
HippyLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
return NO;
}
});)
__weak HippyBridge *weakBridge = bridge;
Hippy_BLOCK_ARGUMENT(^(NSString *code, NSString *message, NSError *error) {
NSDictionary *errorJSON = HippyJSErrorFromCodeMessageAndNSError(code, message, error);
NSArray *args = @[errorJSON];
enqueueBlockCallback(weakBridge, weakSelf, json, args);
});
)
} else if ([HippyTurboModuleManager isTurboModule:typeName]) {
[argumentBlocks addObject:^(__unused HippyBridge * bridge, NSUInteger index, id json) {
[invocation setArgument:&json atIndex:(index) + 2];
Expand All @@ -390,9 +358,7 @@ - (void)processMethodSignature {
}];
} else {
// Unknown argument type
HippyLogError(nil, @"Unknown argument type '%@' in method %@. Extend HippyConvert"
" to support this type.",
typeName, [self methodName]);
HippyLogError(@"Unknown argument type '%@' in method %@. Extend HippyConvert to support this type.", typeName, [self methodName]);
}

if (HIPPY_DEBUG) {
Expand Down
Loading

0 comments on commit 54f8144

Please sign in to comment.