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
10   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    Must be called on a dynamic class to get access to underlying wrapped module. Throws fatal exception otherwise.
36    */
37   @objc
38   public override init() {
39     super.init()
40     guard let module = (self as DynamicModuleWrapperProtocol).wrappedModule?() else {
41       fatalError("Something unexpected has happened. Only dynamically created `ViewModuleWrapper` can be initialized without params.")
42     }
43     self.wrappedModuleHolder = module.wrappedModuleHolder
44   }
45 
46   /**
47    Dummy initializer, for use only in `EXModuleRegistryAdapter.extraModulesForModuleRegistry:`.
48    */
49   @objc
50   public init(dummy: Any?) {
51     super.init()
52   }
53 
54   /**
55    Returns the original name of the wrapped module.
56    */
57   @objc
58   public func name() -> String {
59     return wrappedModuleHolder.name
60   }
61 
62   /**
63    Static function that returns the class name, but keep in mind that dynamic wrappers
64    have custom class name (see `objc_allocateClassPair` invocation in `createViewModuleWrapperClass`).
65    */
66   @objc
67   public override class func moduleName() -> String {
68     return NSStringFromClass(Self.self)
69   }
70 
71   /**
72    The view manager wrapper doesn't require main queue setup — it doesn't call any UI-related stuff on `init`.
73    Also, lazy-loaded modules must return false here.
74    */
75   @objc
76   public override class func requiresMainQueueSetup() -> Bool {
77     return false
78   }
79 
80   /**
81    Creates a view from the wrapped module.
82    */
83   @objc
84   public override func view() -> UIView! {
85     guard let appContext = wrappedModuleHolder.appContext else {
86       fatalError(Exceptions.AppContextLost().reason)
87     }
88     guard let view = wrappedModuleHolder.definition.viewManager?.createView(appContext: appContext) else {
89       fatalError("Cannot create a view from module '\(self.name)'")
90     }
91     return view
92   }
93 
94   /**
95    The config for `proxiedProperties` prop. In Objective-C style, this function is generated by `RCT_CUSTOM_VIEW_PROPERTY` macro.
96    */
97   @objc
98   public class func propConfig_proxiedProperties() -> [String] {
99     return ["NSDictionary", "__custom__"]
100   }
101 
102   /**
103    The setter for `proxiedProperties` prop. In Objective-C style, this function is generated by `RCT_CUSTOM_VIEW_PROPERTY` macro.
104    */
105   @objc
106   public func set_proxiedProperties(_ json: Any, forView view: UIView, withDefaultView defaultView: UIView) {
107     guard let json = json as? [String: Any],
108           let viewManager = wrappedModuleHolder.definition.viewManager,
109           let appContext = wrappedModuleHolder.appContext else {
110       return
111     }
112     let props = viewManager.propsDict()
113 
114     for (key, prop) in props {
115       let newValue = json[key] as Any
116 
117       // TODO: @tsapeta: Figure out better way to rethrow errors from here.
118       // Adding `throws` keyword to the function results in different
119       // method signature in Objective-C. Maybe just call `RCTLogError`?
120       try? prop.set(value: Conversions.fromNSObject(newValue), onView: view, appContext: appContext)
121     }
122     viewManager.callLifecycleMethods(withType: .didUpdateProps, forView: view)
123   }
124 
125   public static let viewManagerAdapterPrefix = "ViewManagerAdapter_"
126 
127   /**
128    Creates a subclass of `ViewModuleWrapper` in runtime. The new class overrides `moduleName` stub.
129    */
130   @objc
131   public static func createViewModuleWrapperClass(module: ViewModuleWrapper) -> ViewModuleWrapper.Type? {
132     // We're namespacing the view name so we know it uses our architecture.
133     let prefixedViewName = "\(viewManagerAdapterPrefix)\(module.name())"
134 
135     return prefixedViewName.withCString { viewNamePtr in
136       // Create a new class that inherits from `ViewModuleWrapper`. The class name passed here, doesn't work for Swift classes,
137       // so we also have to override `moduleName` class method.
138       let wrapperClass: AnyClass? = objc_allocateClassPair(ViewModuleWrapper.self, viewNamePtr, 0)
139 
140       // Dynamically add instance method returning wrapped module to the dynamic wrapper class.
141       // React Native initializes modules with `init` without params,
142       // so there is no other way to pass it to the instances.
143       let wrappedModuleBlock: @convention(block) () -> ViewModuleWrapper = { module }
144       let wrappedModuleImp: IMP = imp_implementationWithBlock(wrappedModuleBlock)
145       class_addMethod(wrapperClass, Selector("wrappedModule"), wrappedModuleImp, "@@:")
146 
147       return wrapperClass as? ViewModuleWrapper.Type
148     }
149   }
150 }
151 
152 // The direct event implementation can be cached and lazy-loaded (global and static variables are lazy by default in Swift).
153 let directEventBlockImplementation = imp_implementationWithBlock({ ["RCTDirectEventBlock"] } as @convention(block) () -> [String])
154