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