1 /** 2 A protocol that helps in identifying whether the instance of `ViewModuleWrapper` is of a dynamically created class. 3 */ 4 @objc 5 protocol DynamicModuleWrapperProtocol { 6 @objc 7 optional func wrappedModule() -> ViewModuleWrapper 8 } 9 10 /** 11 Each module that has a view manager definition needs to be wrapped by `RCTViewManager`. 12 Unfortunately, we can't use just one class because React Native checks for duplicated classes. 13 We're generating its subclasses in runtime as a workaround. 14 */ 15 @objc 16 public class ViewModuleWrapper: RCTViewManager, DynamicModuleWrapperProtocol { 17 /** 18 A reference to the module holder that stores the module definition. 19 Enforced unwrapping is required since it can be set right after the object is initialized. 20 */ 21 var wrappedModuleHolder: ModuleHolder! 22 23 /** 24 The designated initializer. At first, we use this base class to hide `ModuleHolder` from Objective-C runtime. 25 */ 26 public init(_ wrappedModuleHolder: ModuleHolder) { 27 self.wrappedModuleHolder = wrappedModuleHolder 28 } 29 30 /** 31 The designated initializer that is used by React Native to create module instances. 32 Must be called on a dynamic class to get access to underlying wrapped module. Throws fatal exception otherwise. 33 */ 34 @objc 35 public override init() { 36 super.init() 37 guard let module = (self as DynamicModuleWrapperProtocol).wrappedModule?() else { 38 fatalError("Something unexpected has happened. Only dynamically created `ViewModuleWrapper` can be initialized without params.") 39 } 40 self.wrappedModuleHolder = module.wrappedModuleHolder 41 } 42 43 /** 44 Dummy initializer, for use only in `EXModuleRegistryAdapter.extraModulesForModuleRegistry:`. 45 */ 46 @objc 47 public init(dummy: Any?) { 48 super.init() 49 } 50 51 /** 52 Returns the original name of the wrapped module. 53 */ 54 @objc 55 public func name() -> String { 56 return wrappedModuleHolder.name 57 } 58 59 /** 60 Static function that returns the class name, but keep in mind that dynamic wrappers 61 have custom class name (see `objc_allocateClassPair` invocation in `createViewModuleWrapperClass`). 62 */ 63 @objc 64 public override class func moduleName() -> String { 65 return NSStringFromClass(Self.self) 66 } 67 68 /** 69 The view manager wrapper doesn't require main queue setup — it doesn't call any UI-related stuff on `init`. 70 Also, lazy-loaded modules must return false here. 71 */ 72 @objc 73 public override class func requiresMainQueueSetup() -> Bool { 74 return false 75 } 76 77 /** 78 Creates a view from the wrapped module. 79 */ 80 @objc 81 public override func view() -> UIView! { 82 guard let view = wrappedModuleHolder.definition.viewManager?.createView() else { 83 fatalError("Module `\(wrappedModuleHolder.name)` doesn't define the view manager nor view factory.") 84 } 85 return view 86 } 87 88 /** 89 The config for `proxiedProperties` prop. In Objective-C style, this function is generated by `RCT_CUSTOM_VIEW_PROPERTY` macro. 90 */ 91 @objc 92 public class func propConfig_proxiedProperties() -> [String] { 93 return ["NSDictionary", "__custom__"]; 94 } 95 96 /** 97 The setter for `proxiedProperties` prop. In Objective-C style, this function is generated by `RCT_CUSTOM_VIEW_PROPERTY` macro. 98 */ 99 @objc 100 public func set_proxiedProperties(_ json: Any?, forView view: UIView, withDefaultView defaultView: UIView) { 101 guard let json = json as? [String: Any?], 102 let props = wrappedModuleHolder.definition.viewManager?.propsDict() else { 103 return 104 } 105 for (key, value) in json { 106 if let prop = props[key] { 107 prop.set(value: value, onView: view) 108 } 109 } 110 } 111 112 /** 113 Creates a subclass of `ViewModuleWrapper` in runtime. The new class overrides `moduleName` stub. 114 */ 115 @objc 116 public static func createViewModuleWrapperClass(module: ViewModuleWrapper) -> ViewModuleWrapper.Type? { 117 // We're namespacing the view name so we know it uses our architecture. 118 let prefixedViewName = "ViewManagerAdapter_\(module.name())" 119 120 return prefixedViewName.withCString { viewNamePtr in 121 // Create a new meta class that inherits from `ViewModuleWrapper`. The class name passed here, doesn't work for Swift classes, 122 // so we also have to override `moduleName` class method. 123 let wrapperClass: AnyClass? = objc_allocateClassPair(ViewModuleWrapper.self, viewNamePtr, 0) 124 125 // Dynamically add instance method returning wrapped module to the dynamic wrapper class. 126 // React Native initializes modules with `init` without params, 127 // so there is no other way to pass it to the instances. 128 let wrappedModuleBlock: @convention(block) () -> ViewModuleWrapper = { module } 129 let wrappedModuleImp: IMP = imp_implementationWithBlock(wrappedModuleBlock) 130 class_addMethod(wrapperClass, Selector("wrappedModule"), wrappedModuleImp, "@@:") 131 132 return wrapperClass as? ViewModuleWrapper.Type 133 } 134 } 135 } 136