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