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