Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/RheaPublic

iOS/macOS/tvOS... App Time Event Dispatcher. Register via Swift Macro

License

NotificationsYou must be signed in to change notification settings

reers/Rhea

Repository files navigation

中文文档

Ask DeepWiki

Rhea

A framework for triggering various timings. Inspired by ByteDance's internal framework Gaia, but implemented in a different way.In Greek mythology, Rhea is the daughter of Gaia, hence the name of this framework.

After Swift 5.10, with the support of@_used@_section which can write data into sections, combined with Swift Macro, we can now achieve various decoupling and registration capabilities from the OC era. This framework has also been completely refactored using this approach.

🟡 Currently, this capability is still an experimental Swift Feature and needs to be enabled through configuration settings. See the integration documentation for details.

Requirements

XCode 16.0 +

iOS 13.0+, macOS 10.15+, tvOS 13.0+, visionOS 1.0+, watchOS 7.0+

Swift 5.10

swift-syntax 600.0.0

Basic Usage

import RheaExtension#rhea(time:.customEvent, priority:.veryLow, repeatable:true, func:{ _inprint("~~~~ customEvent in main")})#rhea(time:.homePageDidAppear,async:true, func:{ contextin    // This will run on a background threadprint("~~~~ homepageDidAppear")})#rhea(time:.load){ _inprint("load with trailing closure")}#load{print("use load directly")}#premain{print("use premain directly")}#appDidFinishLaunching{print("use appDidFinishLaunching directly")}class ViewController:UIViewController{        #load{DispatchQueue.global().async{print("~~~~ load nested in main")}}    #rhea(time:.homePageDidAppear){ contextinprint("homePageDidAppear with trailing closure\(context.param)")}overridefunc viewDidAppear(_ animated:Bool){        super.viewDidAppear(animated)Rhea.trigger(event:.homePageDidAppear, param:self)}}

The framework provides three callback timings:

  1. OC + load (Strongly Discouraged) Using this timing can significantly slow down app launch as it blocks the entire loading process. Prefer using async, or just use .premain or .appDidFinishLaunching for initialization tasks whenever possible.
  2. constructor (premain)
  3. appDidFinishLaunching ()

These three timings are triggered internally by the framework, and there's no need for external trigger calls.

Additionally, users can customize timings and triggers, configure execution priorities for the same timing, and whether they can be repeatedly executed.⚠️⚠️⚠️ However, note that the variable name of custom timing must exactly match its rawValue String, otherwise Swift Macro cannot process it correctly.

/// Registers a callback function for a specific Rhea event.////// This macro is used to register a callback function to a section in the binary,/// associating it with a specific event time, priority, and repeatability.////// - Parameters:///   - time: A `RheaEvent` representing the timing or event name for the callback.///           This parameter also supports direct string input, which will be///           processed by the framework as an event identifier.///   - priority: A `RheaPriority` value indicating the execution priority of the callback.///               Default is `.normal`. Predefined values include `.veryLow`, `.low`,///               `.normal`, `.high`, and `.veryHigh`. Custom integer priorities are also///               supported. Callbacks for the same event are sorted and executed based///               on this priority.///   - repeatable: A boolean flag indicating whether the callback can be triggered multiple times.///                 If `false` (default), the callback will only be executed once.///                 If `true`, the callback can be re-triggered on subsequent event occurrences.///   - async: A boolean flag indicating whether the callback should be executed asynchronously.///            If `false` (default), the callback will be executed on the main thread.///            If `true`, the callback will be executed on a background thread. Note that when///            `async` is `true`, the execution order based on `priority` may not be guaranteed.///            Even when `async` is set to `false`, users can still choose to dispatch their tasks///            to a background queue within the callback function if needed. This provides///            flexibility for handling both quick, main thread operations and longer-running///            background tasks.///   - func: The callback function of type `RheaFunction`. This function receives a `RheaContext`///           parameter, which includes `launchOptions` and an optional `Any?` parameter.////// - Note: When triggering an event externally using `Rhea.trigger(event:param:)`, you can include///         an additional parameter that will be passed to the callback via the `RheaContext`.////// ```swift/// #rhea(time: .load, priority: .veryLow, repeatable: true, func: { _ in///     print("~~~~ load in Account Module")/// })////// #rhea(time: .registerRoute, func: { _ in///     print("~~~~ registerRoute in Account Module")/// })////// // Use a StaticString as event directly/// #rhea(time: "ACustomEventString", func: { _ in///     print("~~~~ custom event")/// })////// // Example of using async execution/// #rhea(time: .load, async: true, func: { _ in///     // This will run on a background thread///     performHeavyTask()/// })////// // Example of manually dispatching to background queue when async is false/// #rhea(time: .load, func: { _ in///     DispatchQueue.global().async {///         // Perform background task///     }/// })/// ```/// - Note: ⚠️⚠️⚠️ When extending ``RheaEvent`` with static constants, ensure that///   the constant name exactly matches the string literal value. This practice///   maintains consistency and prevents confusion.///@freestanding(declaration)public macro rhea(    time:RheaEvent,    priority:RheaPriority=.normal,    repeatable:Bool=false,async:Bool=false,    func:RheaFunction)= #externalMacro(module:"RheaTimeMacros", type:"WriteTimeToSectionMacro")

