RCTComponentData.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTComponentData.h"
  8. #import <objc/message.h>
  9. #import "RCTBridge.h"
  10. #import "RCTBridgeModule.h"
  11. #import "RCTComponentEvent.h"
  12. #import "RCTConvert.h"
  13. #import "RCTParserUtils.h"
  14. #import "RCTShadowView.h"
  15. #import "RCTUtils.h"
  16. #import "UIView+React.h"
  17. typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
  18. typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
  19. typedef void (^InterceptorBlock)(NSString *eventName, NSDictionary *event, id sender);
  20. /**
  21. * Get the converter function for the specified type
  22. */
  23. static SEL selectorForType(NSString *type)
  24. {
  25. const char *input = type.UTF8String;
  26. return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
  27. }
  28. @implementation RCTComponentData {
  29. id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
  30. RCTPropBlockDictionary *_viewPropBlocks;
  31. RCTPropBlockDictionary *_shadowPropBlocks;
  32. __weak RCTBridge *_bridge;
  33. }
  34. @synthesize manager = _manager;
  35. - (instancetype)initWithManagerClass:(Class)managerClass bridge:(RCTBridge *)bridge
  36. {
  37. if ((self = [super init])) {
  38. _bridge = bridge;
  39. _managerClass = managerClass;
  40. _viewPropBlocks = [NSMutableDictionary new];
  41. _shadowPropBlocks = [NSMutableDictionary new];
  42. _name = moduleNameForClass(managerClass);
  43. }
  44. return self;
  45. }
  46. - (RCTViewManager *)manager
  47. {
  48. if (!_manager) {
  49. _manager = [_bridge moduleForClass:_managerClass];
  50. }
  51. return _manager;
  52. }
  53. RCT_NOT_IMPLEMENTED(-(instancetype)init)
  54. - (UIView *)createViewWithTag:(NSNumber *)tag rootTag:(NSNumber *)rootTag
  55. {
  56. RCTAssertMainQueue();
  57. UIView *view = [self.manager view];
  58. view.reactTag = tag;
  59. view.rootTag = rootTag;
  60. #if !TARGET_OS_TV
  61. view.multipleTouchEnabled = YES;
  62. #endif
  63. view.userInteractionEnabled = YES; // required for touch handling
  64. view.layer.allowsGroupOpacity = YES; // required for touch handling
  65. return view;
  66. }
  67. - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
  68. {
  69. RCTShadowView *shadowView = [self.manager shadowView];
  70. shadowView.reactTag = tag;
  71. shadowView.viewName = _name;
  72. return shadowView;
  73. }
  74. - (void)callCustomSetter:(SEL)setter onView:(id<RCTComponent>)view withProp:(id)json isShadowView:(BOOL)isShadowView
  75. {
  76. json = RCTNilIfNull(json);
  77. if (!isShadowView) {
  78. if (!json && !_defaultView) {
  79. // Only create default view if json is null
  80. _defaultView = [self createViewWithTag:nil rootTag:nil];
  81. }
  82. ((void (*)(id, SEL, id, id, id))objc_msgSend)(self.manager, setter, json, view, _defaultView);
  83. } else {
  84. ((void (*)(id, SEL, id, id))objc_msgSend)(self.manager, setter, json, view);
  85. }
  86. }
  87. static RCTPropBlock
  88. createEventSetter(NSString *propName, SEL setter, InterceptorBlock eventInterceptor, RCTBridge *bridge)
  89. {
  90. __weak RCTBridge *weakBridge = bridge;
  91. return ^(id target, id json) {
  92. void (^eventHandler)(NSDictionary *event) = nil;
  93. if ([RCTConvert BOOL:json]) {
  94. __weak id<RCTComponent> weakTarget = target;
  95. eventHandler = ^(NSDictionary *event) {
  96. // The component no longer exists, we shouldn't send the event
  97. id<RCTComponent> strongTarget = weakTarget;
  98. if (!strongTarget) {
  99. return;
  100. }
  101. if (eventInterceptor) {
  102. eventInterceptor(propName, event, strongTarget.reactTag);
  103. } else {
  104. RCTComponentEvent *componentEvent = [[RCTComponentEvent alloc] initWithName:propName
  105. viewTag:strongTarget.reactTag
  106. body:event];
  107. [weakBridge.eventDispatcher sendEvent:componentEvent];
  108. }
  109. };
  110. }
  111. ((void (*)(id, SEL, id))objc_msgSend)(target, setter, eventHandler);
  112. };
  113. }
  114. static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, SEL type, SEL getter, SEL setter)
  115. {
  116. NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
  117. typeInvocation.selector = type;
  118. typeInvocation.target = [RCTConvert class];
  119. __block NSInvocation *targetInvocation = nil;
  120. __block NSMutableData *defaultValue = nil;
  121. return ^(id target, id json) {
  122. if (!target) {
  123. return;
  124. }
  125. // Get default value
  126. if (!defaultValue) {
  127. if (!json) {
  128. // We only set the defaultValue when we first pass a non-null
  129. // value, so if the first value sent for a prop is null, it's
  130. // a no-op (we'd be resetting it to its default when its
  131. // value is already the default).
  132. return;
  133. }
  134. // Use NSMutableData to store defaultValue instead of malloc, so
  135. // it will be freed automatically when setterBlock is released.
  136. defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
  137. if ([target respondsToSelector:getter]) {
  138. NSMethodSignature *signature = [target methodSignatureForSelector:getter];
  139. NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
  140. sourceInvocation.selector = getter;
  141. [sourceInvocation invokeWithTarget:target];
  142. [sourceInvocation getReturnValue:defaultValue.mutableBytes];
  143. }
  144. }
  145. // Get value
  146. BOOL freeValueOnCompletion = NO;
  147. void *value = defaultValue.mutableBytes;
  148. if (json) {
  149. freeValueOnCompletion = YES;
  150. value = malloc(typeSignature.methodReturnLength);
  151. if (!value) {
  152. // CWE - 391 : Unchecked error condition
  153. // https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
  154. // https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
  155. abort();
  156. }
  157. [typeInvocation setArgument:&json atIndex:2];
  158. [typeInvocation invoke];
  159. [typeInvocation getReturnValue:value];
  160. }
  161. // Set value
  162. if (!targetInvocation) {
  163. NSMethodSignature *signature = [target methodSignatureForSelector:setter];
  164. targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
  165. targetInvocation.selector = setter;
  166. }
  167. [targetInvocation setArgument:value atIndex:2];
  168. [targetInvocation invokeWithTarget:target];
  169. if (freeValueOnCompletion) {
  170. // Only free the value if we `malloc`d it locally, otherwise it
  171. // points to `defaultValue.mutableBytes`, which is managed by ARC.
  172. free(value);
  173. }
  174. };
  175. }
  176. - (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView
  177. {
  178. // Get type
  179. SEL type = NULL;
  180. NSString *keyPath = nil;
  181. SEL selector =
  182. NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", isShadowView ? @"Shadow" : @"", name]);
  183. if ([_managerClass respondsToSelector:selector]) {
  184. NSArray<NSString *> *typeAndKeyPath = ((NSArray<NSString *> * (*)(id, SEL)) objc_msgSend)(_managerClass, selector);
  185. type = selectorForType(typeAndKeyPath[0]);
  186. keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
  187. } else {
  188. return ^(__unused id view, __unused id json) {
  189. };
  190. }
  191. // Check for custom setter
  192. if ([keyPath isEqualToString:@"__custom__"]) {
  193. // Get custom setter. There is no default view in the shadow case, so the selector is different.
  194. NSString *selectorString;
  195. if (!isShadowView) {
  196. selectorString =
  197. [NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, isShadowView ? @"Shadow" : @""];
  198. } else {
  199. selectorString = [NSString stringWithFormat:@"set_%@:forShadowView:", name];
  200. }
  201. SEL customSetter = NSSelectorFromString(selectorString);
  202. __weak RCTComponentData *weakSelf = self;
  203. return ^(id<RCTComponent> view, id json) {
  204. [weakSelf callCustomSetter:customSetter onView:view withProp:json isShadowView:isShadowView];
  205. };
  206. } else {
  207. // Disect keypath
  208. NSString *key = name;
  209. NSArray<NSString *> *parts = [keyPath componentsSeparatedByString:@"."];
  210. if (parts) {
  211. key = parts.lastObject;
  212. parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}];
  213. }
  214. // Get property getter
  215. SEL getter = NSSelectorFromString(key);
  216. // Get property setter
  217. SEL setter = NSSelectorFromString(
  218. [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]]);
  219. // Build setter block
  220. void (^setterBlock)(id target, id json) = nil;
  221. if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
  222. type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
  223. // Special case for event handlers
  224. setterBlock = createEventSetter(name, setter, self.eventInterceptor, _bridge);
  225. } else {
  226. // Ordinary property handlers
  227. NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
  228. if (!typeSignature) {
  229. RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
  230. return ^(__unused id<RCTComponent> view, __unused id json) {
  231. };
  232. }
  233. switch (typeSignature.methodReturnType[0]) {
  234. #define RCT_CASE(_value, _type) \
  235. case _value: { \
  236. __block BOOL setDefaultValue = NO; \
  237. __block _type defaultValue; \
  238. _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
  239. _type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
  240. void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
  241. setterBlock = ^(id target, id json) { \
  242. if (json) { \
  243. if (!setDefaultValue && target) { \
  244. if ([target respondsToSelector:getter]) { \
  245. defaultValue = get(target, getter); \
  246. } \
  247. setDefaultValue = YES; \
  248. } \
  249. set(target, setter, convert([RCTConvert class], type, json)); \
  250. } else if (setDefaultValue) { \
  251. set(target, setter, defaultValue); \
  252. } \
  253. }; \
  254. break; \
  255. }
  256. RCT_CASE(_C_SEL, SEL)
  257. RCT_CASE(_C_CHARPTR, const char *)
  258. RCT_CASE(_C_CHR, char)
  259. RCT_CASE(_C_UCHR, unsigned char)
  260. RCT_CASE(_C_SHT, short)
  261. RCT_CASE(_C_USHT, unsigned short)
  262. RCT_CASE(_C_INT, int)
  263. RCT_CASE(_C_UINT, unsigned int)
  264. RCT_CASE(_C_LNG, long)
  265. RCT_CASE(_C_ULNG, unsigned long)
  266. RCT_CASE(_C_LNG_LNG, long long)
  267. RCT_CASE(_C_ULNG_LNG, unsigned long long)
  268. RCT_CASE(_C_FLT, float)
  269. RCT_CASE(_C_DBL, double)
  270. RCT_CASE(_C_BOOL, BOOL)
  271. RCT_CASE(_C_PTR, void *)
  272. RCT_CASE(_C_ID, id)
  273. case _C_STRUCT_B:
  274. default: {
  275. setterBlock = createNSInvocationSetter(typeSignature, type, getter, setter);
  276. break;
  277. }
  278. }
  279. }
  280. return ^(__unused id view, __unused id json) {
  281. // Follow keypath
  282. id target = view;
  283. for (NSString *part in parts) {
  284. target = [target valueForKey:part];
  285. }
  286. // Set property with json
  287. setterBlock(target, RCTNilIfNull(json));
  288. };
  289. }
  290. }
  291. - (RCTPropBlock)propBlockForKey:(NSString *)name isShadowView:(BOOL)isShadowView
  292. {
  293. RCTPropBlockDictionary *propBlocks = isShadowView ? _shadowPropBlocks : _viewPropBlocks;
  294. RCTPropBlock propBlock = propBlocks[name];
  295. if (!propBlock) {
  296. propBlock = [self createPropBlock:name isShadowView:isShadowView];
  297. #if RCT_DEBUG
  298. // Provide more useful log feedback if there's an error
  299. RCTPropBlock unwrappedBlock = propBlock;
  300. __weak __typeof(self) weakSelf = self;
  301. propBlock = ^(id<RCTComponent> view, id json) {
  302. NSString *logPrefix = [NSString
  303. stringWithFormat:@"Error setting property '%@' of %@ with tag #%@: ", name, weakSelf.name, view.reactTag];
  304. RCTPerformBlockWithLogPrefix(
  305. ^{
  306. unwrappedBlock(view, json);
  307. },
  308. logPrefix);
  309. };
  310. #endif
  311. propBlocks[name] = [propBlock copy];
  312. }
  313. return propBlock;
  314. }
  315. - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>)view
  316. {
  317. if (!view) {
  318. return;
  319. }
  320. [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
  321. [self propBlockForKey:key isShadowView:NO](view, json);
  322. }];
  323. }
  324. - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
  325. {
  326. if (!shadowView) {
  327. return;
  328. }
  329. [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
  330. [self propBlockForKey:key isShadowView:YES](shadowView, json);
  331. }];
  332. }
  333. - (NSDictionary<NSString *, id> *)viewConfig
  334. {
  335. NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
  336. NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
  337. #pragma clang diagnostic push
  338. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  339. if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) {
  340. NSArray<NSString *> *events = [self.manager customBubblingEventTypes];
  341. for (NSString *event in events) {
  342. [bubblingEvents addObject:RCTNormalizeInputEventName(event)];
  343. }
  344. }
  345. #pragma clang diagnostic pop
  346. unsigned int count = 0;
  347. NSMutableDictionary *propTypes = [NSMutableDictionary new];
  348. Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
  349. for (unsigned int i = 0; i < count; i++) {
  350. SEL selector = method_getName(methods[i]);
  351. const char *selectorName = sel_getName(selector);
  352. if (strncmp(selectorName, "propConfig", strlen("propConfig")) != 0) {
  353. continue;
  354. }
  355. // We need to handle both propConfig_* and propConfigShadow_* methods
  356. const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_');
  357. if (!underscorePos) {
  358. continue;
  359. }
  360. NSString *name = @(underscorePos + 1);
  361. NSString *type = ((NSArray<NSString *> * (*)(id, SEL)) objc_msgSend)(_managerClass, selector)[0];
  362. if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) {
  363. RCTLogError(
  364. @"Property '%@' of component '%@' redefined from '%@' "
  365. "to '%@'",
  366. name,
  367. _name,
  368. propTypes[name],
  369. type);
  370. }
  371. if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
  372. [bubblingEvents addObject:RCTNormalizeInputEventName(name)];
  373. propTypes[name] = @"BOOL";
  374. } else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
  375. [directEvents addObject:RCTNormalizeInputEventName(name)];
  376. propTypes[name] = @"BOOL";
  377. } else {
  378. propTypes[name] = type;
  379. }
  380. }
  381. free(methods);
  382. #if RCT_DEBUG
  383. for (NSString *event in bubblingEvents) {
  384. if ([directEvents containsObject:event]) {
  385. RCTLogError(
  386. @"Component '%@' registered '%@' as both a bubbling event "
  387. "and a direct event",
  388. _name,
  389. event);
  390. }
  391. }
  392. #endif
  393. Class superClass = [_managerClass superclass];
  394. return @{
  395. @"propTypes" : propTypes,
  396. @"directEvents" : directEvents,
  397. @"bubblingEvents" : bubblingEvents,
  398. @"baseModuleName" : superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass),
  399. };
  400. }
  401. static NSString *moduleNameForClass(Class managerClass)
  402. {
  403. // Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
  404. // We want to get rid of RCT and RK prefixes, but a lot of JS code still references
  405. // view names by prefix. So, while RCTBridgeModuleNameForClass now drops these
  406. // prefixes by default, we'll still keep them around here.
  407. NSString *name = [managerClass moduleName];
  408. if (name.length == 0) {
  409. name = NSStringFromClass(managerClass);
  410. }
  411. if ([name hasPrefix:@"RK"]) {
  412. name = [name stringByReplacingCharactersInRange:(NSRange){0, @"RK".length} withString:@"RCT"];
  413. }
  414. if ([name hasSuffix:@"Manager"]) {
  415. name = [name substringToIndex:name.length - @"Manager".length];
  416. }
  417. RCTAssert(name.length, @"Invalid moduleName '%@'", name);
  418. return name;
  419. }
  420. @end