// Copyright 2024-present 650 Industries. All rights reserved. import Foundation /// A Swift-based registry for legacy Expo modules that use the `EX_EXPORT_MODULE` /// and `EX_REGISTER_SINGLETON_MODULE` Objective-C macros. /// /// This registry replaces the static Objective-C data structures to enable /// compatibility with prebuilt SPM xcframeworks while maintaining backward /// compatibility with legacy module registration. /// /// This made it impossible to include both files in prebuilt xcframeworks. /// /// This Swift implementation: /// 1. Provides real registration storage via `@_cdecl` exported C functions /// 2. Bridges to `EXModuleRegistryProvider` for the `moduleRegistry` creation @objc(EXLegacyModuleRegistry) public final class LegacyModuleRegistry: NSObject, @unchecked Sendable { /// Shared singleton instance nonisolated(unsafe) public static let shared = LegacyModuleRegistry() /// Set of registered module classes (those using `EX_EXPORT_MODULE`) private var moduleClasses = Set() private var moduleClassList = [AnyClass]() /// Set of registered singleton module classes (those using `EX_REGISTER_SINGLETON_MODULE`) private var singletonModuleClasses = Set() private var singletonModuleClassList = [AnyClass]() /// Lazy-initialized singleton module instances private var singletonModules: [EXSingletonModule]? /// Thread-safety lock private let lock = NSLock() private override init() { super.init() // Register the Swift FileSystemManager which was previously hardcoded if let fileSystemClass = NSClassFromString("EXFileSystemLegacyUtilities") { registerModule(fileSystemClass) } } // MARK: - Module Registration /** Registers a module class. Called from `+load` methods of classes using the `EX_EXPORT_MODULE` macro. */ @objc public func registerModule(_ moduleClass: AnyClass) { lock.lock() defer { lock.unlock() } let identifier = ObjectIdentifier(moduleClass) if !moduleClasses.contains(identifier) { moduleClasses.insert(identifier) moduleClassList.append(moduleClass) } } /** Registers a singleton module class. Called from `+load` methods of classes using the `EX_REGISTER_SINGLETON_MODULE` macro. Handles the subclass preference heuristic: if a subclass is registered after its superclass, the superclass is removed. If a superclass is registered after a subclass, it's ignored. */ @objc public func registerSingletonModule(_ singletonModuleClass: AnyClass) { lock.lock() defer { lock.unlock() } // Remove any superclasses that are already registered // (prefer subclasses for override scenarios like ExpoKit) var superClass: AnyClass? = class_getSuperclass(singletonModuleClass) while let currentSuper = superClass, currentSuper != NSObject.self { let superId = ObjectIdentifier(currentSuper) if singletonModuleClasses.contains(superId) { singletonModuleClasses.remove(superId) singletonModuleClassList.removeAll { ObjectIdentifier($0) == superId } } superClass = class_getSuperclass(currentSuper) } // If a subclass is already registered, ignore this superclass for registeredClass in singletonModuleClassList { if class_getSuperclass(registeredClass) != nil && (singletonModuleClass as AnyObject).isKind(of: type(of: registeredClass)) { return } } let identifier = ObjectIdentifier(singletonModuleClass) if !singletonModuleClasses.contains(identifier) { singletonModuleClasses.insert(identifier) singletonModuleClassList.append(singletonModuleClass) } } // MARK: - Accessing Registered Modules /** Returns all registered module classes as an NSSet for ObjC compatibility. */ @objc public func getModuleClasses() -> NSSet { lock.lock() defer { lock.unlock() } return NSSet(array: moduleClassList) } /** Returns all registered module classes as an array. */ @objc public func getModuleClassesArray() -> [AnyClass] { lock.lock() defer { lock.unlock() } return moduleClassList } /** Returns all singleton module instances, creating them if necessary. */ @objc public func getSingletonModules() -> Set { lock.lock() defer { lock.unlock() } if let modules = singletonModules { return Set(modules) } // Initialize singleton instances var modules = [EXSingletonModule]() for singletonClass in singletonModuleClassList { if let instance = (singletonClass as? NSObject.Type)?.init() as? EXSingletonModule { modules.append(instance) } } singletonModules = modules return Set(modules) } /** Returns a singleton module instance for a specific class. */ @objc public func getSingletonModule(for singletonClass: AnyClass) -> EXSingletonModule? { let modules = getSingletonModules() for singleton in modules where singleton.isKind(of: singletonClass) { return singleton } return nil } } // MARK: - Module Registration (called from EXModuleRegistryProvider.m) // Note: The C functions EXRegisterModule and EXRegisterSingletonModule are // defined in EXModuleRegistryProvider.m. Those functions forward to this // Swift class for storage. This avoids duplicate symbol issues.