AddCodeSnippets to XCode for greater efficiency.

~/Library/Developer/Xcode/UserData/CodeSnippets/

截屏2025-02-08 20 26 22

Performance

When tested on an iPhone 15 Pro in Release mode, with 3000 registered macros, it takes about 20 milliseconds to read the registered functions from the section. Additionally, the performance loss caused by executing theprint function after 3000 dispatches is about 1.5 milliseconds. For older models like the iPhone 8, it takes 98 milliseconds to read 3000 registered functions. Overall, such performance is sufficient for any ultra-large app.

Usage

  1. Setting breakpoints inside functions requires macro expansion first; otherwise, the breakpoints won't take effect.CleanShot 2025-07-30 at 16 08 14@2x

  2. If the passed-in function is relatively complex, an error may occur. You can encapsulate it into a function and then call it:

func complexFunction(context:RheaContext){    // Complex business logicperformComplexTask()handleMultipleOperations()}#rhea(time:.load){ contextincomplexFunction(context: context)}

Project Integration

Since business needs to customize events, like this:

extensionRheaEvent{publicstaticlethomePageDidAppear:RheaEvent="homePageDidAppear"publicstaticletregisterRoute:RheaEvent="registerRoute"publicstaticletdidEnterBackground:RheaEvent="didEnterBackground"}

The recommended approach is to wrap this framework in another layer, named RheaExtension for example

BusinessA    BusinessB    ↓           ↓RheaExtension     ↓  RheaTime

Additionally, RheaExtension can not only customize event names but also encapsulate business logic for timing events

#rhea(time: .appDidFinishLaunching, func: { _ in    NotificationCenter.default.addObserver(        forName: UIApplication.didEnterBackgroundNotification,        object: nil,        queue: .main    ) { _ in        Rhea.trigger(event: .didEnterBackground)    }})

External usage

#rhea(time: .didEnterBackground, repeatable: true, func: { _ in    print("~~~~ app did enter background")})

Swift Package Manager

Enable experimental feature throughswiftSettings:[.enableExperimentalFeature("SymbolLinkageMarkers")] in the dependent Package

// Package.swiftletpackage=Package(    name:"RheaExtension",    platforms:[.iOS(.v13)],    products:[.library(name:"RheaExtension", targets:["RheaExtension"]),],    dependencies:[.package(url:"https://github.com/reers/Rhea.git", from:"2.2.7")],    targets:[.target(            name:"RheaExtension",            dependencies:[.product(name:"RheaTime",package:"Rhea")],            // Add experimental feature enable here            swiftSettings:[.enableExperimentalFeature("SymbolLinkageMarkers")]),])// RheaExtension.swift// After @_exported, other business modules and main target only need to import RheaExtension@_exportedimport RheaTimeextensionRheaEvent{publicstaticlethomePageDidAppear:RheaEvent="homePageDidAppear"publicstaticletregisterRoute:RheaEvent="registerRoute"publicstaticletdidEnterBackground:RheaEvent="didEnterBackground"}
// Business Module Account// Package.swiftletpackage=Package(    name:"Account",    platforms:[.iOS(.v13)],    products:[.library(            name:"Account",            targets:["Account"]),],    dependencies:[.package(name:"RheaExtension", path:"../RheaExtension")],    targets:[.target(            name:"Account",            dependencies:[.product(name:"RheaExtension",package:"RheaExtension")],            // Add experimental feature enable here            swiftSettings:[.enableExperimentalFeature("SymbolLinkageMarkers")]),])// Business Module Account usageimport RheaExtension#rhea(time:.homePageDidAppear, func:{ contextinprint("~~~~ homepageDidAppear in main")})

In the main App Target, enable experimental feature in Build Settings:-enable-experimental-feature SymbolLinkageMarkersCleanShot 2024-10-12 at 20 39 59@2x

// Main target usageimport RheaExtension#rhea(time:.premain, func:{ _inRhea.trigger(event:.registerRoute)})

Additionally, you can directly passStaticString as time key.

#rhea(time: "ACustomEventString", func: { _ in    print("~~~~ custom event")})

CocoaPods

Add to Podfile:

pod'RheaTime'

Since CocoaPods doesn't support using Swift Macro directly, you can compile the macro implementation into binary for use. The integration method is as follows, requirings.pod_target_xcconfig to load the binary plugin of macro implementation:

// RheaExtension podspecPod::Spec.newdo |s|  s.name= 'RheaExtension'  s.version= '0.1.0'  s.summary= 'A short description of RheaExtension.'  s.description=<<-DESCTODO: Add long description of the pod here.                       DESC  s.homepage= 'https://github.com/bjwoodman/RheaExtension'  s.license={:type => 'MIT',:file => 'LICENSE'}  s.author={ 'bjwoodman'=> 'x.rhythm@qq.com'}  s.source={:git => 'https://github.com/bjwoodman/RheaExtension.git', :tag => s.version.to_s }  s.ios.deployment_target= '13.0'  s.source_files= 'RheaExtension/Classes/**/*'  s.dependency 'RheaTime', '2.2.7'  # Copy following config to your pod  s.pod_target_xcconfig={    'OTHER_SWIFT_FLAGS'=> '-enable-experimental-feature SymbolLinkageMarkers-Xfrontend-load-plugin-executable-Xfrontend ${PODS_ROOT}/RheaTime/MacroPlugin/RheaTimeMacros#RheaTimeMacros'}end
Pod::Spec.newdo |s|  s.name= 'Account'  s.version= '0.1.0'  s.summary= 'A short description of Account.'  s.description=<<-DESCTODO: Add long description of the pod here.                       DESC  s.homepage= 'https://github.com/bjwoodman/Account'  s.license={:type => 'MIT',:file => 'LICENSE'}  s.author={ 'bjwoodman'=> 'x.rhythm@qq.com'}  s.source={:git => 'https://github.com/bjwoodman/Account.git', :tag => s.version.to_s }  s.ios.deployment_target= '13.0'  s.source_files= 'Account/Classes/**/*'  s.dependency 'RheaExtension'    # Copy following config to your pod  s.pod_target_xcconfig={    'OTHER_SWIFT_FLAGS'=> '-enable-experimental-feature SymbolLinkageMarkers-Xfrontend-load-plugin-executable-Xfrontend ${PODS_ROOT}/RheaTime/MacroPlugin/RheaTimeMacros#RheaTimeMacros'}end

Alternatively, if not usings.pod_target_xcconfig ands.user_target_xcconfig, you can add the following script in podfile for unified processing:

post_installdo |installer|installer.pods_project.targets.eachdo |target|rhea_dependency=target.dependencies.find{ |d|['RheaTime','RheaExtension'].include?(d.name)}ifrhea_dependencyputs"Adding Rhea Swift flags to target:#{target.name}"target.build_configurations.eachdo |config|swift_flags=config.build_settings['OTHER_SWIFT_FLAGS'] ||=['$(inherited)']plugin_flag='-Xfrontend -load-plugin-executable -Xfrontend ${PODS_ROOT}/RheaTime/MacroPlugin/RheaTimeMacros#RheaTimeMacros'unlessswift_flags.join(' ').include?(plugin_flag)swift_flags.concat(plugin_flag.split)end# Add SymbolLinkageMarkers experimental feature flagsymbol_linkage_flag='-enable-experimental-feature SymbolLinkageMarkers'unlessswift_flags.join(' ').include?(symbol_linkage_flag)swift_flags.concat(symbol_linkage_flag.split)endconfig.build_settings['OTHER_SWIFT_FLAGS']=swift_flagsendendendend

Code usage is the same as SPM.

Note

⚠️ In theory, wrapping rhea macros could enable more convenient macros for use cases like route registration, plugin registration, module initialization, or specific encapsulations of rhea's time functionality. However, this appears to be currently blocked by a potential Swift bug. I've submitted anissue to the Swift repository and am awaiting a response.

Author

Asura19,x.rhythm@qq.com

License

Rhea is available under the MIT license. See the LICENSE file for more info.

About

iOS/macOS/tvOS... App Time Event Dispatcher. Register via Swift Macro

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp