├── axe-system.png ├── router-declaration.png ├── Axe ├── Axe.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── Extension │ ├── React │ │ ├── AXEReactEventModule.h │ │ ├── AXEReactRouterModule.h │ │ ├── AXEReactDataModule.h │ │ ├── AXEReactNavigationModule.h │ │ ├── AXEReactControllerWrapper.h │ │ ├── AXEReactControllerWrapper.m │ │ ├── AXEReactDataModule.m │ │ ├── AXEReactViewController.h │ │ ├── AXEReactNavigationModule.m │ │ ├── AXEReactEventModule.m │ │ ├── AXEReactRouterModule.m │ │ └── AXEReactViewController.m │ ├── JavascriptSupport │ │ ├── AXEJavaScriptModelData.h │ │ ├── AXEData+JavaScriptSupport.h │ │ ├── AXEJavaScriptModelData.m │ │ └── AXEData+JavaScriptSupport.m │ ├── TabBarController │ │ ├── AXETabBarItem.m │ │ ├── AXETabBarItem.h │ │ ├── AXETabBarController.h │ │ └── AXETabBarController.m │ ├── OfflineHtml │ │ ├── AXEOfflineWebViewController.h │ │ ├── AXEOfflineWKWebViewController.h │ │ ├── AXEOfflineWebViewController.m │ │ └── AXEOfflineWKWebViewController.m │ ├── Util │ │ ├── AXEOfflineDownloadView.h │ │ ├── AXEViewController.h │ │ ├── AXEViewController.m │ │ └── AXEOfflineDownloadView.m │ ├── OfflineReact │ │ ├── AXEOfflineReactViewController.h │ │ └── AXEOfflineReactViewController.m │ ├── Html │ │ ├── AXEWKWebViewController.h │ │ ├── AXEWebViewController.h │ │ ├── AXEWebViewBridge.h │ │ ├── AXEWebViewController.m │ │ ├── AXEWKWebViewController.m │ │ └── AXEWebViewBridge.m │ └── DynamicRouter │ │ ├── AXEDynamicRouter.h │ │ └── AXEDynamicRouter.m ├── Axe │ ├── Event │ │ ├── AXEAutoreleaseEvent.h │ │ ├── AXEEventUserInterfaceState+Event.h │ │ ├── AXEEventUserInterfaceState.h │ │ ├── AXEModuleInitializer.m │ │ ├── AXEEventListener.h │ │ ├── AXEEventListener.m │ │ ├── AXEModuleInitializer.h │ │ ├── AXEEventUserInterfaceState.m │ │ ├── AXEAutoreleaseEvent.m │ │ ├── AXEEXTScope.h │ │ ├── AXEEvent.h │ │ └── AXEEvent.m │ ├── Axe.h │ ├── Data │ │ ├── AXEModelTypeData.m │ │ ├── AXEBaseData.h │ │ ├── AXEBaseData.m │ │ ├── AXEModelTypeData.h │ │ ├── AXEBasicTypeData.h │ │ ├── AXEBasicTypeData.m │ │ ├── AXEData.h │ │ └── AXEData.m │ ├── Router │ │ ├── AXERouteDefinition.h │ │ ├── AXERouteProtocolDefinition.h │ │ ├── AXERouteProtocolDefinition.m │ │ ├── AXERouteDefinition.m │ │ ├── AXERouteRequest.m │ │ ├── AXERouteRequest.h │ │ ├── AXERouter.h │ │ └── AXERouter.m │ ├── Info.plist │ └── AXEDefines.h ├── Podfile └── AxeTests │ ├── Info.plist │ └── AxeTests.m ├── LICENSE ├── .gitignore ├── Axe.podspec └── README.md /axe-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axe-org/axe/HEAD/axe-system.png -------------------------------------------------------------------------------- /router-declaration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axe-org/axe/HEAD/router-declaration.png -------------------------------------------------------------------------------- /Axe/Axe.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactEventModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactEventModule.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | event 15 | */ 16 | @interface AXEReactEventModule : NSObject 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactRouterModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactRouterModule.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | 路由 15 | */ 16 | @interface AXEReactRouterModule : NSObject 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEAutoreleaseEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEAutoreleaseEvent.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEEvent.h" 11 | 12 | /** 13 | AXEEvent 的派生类, 提供了简单的 自动释放的事件监听, 在事件执行一次后,会自动取消监听。 14 | */ 15 | @interface AXEAutoreleaseEvent : AXEEvent 16 | 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactDataModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactDataModule.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | 14 | /** 15 | axe 三大组件之 data 16 | */ 17 | @interface AXEReactDataModule : NSObject 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Axe/Axe/Axe.h: -------------------------------------------------------------------------------- 1 | // 2 | // Axe.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // data 12 | #import "AXEData.h" 13 | 14 | // event 15 | #import "AXEEvent.h" 16 | #import "AXEAutoreleaseEvent.h" 17 | #import "AXEModuleInitializer.h" 18 | #import "AXEEventUserInterfaceState.h" 19 | 20 | // router 21 | #import "AXERouter.h" 22 | 23 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactNavigationModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactNavigationModule.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | 提供一些常用的方法,进行 导航栏设置或者页面跳转, 以支持 使用原生导航栏的多页面应用。 15 | */ 16 | @interface AXEReactNavigationModule : NSObject 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEModelTypeData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEModelTypeData.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEModelTypeData.h" 10 | 11 | @implementation AXEModelTypeData 12 | 13 | + (instancetype)modelDataWithValue:(id)value { 14 | AXEModelTypeData *data = [AXEModelTypeData dataWithValue:value]; 15 | return data; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEBaseData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEBaseData.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | 数据类型基类。 13 | */ 14 | @interface AXEBaseData : NSObject 15 | 16 | /** 17 | 存放的实际数据内容 18 | */ 19 | @property (nonatomic,strong,readonly) id value; 20 | 21 | 22 | /** 23 | 实例方法。 24 | */ 25 | + (instancetype)dataWithValue:(id)value; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Axe/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Axe' do 5 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks 6 | # use_frameworks! 7 | 8 | # Pods for Axe 9 | pod 'WebViewJavascriptBridge' 10 | pod 'MXReact/CxxBridge' 11 | pod 'OfflinePackage' 12 | target 'AxeTests' do 13 | inherit! :search_paths 14 | # Pods for testing 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEventUserInterfaceState+Event.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEventUserInterfaceState+Event.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEEventUserInterfaceState.h" 10 | 11 | 12 | @interface AXEEventUserInterfaceStatus(Event) 13 | 14 | 15 | 16 | /** 17 | 当界面不在前台时, 存储事件 , 等待页面恢复。 18 | 19 | @param name 事件名称 20 | @param block 回调。 21 | */ 22 | - (void)storeEventName:(NSString *)name handlerBlock:(dispatch_block_t)block; 23 | 24 | 25 | 26 | /** 27 | 清理存储的事件。 28 | 当 事件取消监听时, 也要将这里存储的给清空。 29 | */ 30 | - (void)cleanStoredEvents; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEBaseData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEBaseData.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEBaseData.h" 10 | 11 | 12 | @interface AXEBaseData() 13 | @property (nonatomic,strong) id value; 14 | @end 15 | 16 | @implementation AXEBaseData 17 | 18 | + (instancetype)dataWithValue:(id)value { 19 | AXEBaseData *data = [[self alloc] init]; 20 | data.value = value; 21 | return data; 22 | } 23 | 24 | - (NSString *)description { 25 | return [NSString stringWithFormat:@"%@ , value : %@" , NSStringFromClass([self class]),_value]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Axe/Extension/JavascriptSupport/AXEJavaScriptModelData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEJavaScriptModelData.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEModelTypeData.h" 10 | #import "AXEData.h" 11 | 12 | 13 | /** 14 | 由 JS 创建的model类型的数据。 15 | js注意, 对于一个模型, 如果有key ,但是当前没值, 也必须使用 null 声明, 原生记录为 NSNull类型。 这样来记录完整的model的结构。 16 | 对于 由 js 共享出去的 model类型, 不支持 NSInterger float等类型, 只支持基础类型 ,即 NSNumber,NSString,NSArray,NSDictionary. 17 | */ 18 | @interface AXEJavaScriptModelData : AXEModelTypeData 19 | 20 | /** 21 | 创建 javascriptmodel. 22 | 23 | @param value js传入NSDictionary. 24 | */ 25 | + (instancetype)javascriptModelWithValue:(NSDictionary *)value; 26 | 27 | @end 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactControllerWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactControllerWrapper.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/14. 6 | // 7 | 8 | #import 9 | #import "AXEReactViewController.h" 10 | 11 | /** 12 | 使用弱引用 包装一下 AXEReactViewController 13 | 通过 launchOptions 传递给每个 NativeModule. 14 | */ 15 | @interface AXEReactControllerWrapper : NSObject 16 | 17 | 18 | /** 19 | 弱引用, 使每个 Module都可以获取当前页面。 20 | */ 21 | @property (nonatomic,readonly,weak) AXEReactViewController *controller; 22 | 23 | 24 | /** 25 | 初始化 26 | */ 27 | + (instancetype)wrapperWithController:(AXEReactViewController *)controller; 28 | 29 | @end 30 | 31 | /// 我们规定每个AXEReactViewController 创建时, 都会将自己的弱引用传入,以供module使用。 32 | extern NSString *const AXEReactControllerWrapperKey; 33 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteDefinition.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteDefinition.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXERouter.h" 11 | #import "AXERouteRequest.h" 12 | 13 | /** 14 | 路由的配置。 每个配置生成一个definition. 15 | */ 16 | @interface AXERouteDefinition : NSObject 17 | 18 | // 跳转路由的定义 19 | + (instancetype)defineJumpRouteForPath:(NSString *)path withRouteHandler:(AXEJumpRouteHandler)handler; 20 | 21 | - (void)jumpForRequest:(AXERouteRequest *)request; 22 | 23 | // 视图路由的定义 24 | + (instancetype)defineViewRouteForPath:(NSString *)path withRouteHandler:(AXEViewRouteHandler)handler; 25 | 26 | - (UIViewController *)viewForRequest:(AXERouteRequest *)request; 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Axe/AxeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteProtocolDefinition.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteProtocolDefinition.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXERouter.h" 11 | #import "AXERouteRequest.h" 12 | 13 | 14 | /** 15 | 协议路由的定义 16 | */ 17 | @interface AXERouteProtocolDefinition : NSObject 18 | 19 | // 跳转路由 20 | + (instancetype)defineJumpRouteForProtocol:(NSString *)protocol withRouteHandler:(AXEJumpRouteHandler)handler; 21 | 22 | - (void)jumpForRequest:(AXERouteRequest *)request; 23 | 24 | // 返回VC路由 25 | + (instancetype)defineViewRouteForProtocol:(NSString *)protocol withRouteHandler:(AXEViewRouteHandler)handler; 26 | 27 | - (UIViewController *)viewForRequest:(AXERouteRequest *)request; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Axe/Extension/TabBarController/AXETabBarItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXETabBarItem.m 3 | // Demo 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXETabBarItem.h" 10 | 11 | @interface AXETabBarItem() 12 | 13 | @property (nonatomic,copy) NSString *path; 14 | @property (nonatomic,copy) NSString *viewRoute; 15 | @end 16 | 17 | @implementation AXETabBarItem 18 | 19 | + (instancetype)itemWithPath:(NSString *)path viewRoute:(NSString *)viewRoute { 20 | NSParameterAssert([path isKindOfClass:[NSString class]]); 21 | NSParameterAssert([viewRoute isKindOfClass:[NSString class]]); 22 | 23 | AXETabBarItem *item = [[AXETabBarItem alloc] init]; 24 | item.path = path; 25 | item.viewRoute = viewRoute; 26 | return item; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactControllerWrapper.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactControllerWrapper.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/14. 6 | // 7 | 8 | #import "AXEReactControllerWrapper.h" 9 | 10 | 11 | @interface AXEReactControllerWrapper () 12 | @property (nonatomic,weak) AXEReactViewController *controller; 13 | @end 14 | 15 | @implementation AXEReactControllerWrapper 16 | 17 | + (instancetype)wrapperWithController:(AXEReactViewController *)controller { 18 | NSParameterAssert([controller isKindOfClass:[AXEReactViewController class]]); 19 | 20 | AXEReactControllerWrapper *wrapper = [[AXEReactControllerWrapper alloc] init]; 21 | wrapper.controller = controller; 22 | return wrapper; 23 | } 24 | 25 | @end 26 | 27 | NSString *const AXEReactControllerWrapperKey = @"AXEReactControllerWrapperKey"; 28 | -------------------------------------------------------------------------------- /Axe/Axe/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEModelTypeData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEModelTypeData.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEBaseData.h" 11 | 12 | 13 | 14 | /** 15 | model类型指的是 可以序列化的,用于前后端交互的 model类型。 所以能做到跨语言跨容器通用。 16 | 17 | model 协议, 存储的model类型,需要满足这个协议,以进行 序列化和反序列化。 18 | */ 19 | @protocol AXEDataModelProtocol 20 | 21 | /** 22 | 使用一个json来 重置model。 23 | 进行的是合并操作, 如现有值为 {a: 1,b: 2} 24 | 则我们传入 {b: 3} , 只修改 b的值,不修改a的值。 25 | */ 26 | - (void)axe_modelSetWithJSON:(NSDictionary *)json; 27 | /** 28 | model类型,必须支持能够转换成一个 json类型, 而且是map类型,不能是其他类型。 29 | */ 30 | - (NSDictionary *)axe_modelToJSONObject; 31 | 32 | @end 33 | 34 | /** 35 | model类型数据。 36 | */ 37 | @interface AXEModelTypeData : AXEBaseData 38 | 39 | 40 | 41 | /** 42 | 实例方法。 43 | 44 | @param value model 45 | @return 实例。 46 | */ 47 | + (instancetype)modelDataWithValue:(id)value; 48 | 49 | @end 50 | 51 | 52 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEventUserInterfaceState.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEventUserInterfaceStatus.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** 13 | 界面状态类, 页面打开关闭时,通知该状态进行变化。 14 | */ 15 | @interface AXEEventUserInterfaceStatus : NSObject 16 | 17 | /** 18 | 界面将要消失或隐藏 19 | */ 20 | - (void)containerWillDisappear; 21 | 22 | /** 23 | 界面显示。 24 | */ 25 | - (void)containerDidAppear; 26 | 27 | 28 | /** 29 | UI当前是否在前台或展示中。 30 | */ 31 | @property (nonatomic,readonly,assign) BOOL inFront; 32 | 33 | /** 34 | 创建函数。 35 | */ 36 | + (instancetype)status; 37 | 38 | 39 | /** 40 | 清理存储内容。 41 | */ 42 | - (void)cleanStoredEvents; 43 | @end 44 | 45 | /** 46 | UI 容器协议 , 负责监控界面是否在前台。 47 | 通过监控,来决定是否响应事件。 48 | */ 49 | @protocol AXEEventUserInterfaceContainer 50 | 51 | /** 52 | 持有 状态类,以监听页面前后台切换。 53 | */ 54 | @property (nonatomic,readonly,strong) AXEEventUserInterfaceStatus *AXEContainerStatus; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Axe/AxeTests/AxeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AxeTests.m 3 | // AxeTests 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AxeTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation AxeTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Axe/Extension/JavascriptSupport/AXEData+JavaScriptSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEData+JavaScriptSupport.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEData.h" 11 | 12 | 13 | /** 14 | 扩展 AXEData ,以支持 JavaScript中使用 AXEData. 15 | */ 16 | @interface AXEData (JavaScriptSupport) 17 | 18 | 19 | /** 20 | 设置 JavaScriptData 21 | 从 JS传入的是 NSDictionary类型的数据,其中声明了数据的具体类型, 然后转换成原生对应的类型。 22 | @param data 数据 23 | */ 24 | - (void)setJavascriptData:(NSDictionary *)data forKey:(NSString *)key; 25 | 26 | 27 | /** 28 | 获取数据,转换成javascript中可以使用的类型。 29 | @param key 键值 30 | @return 数据。 可能为空。 31 | */ 32 | - (NSDictionary *)javascriptDataForKey:(NSString *)key; 33 | 34 | 35 | 36 | /** 37 | 将 axedata 转换成 javascript可以理解的格式。 38 | @param data axedata 不能为空。 39 | @return 值, 如果解析失败,会返回空值。 40 | */ 41 | + (NSDictionary *)javascriptDataFromAXEData:(AXEData *)data; 42 | 43 | 44 | /** 45 | 将 javascript数据转换为 AXEData. 46 | */ 47 | + (AXEData *)axeDataFromJavascriptData:(NSDictionary *)javascriptData; 48 | 49 | @end 50 | 51 | 52 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineHtml/AXEOfflineWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWebViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/19. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEWebViewController.h" 11 | #import "AXEOfflineDownloadView.h" 12 | 13 | /** 14 | 我们推荐使用单页面应用 : 15 | 1.是单页面应用, 使用router, 页面通过 #/page 访问。 16 | 2.固定页面入口为 index.html文件, 放在根目录下。 17 | */ 18 | @interface AXEOfflineWebViewController : AXEWebViewController 19 | 20 | /** 21 | 创建离线webviewController 22 | 23 | @param path 入口文件, 固定为 index.html 24 | @param page 页面. 设置表示使用 #/page,通过路由访问具体页面。 25 | @param module 离线包模块。 26 | */ 27 | + (instancetype)webViewControllerWithFilePath:(NSString *)path 28 | page:(NSString *)page 29 | inModule:(NSString *)module; 30 | 31 | 32 | 33 | /** 34 | 注册 UIWebview 用于处理离线的网页请求。 该实现 只支持单页面应用 35 | 注册的协议为 ophttp://moduleName/pageName 36 | 解析后 , 通过 file:///path/to/index.html#/pageName 来访问页面。 37 | */ 38 | + (void)registerUIWebVIewForOfflineHtml; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineHtml/AXEOfflineWKWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWKWebViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/19. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEWKWebViewController.h" 11 | 12 | 13 | /** 14 | 使用 wkwebview实现的离线包的 webviewController. 15 | 16 | 我们推荐使用单页面应用 : 17 | 1.是单页面应用, 使用router, 页面通过 #/page 访问。 18 | 2.固定页面入口为 index.html文件, 放在根目录下。 19 | */ 20 | @interface AXEOfflineWKWebViewController : AXEWKWebViewController 21 | 22 | 23 | /** 24 | 创建离线webviewController 25 | 26 | @param path 入口文件, 固定为 index.html 27 | @param page 页面. 设置表示使用 #/page,通过路由访问具体页面。 28 | @param module 离线包模块。 29 | */ 30 | + (instancetype)webViewControllerWithFilePath:(NSString *)path 31 | page:(NSString *)page 32 | inModule:(NSString *)module; 33 | 34 | 35 | 36 | /** 37 | 注册 UIWebview 用于处理离线的网页请求。 该实现 只支持单页面应用 38 | 注册的协议为 ophttp://moduleName/pageName 39 | 解析后 , 通过 file:///path/to/index.html#/pageName 来访问页面。 40 | */ 41 | + (void)registerWKWebVIewForOfflineHtml; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present lxm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Axe/Axe/AXEDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEDefines.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | // 一些常用的配置内容。 10 | 11 | #if __OBJC__ 12 | # import 13 | #endif 14 | 15 | #define AXE_DEBUG 0 16 | 17 | //#if DEBUG 18 | //#define AXE_DEBUG 1 19 | //#else 20 | //#define AXE_DEBUG 0 21 | //#endif 22 | 23 | #if AXE_DEBUG 24 | #define AXELogTrace(...) fprintf(stderr,"%s\n",[NSString stringWithFormat:@"[AXE Trace] %@.%d %s \n: %@",\ 25 | [[NSString stringWithUTF8String:__FILE__] lastPathComponent] ,\ 26 | __LINE__ ,__PRETTY_FUNCTION__ , [NSString stringWithFormat:__VA_ARGS__]].UTF8String) 27 | #else 28 | #define AXELogTrace(...) 29 | #endif 30 | 31 | #define AXELogWarn(...) fprintf(stderr,"%s\n",[NSString stringWithFormat:@"[AXE Warn] %@.%d %s \n: %@",\ 32 | [[NSString stringWithUTF8String:__FILE__] lastPathComponent] ,\ 33 | __LINE__ ,__PRETTY_FUNCTION__ , [NSString stringWithFormat:__VA_ARGS__]].UTF8String) 34 | 35 | -------------------------------------------------------------------------------- /Axe/Extension/Util/AXEOfflineDownloadView.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWebViewDownloadView.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/20. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | 离线包 下载进度条。 13 | */ 14 | @interface AXEOfflineDownloadView : UIView 15 | 16 | 17 | /** 18 | 设置自定义实现。 19 | */ 20 | + (void)setCustomImplemation:(AXEOfflineDownloadView *(^)(UIView *))block; 21 | 22 | 23 | /** 24 | 在页面上显示 25 | 26 | @param view 展示在view或者window. 27 | @return AXEOfflineWebViewDownloadView 下载view. 28 | */ 29 | + (AXEOfflineDownloadView *)showInView:(UIView *)view; 30 | 31 | 32 | /** 33 | 设置一个按钮的标题与回调。 用于出错时处理。出错一般有两种思路 : 34 | 1. 提供一个关闭页面按钮 : 问题是 如果当前页面是放在首页的,不能关闭的页面,就会很尴尬。 35 | 2. 提供一个重试按钮。 即再次请求,重新下载。 我们当前默认使用重试按钮。 36 | 37 | @param title 标题 38 | @param block 回调 39 | */ 40 | - (void)setErrorHandlerButtonTitle:(NSString *)title withBlock:(void(^)(void))block; 41 | 42 | 43 | // 以下三个函数是回调,自定义时,需要实现以下三个函数。 44 | 45 | /** 46 | 进度条。 47 | 48 | @param progress 进度, 0-100 49 | */ 50 | - (void)didDownloadProgress:(NSInteger)progress; 51 | 52 | 53 | /** 54 | 成功下载回调 55 | */ 56 | - (void)didFinishLoadSuccess; 57 | 58 | /** 59 | 下载失败回调。 60 | */ 61 | - (void)didFinishLoadFailed; 62 | @end 63 | -------------------------------------------------------------------------------- /Axe/Extension/TabBarController/AXETabBarItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXETabBarItem.h 3 | // Demo 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | 记录 子项的相关数据 , 以供 AXETabBarController 配置相关界面和属性。 15 | */ 16 | @interface AXETabBarItem : NSObject 17 | 18 | 19 | /** 20 | 标题, 可为空 21 | */ 22 | @property (nonatomic,copy) NSString *title; 23 | 24 | 25 | /** 26 | 图标 , 可能为空 27 | */ 28 | @property (nonatomic,strong) UIImage *normalIcon; 29 | 30 | 31 | /** 32 | 选中时图标。 33 | */ 34 | @property (nonatomic,strong) UIImage *selectedIcon; 35 | 36 | 37 | 38 | /** 39 | 注册 在 axe://home/ 协议下的路径, 如果为 abc , 则路由协议为 axe://home/abc 40 | */ 41 | @property (nonatomic,readonly,copy) NSString *path; 42 | 43 | 44 | /** 45 | 已经注册的视图路由 , 如 axe://login/register 46 | AXETabBarController 根据这个路由协议,去获取viewController,并进行配置。 47 | */ 48 | @property (nonatomic,readonly,copy) NSString *viewRoute; 49 | 50 | 51 | /** 52 | 实例方法 53 | @param path 页面路径 54 | @param viewRoute 视图路由 55 | @return 实例 56 | */ 57 | + (instancetype)itemWithPath:(NSString *)path viewRoute:(NSString *)viewRoute; 58 | 59 | 60 | + (instancetype)new NS_UNAVAILABLE; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Axe/Extension/Util/AXEViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/21. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXERouter.h" 11 | #import "Axe.h" 12 | #import "AXERouteRequest.h" 13 | 14 | /** 15 | UIViewController 基类,使 webview、react和原生都使用同一个基类,以便于之后的扩展。 16 | */ 17 | @interface AXEViewController : UIViewController 18 | 19 | 20 | // 使用该方法实例化。 21 | - (instancetype)init; 22 | 23 | 24 | /** 25 | 记录request, 以进行传递。 26 | */ 27 | @property (nonatomic,strong) AXERouteRequest *routeRequest; 28 | 29 | // UI事件支持。 30 | @property (nonatomic,strong) AXEEventUserInterfaceStatus *AXEContainerStatus; 31 | 32 | 33 | /** 34 | 注册UI监听, 如果在界面在后台接受到通知时,会等待界面回到前台才会执行。 35 | 没有优先级设定,且必定是在主线程执行 。 36 | 37 | @param event 事件名 38 | @param block 回调。 39 | */ 40 | - (id)registerUIEvent:(NSString *)event withHandler:(AXEEventHandlerBlock)block; 41 | 42 | // 直接调用的路由接口 43 | - (void)jumpTo:(NSString *)url withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block; 44 | 45 | - (void)jumpTo:(NSString *)url; 46 | 47 | 48 | /** 49 | 提供一个页面完成展示时的回调。 50 | */ 51 | @property (nonatomic,copy) void (^didAppearBlock)(void); 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEModuleInitializer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEModuleInitializer.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEModuleInitializer.h" 10 | 11 | NSString *const AXEEventModulesBeginInitializing = @"AXEEventModulesBeginInitializing"; 12 | 13 | static NSMutableArray *registeredModules; 14 | 15 | @implementation AXEModuleInitializerManager 16 | 17 | 18 | + (void)registerModuleInitializer:(Class)initializer { 19 | NSParameterAssert(initializer); 20 | if ([initializer conformsToProtocol:@protocol(AXEModuleInitializer)]) { 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | registeredModules = [[NSMutableArray alloc] init]; 24 | }); 25 | [registeredModules addObject:initializer]; 26 | }else { 27 | NSLog(@"模块初始程序 必须实现协议 AXEModuleInitializer , 当前类%@不支持",NSStringFromClass(initializer)); 28 | } 29 | } 30 | 31 | 32 | + (void)initializeModules { 33 | if (registeredModules) { 34 | for (Class cls in registeredModules) { 35 | id initializer = [[cls alloc] init]; 36 | [initializer AEXInitialize]; 37 | } 38 | } 39 | [AXEEvent postEventName:AXEEventModulesBeginInitializing]; 40 | } 41 | 42 | @end 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineReact/AXEOfflineReactViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineReactViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/21. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEReactViewController.h" 11 | 12 | 13 | /** 14 | 实现了 oprn 协议的, 离线包的 rnviewcontroller. 以加载离线包的RN页面。 15 | 16 | 当前实现react-native的离线包 是针对单页面应用 , 所以我们约定了 17 | 1. 基于原生导航栏的多页面应用。 18 | 2. bundle文件名为 index.bundle 19 | 3. URL oprn://module/page module对应的是离线包的模块, 而page 对应的是 AppRegistry注册的页面。 20 | */ 21 | @interface AXEOfflineReactViewController : AXEReactViewController 22 | 23 | 24 | /** 25 | 实例方法。 26 | @param path bunlde文件的相对路径,默认为 'bundle.js' 27 | @param moduleName 通过AppRegistry注册的模块名, 28 | @param offlineModule 离线包的模块名。 29 | @return 返回创建vc. 30 | */ 31 | + (instancetype)controllerWithBundlePath:(NSString *)path 32 | moduleName:(NSString *)moduleName 33 | inOfflineModule:(NSString *)offlineModule; 34 | 35 | // 离线包的模块名。 36 | @property (nonatomic,readonly,copy) NSString *offlineModule; 37 | 38 | /** 39 | 注册协议为 oprn:// 40 | 一般形式的路径为 oprn://moudleA/login 41 | 42 | 则实际解析的URL为 : 43 | file:///path/to/module/bundle.js 44 | 45 | */ 46 | + (void)registerOfflineReactProtocol; 47 | 48 | @end 49 | 50 | // oprn 51 | extern NSString *const AXEOfflineReactProtocol; 52 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactDataModule.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactDataModule.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEReactDataModule.h" 10 | #import 11 | #import "AXEData+JavaScriptSupport.h" 12 | #import "AXEDefines.h" 13 | 14 | @implementation AXEReactDataModule 15 | RCT_EXPORT_MODULE(axe_data); 16 | 17 | 18 | 19 | RCT_EXPORT_METHOD(setData:(NSDictionary *)param){ 20 | if ([param isKindOfClass:[NSDictionary class]]) { 21 | [[AXEData sharedData] setJavascriptData:param forKey:[param objectForKey:@"key"]]; 22 | } else { 23 | AXELogWarn(@"param 需要为 NSDictionary类型!"); 24 | } 25 | } 26 | 27 | RCT_EXPORT_METHOD(removeData:(NSString *)key){ 28 | if ([key isKindOfClass:[NSString class]]) { 29 | [[AXEData sharedData] removeDataForKey:key]; 30 | } else { 31 | AXELogWarn(@"key 需要为 NSString 类型!"); 32 | } 33 | 34 | } 35 | 36 | 37 | RCT_EXPORT_METHOD(getData:(NSString *)key callback:(RCTResponseSenderBlock)callback) { 38 | if ([key isKindOfClass:[NSString class]]) { 39 | NSDictionary *data = [[AXEData sharedData] javascriptDataForKey:key]; 40 | NSArray *response; 41 | if (data) { 42 | response = @[data]; 43 | } 44 | callback(response); 45 | } else { 46 | AXELogWarn(@"key 需要为 NSString 类型!"); 47 | } 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Axe/Extension/Util/AXEViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/21. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEViewController.h" 10 | 11 | @implementation AXEViewController 12 | 13 | - (instancetype)init { 14 | if (self = [super init]) { 15 | _AXEContainerStatus = [AXEEventUserInterfaceStatus status]; 16 | } 17 | return self; 18 | } 19 | 20 | - (void)viewDidAppear:(BOOL)animated { 21 | [super viewDidAppear:animated]; 22 | 23 | if (_didAppearBlock) { 24 | _didAppearBlock(); 25 | _didAppearBlock = nil; 26 | } 27 | [_AXEContainerStatus containerDidAppear]; 28 | } 29 | 30 | - (void)viewWillDisappear:(BOOL)animated { 31 | [super viewWillDisappear:animated]; 32 | 33 | [_AXEContainerStatus containerWillDisappear]; 34 | } 35 | 36 | - (id)registerUIEvent:(NSString *)event withHandler:(AXEEventHandlerBlock)block { 37 | return [AXEEvent registerUIListenerForEventName:event handler:block inUIContainer:self]; 38 | } 39 | 40 | - (void)jumpTo:(NSString *)url withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block { 41 | return [[AXERouter sharedRouter] jumpTo:url fromViewController:self withParams:params finishBlock:block]; 42 | } 43 | 44 | - (void)jumpTo:(NSString *)url { 45 | return [[AXERouter sharedRouter] jumpTo:url fromViewController:self]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWKWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWKWebViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "AXERouter.h" 13 | #import "AXEViewController.h" 14 | // 使用WebViewJavascriptBridge 作为桥接。 15 | @class WKWebViewJavascriptBridge; 16 | 17 | /** 18 | 使用 WKWebView展示网页 19 | 推荐使用 WKWebView, 性能更好。 20 | */ 21 | @interface AXEWKWebViewController : AXEViewController 22 | 23 | /** 24 | 创建实例 25 | 26 | @param url url 27 | @return 实例 28 | */ 29 | + (instancetype)webViewControllerWithURL:(NSString *)url; 30 | 31 | 32 | /** 33 | 需要注意的是 ,这个webview使用webviewJSBridge设置了delegate , 所以不能直接设置delegate, 请使用下面的 webViewDelegate属性。 34 | */ 35 | @property (nonatomic,strong,readonly) WKWebView *webView; 36 | 37 | 38 | /** 39 | js bridge 。 通过这个来注册自己的插件,供h5调用. 40 | */ 41 | @property (nonatomic,readonly,strong) WKWebViewJavascriptBridge *javascriptBridge; 42 | 43 | /** 44 | 设置代理 不能直接操作上面的webview的delegate 45 | */ 46 | @property (nonatomic,weak) id webViewDelegate; 47 | 48 | /** 49 | 定制AXEWebViewController. 50 | */ 51 | + (void)setCustomViewDidLoadBlock:(void(^)(AXEWKWebViewController *))block; 52 | 53 | #pragma mark - router and setting 54 | 55 | 56 | /** 57 | 注册 http协议, 使用WKWebView处理。 58 | */ 59 | + (void)registerWKWebViewForHTTP; 60 | 61 | 62 | /** 63 | 注册 https协议 , 使用WKWebView处理。 64 | */ 65 | + (void)registerWKWebViewForHTTPS; 66 | @end 67 | -------------------------------------------------------------------------------- /Axe/Extension/TabBarController/AXETabBarController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXETabBarController.h 3 | // Demo 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "AXETabBarItem.h" 12 | 13 | 14 | 15 | /** 16 | 基于Axe , 实现简单的TabBarController路由 17 | 这里是放置于首页的tabbarController 。 所以默认注册协议 axe://home/page 18 | 所有首页的ViewController 都包含在 UINavigationController中!!! 19 | */ 20 | @interface AXETabBarController : UITabBarController 21 | 22 | /** 23 | 注册一个子项。 注意 tarbar的子界面的顺序,由注册的顺序决定。 24 | 25 | @param barItem 详细配置 26 | */ 27 | + (void)registerTabBarItem:(AXETabBarItem *)barItem; 28 | 29 | 30 | /** 31 | 在viewDidLoad时,回调该block, 以再做一些barItem的定制化 32 | 33 | @param block 定制block 34 | */ 35 | + (void)setCustomDecorateBlock:(void (^)(AXETabBarController *))block; 36 | 37 | 38 | /** 39 | 设置 navigationController 基类。 40 | 设置该类型, 调用 -initWithRootViewController: 方法来创建指定类型的 NavigationController类的实例。 41 | @param cls navigationController基类 42 | */ 43 | + (void)setNavigationControllerClass:(Class)cls; 44 | 45 | /** 46 | 单例。 47 | */ 48 | + (instancetype)sharedTabBarController; 49 | 50 | // 跳回到首页具体 index的页面。 51 | - (void)backToIndex:(NSInteger)index fromViewController:(UIViewController *)fromVC; 52 | @end 53 | 54 | // 默认protocol名称 , home 55 | extern NSString *AXETabBarRouterDefaultProtocolName; 56 | 57 | // 注册的路由会附带两个参数 58 | // index ,从0开始 59 | extern NSString *const AXETabBarRouterIndexKey; 60 | // path , 指 AXETabBarItem.pagePath 61 | extern NSString *const AXETabBarRouterPathKey;; 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | Pods/ 38 | *.xcworkspace 39 | Podfile.lock 40 | 41 | # Carthage 42 | # 43 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 44 | # Carthage/Checkouts 45 | 46 | Carthage/Build 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 54 | 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | 60 | # Code Injection 61 | # 62 | # After new code Injection tools there's a generated folder /iOSInjectionProject 63 | # https://github.com/johnno1962/injectionforxcode 64 | 65 | iOSInjectionProject/ 66 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteProtocolDefinition.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteProtocolDefinition.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXERouteProtocolDefinition.h" 10 | 11 | @interface AXERouteProtocolDefinition() 12 | 13 | @property (nonatomic,copy) NSString *protocol; 14 | 15 | @property (nonatomic,copy) AXEJumpRouteHandler jumpHandler; 16 | 17 | @property (nonatomic,copy) AXEViewRouteHandler viewHandler; 18 | 19 | @property (nonatomic,assign) NSInteger routeCount; 20 | 21 | @end 22 | 23 | @implementation AXERouteProtocolDefinition 24 | 25 | + (instancetype)defineJumpRouteForProtocol:(NSString *)protocol withRouteHandler:(AXEJumpRouteHandler)handler { 26 | AXERouteProtocolDefinition *definition = [[AXERouteProtocolDefinition alloc] init]; 27 | definition.routeCount = 0; 28 | definition.protocol = protocol; 29 | definition.jumpHandler = handler; 30 | return definition; 31 | } 32 | 33 | - (void)jumpForRequest:(AXERouteRequest *)request { 34 | _routeCount ++; 35 | dispatch_async(dispatch_get_main_queue(), ^{ 36 | self->_jumpHandler(request); 37 | }); 38 | } 39 | 40 | // 返回VC路由 41 | + (instancetype)defineViewRouteForProtocol:(NSString *)protocol withRouteHandler:(AXEViewRouteHandler)handler { 42 | AXERouteProtocolDefinition *definition = [[AXERouteProtocolDefinition alloc] init]; 43 | definition.routeCount = 0; 44 | definition.protocol = protocol; 45 | definition.viewHandler = handler; 46 | return definition; 47 | } 48 | 49 | - (UIViewController *)viewForRequest:(AXERouteRequest *)request { 50 | _routeCount ++; 51 | return _viewHandler(request); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWebViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "AXERouter.h" 12 | #import "AXEViewController.h" 13 | // 使用 WebViewJavascriptBridge 作为桥接 14 | @class WebViewJavascriptBridge; 15 | 16 | // 对于 H5默认路由, 关于回调的注意事项 。 17 | // 参考 AXERouter.h中对于路由的描述 ,已知, 对于 跳转路由 ,回调时的界面关闭由 被调用者 实现, 对于 页面路由 ,界面关闭由调用者管理。 18 | // 所以, 在 h5 默认实现中, 对于 jump route 带有回调时, js 正常调用回调接口即可, 页面关闭由 AXEWebViewController 实现了。 19 | 20 | /** 21 | WebView容器, 使用 UIWebView 22 | */ 23 | @interface AXEWebViewController : AXEViewController 24 | 25 | 26 | /** 27 | 创建实例 28 | 29 | @param url url 30 | @return 实例 31 | */ 32 | + (instancetype)webViewControllerWithURL:(NSString *)url; 33 | 34 | /** 35 | 需要注意的是 ,这个webview使用webviewJSBridge设置了delegate ,所以不能直接设置delegate, 请使用下面的 webViewDelegate属性。 36 | */ 37 | @property (nonatomic,readonly,strong) UIWebView *webView; 38 | 39 | /** 40 | js bridge 。 通过WebViewJavascriptBridge来注册自己的插件,供h5调用. 41 | */ 42 | @property (nonatomic,readonly,strong) WebViewJavascriptBridge *javascriptBridge; 43 | 44 | /** 45 | 设置webView代理 . 46 | */ 47 | @property (nonatomic,weak) id webViewDelegate; 48 | 49 | 50 | /** 51 | 定制AXEWebViewController. 52 | */ 53 | + (void)setCustomViewDidLoadBlock:(void(^)(AXEWebViewController *))block; 54 | 55 | #pragma mark - router 56 | 57 | 58 | /** 59 | 注册 http协议, 使用UIWebView处理。 60 | */ 61 | + (void)registerUIWebViewForHTTP; 62 | 63 | 64 | /** 65 | 注册 https协议 , 使用UIWebView处理。 66 | */ 67 | + (void)registerUIWebViewForHTTPS; 68 | 69 | 70 | @end 71 | 72 | 73 | -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWebViewBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWebViewBridge.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "WebViewJavascriptBridgeBase.h" 13 | #import "AXEEvent.h" 14 | #import "AXEEventUserInterfaceState.h" 15 | #import "AXERouter.h" 16 | #import "AXEViewController.h" 17 | 18 | // wkWebview和uiwebview的 jsbridge 接口相同, 使用同样的方式去调用。 19 | @protocol AXEWebViewJavaScriptBridge 20 | - (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler; 21 | - (void)removeHandler:(NSString*)handlerName; 22 | - (void)callHandler:(NSString*)handlerName; 23 | - (void)callHandler:(NSString*)handlerName data:(id)data; 24 | - (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback; 25 | - (void)setWebViewDelegate:(id)webViewDelegate; 26 | - (void)disableJavscriptAlertBoxSafetyTimeout; 27 | @end 28 | 29 | 30 | /** 31 | 桥接 axe的三大组件与webview。 桥接使用 WebviewJavaScriptBridge, 32 | 请查看 axe-js项目 33 | https://github.com/axe-org/axe-js 34 | 35 | * 三大组件中, router 和 event 支持各种形式的h5页面。 36 | 但是event 比较特殊, 容易发生 重复注册 和 内存泄漏问题, 目前只适配了基于Vue的单页面应用。 37 | */ 38 | @interface AXEWebViewBridge : NSObject 39 | 40 | 41 | /** 42 | 初始化 43 | */ 44 | + (instancetype)bridgeWithUIWebView:(UIWebView *)webView; 45 | 46 | 47 | /** 48 | 初始化 49 | */ 50 | + (instancetype)bridgeWithWKWebView:(WKWebView *)webView; 51 | 52 | @property (nonatomic,readonly,strong) id javascriptBridge; 53 | 54 | 55 | @property (nonatomic,weak) AXEViewController *webviewController; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteDefinition.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteDefinition.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXERouteDefinition.h" 10 | 11 | @interface AXERouteDefinition() 12 | 13 | @property (nonatomic,copy) NSString *path; 14 | 15 | @property (nonatomic,copy) AXEJumpRouteHandler jumpHandler; 16 | 17 | @property (nonatomic,copy) AXEViewRouteHandler viewHandler; 18 | // 简单的统计。。 19 | @property (nonatomic,assign) NSInteger routeCount; 20 | @end 21 | 22 | @implementation AXERouteDefinition 23 | 24 | 25 | + (instancetype)defineJumpRouteForPath:(NSString *)path withRouteHandler:(AXEJumpRouteHandler)handler; { 26 | AXERouteDefinition *definition = [[AXERouteDefinition alloc] init]; 27 | definition.routeCount = 0; 28 | definition.path = path; 29 | definition.jumpHandler = handler; 30 | return definition; 31 | } 32 | 33 | - (void)jumpForRequest:(AXERouteRequest *)request { 34 | _routeCount ++ ; 35 | dispatch_async(dispatch_get_main_queue(), ^{ 36 | self->_jumpHandler(request); 37 | }); 38 | } 39 | 40 | - (NSString *)description { 41 | return [NSString stringWithFormat:@"<%@ %@://%@>" , _jumpHandler ? @"JumpRoute" : @"ViewRoute", AXERouterProtocolName , _path]; 42 | } 43 | 44 | 45 | + (instancetype)defineViewRouteForPath:(NSString *)path withRouteHandler:(AXEViewRouteHandler)handler { 46 | AXERouteDefinition *definition = [[AXERouteDefinition alloc] init]; 47 | definition.routeCount = 0; 48 | definition.path = path; 49 | definition.viewHandler = handler; 50 | return definition; 51 | } 52 | 53 | - (UIViewController *)viewForRequest:(AXERouteRequest *)request { 54 | _routeCount ++; 55 | return _viewHandler(request); 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEBasicTypeData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEBasicTypeData.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEBaseData.h" 11 | 12 | @class UIImage; 13 | // 基础数据类型, 再继续细分。 14 | typedef NS_ENUM(NSUInteger, AXEDataBasicType) { 15 | AXEDataBasicTypeNumber,// 数据类型只能存 number 类型, 其他的 int ,bool,float都需要转换成number. 16 | AXEDataBasicTypeString, // 字符串类型 17 | AXEDataBasicTypeArray, // 数组类型 , 18 | AXEDataBasicTypeDictionary,// 字典类型 19 | // 以上是基础类型。 20 | // 以下是比较特殊的类型。 21 | AXEDataBasicTypeUIImage, // 支持图像类型。 22 | AXEDataBasicTypeBoolean, // bool 类型 23 | AXEDataBasicTypeData , // NSData 类型 24 | AXEDataBasicTypeDate , // NSDate类型 25 | }; 26 | // 注意, 容器类型,字典和数组, 不支持Model类型,只能放基础数据类型。 27 | // 注意 , 如果 NSArray 和 NSDictionary中放嵌套的model类型, 正常在原生代码中没有问题,但是传递给js时会导致异常。 28 | /** 29 | 基础数据类型的data 30 | */ 31 | @interface AXEBasicTypeData : AXEBaseData 32 | 33 | 34 | /** 35 | 基础数据类型 36 | */ 37 | @property (nonatomic,readonly,assign) AXEDataBasicType basicType; 38 | 39 | /** 40 | 实例方法 41 | */ 42 | + (instancetype)basicDataWithNumber:(NSNumber *)number; 43 | 44 | 45 | /** 46 | 实例方法 47 | */ 48 | + (instancetype)basicDataWithString:(NSString *)string; 49 | 50 | 51 | /** 52 | 实例方法 53 | */ 54 | + (instancetype)basicDataWithArray:(NSArray *)array; 55 | 56 | /** 57 | 实例方法 58 | */ 59 | + (instancetype)basicDataWithDictionary:(NSDictionary *)dictionary; 60 | 61 | 62 | /** 63 | 实例方法 64 | */ 65 | + (instancetype)basicDataWithImage:(UIImage *)image; 66 | 67 | /** 68 | 实例方法 69 | */ 70 | + (instancetype)basicDataWithBoolean:(BOOL)boolean; 71 | 72 | /** 73 | 实例方法 74 | */ 75 | + (instancetype)basicDataWithData:(NSData *)data; 76 | 77 | /** 78 | 实例方法 79 | */ 80 | + (instancetype)basicDataWithDate:(NSDate *)date; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Axe/Extension/DynamicRouter/AXEDynamicRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEDynamicRouter.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/4/16. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | /** 13 | 动态路由, 使用路由,表示我们使用声明路由与实现路由结合的 动态化路由跳转方案。 14 | 声明路由 为 axes://module/page 形式 15 | 实现路由 为 axe://module/page 或者 http://xxx.xx/xx/#/page 16 | 17 | 我们定义的动态路由规则为 : 18 | 19 | 'module' => 'axe://module/' 20 | 左侧为模块名, 右侧为重定向地址。 21 | 右侧的重定向地址可以是 "http://xxx.xx/xxx/#/" 22 | 或者 'reacts://demo.axe-org.cn/login-react/bundle.js?module='这样的地址。 23 | 最终组装URL = redirectURL + page (注意,这里就是直接拼装, 所以这个page可以被添加到 锚点、参数和URL上。) 24 | 25 | 即 如果一个声明路由为 axes://module/page 26 | 则如上的http的重定向,指向了 "http://xxx.xx/xxx/#/page" 27 | 28 | 。 29 | */ 30 | @interface AXEDynamicRouter : NSObject 31 | 32 | /// 单例 33 | + (instancetype)sharedDynamicRouter; 34 | 35 | 36 | /** 37 | 初始化 38 | 39 | @param serverURL 设置服务器地址, 如果为空,则表示不请求网络 40 | @param setting 默认配置, 必须要有默认配置,因为整个APP基于动态路由去加载,没有默认配置会导致初始化问题 41 | 42 | 格式是 : 43 | { 44 | "moduleA" : "http://www.xxx.com/xx/", 45 | "moduleB" : "axe://moduleB/" , 46 | "moduleC" : "react://moduleC/" 47 | } 48 | 49 | 该模块的配置有三个来源, 默认配置 、 本地存储、 接口请求。 通过检测版本变更,来判断是使用本地存储还是默认配置。 如果本地修改测试时,需要注意这一点。 50 | 51 | */ 52 | - (void)setupWithURL:(NSString *)serverURL defaultSetting:(NSDictionary *)setting; 53 | 54 | 55 | /** 56 | 检测时间间隔, 默认10分钟。 检测时机是 进入前台且间隔10分钟。 57 | */ 58 | @property (nonatomic,assign) NSTimeInterval checkTimeInterval; 59 | 60 | 61 | /** 62 | app 版本号,要求 三段式 。 默认自己读Info.plist,不需要设置 (但是如果Info.plist不是三段式格式,后台会报错)。 63 | */ 64 | @property (nonatomic,strong) NSString *appVersion; 65 | 66 | /** 67 | 设置 tags , 以用于AB测试。 68 | */ 69 | @property (nonatomic,strong) NSArray *tags; 70 | 71 | @end 72 | 73 | // 声明路由的协议, 默认为 axes,可以修改 74 | extern NSString *AXEStatementRouterProtocol; 75 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEventListener.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEventListener.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEEvent.h" 11 | #import "AXEEventUserInterfaceState.h" 12 | 13 | @interface AXEEvent (ListenerRemoval) 14 | 15 | 16 | /** 17 | 当 事件没有监听时, 进行删除。 18 | */ 19 | + (void)removeListenerForEventName:(NSString *)name; 20 | @end 21 | 22 | 23 | @class AXEEventListenerPriorityQueue; 24 | 25 | /** 26 | 事件监听器。 27 | */ 28 | @interface AXEEventListener : NSObject 29 | 30 | 31 | /** 32 | 回调block 33 | */ 34 | @property (nonatomic,copy) AXEEventHandlerBlock handler; 35 | 36 | 37 | /** 38 | 监听的事件名称 39 | */ 40 | @property (nonatomic,copy) NSString *eventName; 41 | 42 | 43 | /** 44 | 同步异步 45 | */ 46 | @property (nonatomic,assign) BOOL asynchronous; 47 | 48 | 49 | /** 50 | 异步的情况下, 决定是串行还是并行。 51 | */ 52 | @property (nonatomic,assign) BOOL serial; 53 | 54 | /** 55 | 优先级 56 | */ 57 | @property (nonatomic,assign) NSInteger priority; 58 | 59 | 60 | 61 | /** 62 | UI监听 。 63 | */ 64 | @property (nonatomic,assign) BOOL userInterface; 65 | 66 | /** 67 | 用于判断UI状态。 设置为 weak , 当界面消失时, 会自动释放监听。 68 | */ 69 | @property (nonatomic,weak) AXEEventUserInterfaceStatus *containerStatus; 70 | 71 | /** 72 | 当前监听所在的队列,用于 dispose 73 | */ 74 | @property (nonatomic,strong) AXEEventListenerPriorityQueue *queue; 75 | 76 | @end 77 | 78 | 79 | /** 80 | 优先级队列,先入先出 , 同时使用优先级控制。 81 | */ 82 | @interface AXEEventListenerPriorityQueue : NSObject 83 | 84 | 85 | - (void)insert:(AXEEventListener *)item; 86 | 87 | - (void)delete:(AXEEventListener *)item; 88 | 89 | /** 90 | 根据优先级便利。 91 | 92 | @param block 外部设定便利任务, 这里将handler的执行,放在 AXEEvent中。 93 | */ 94 | - (void)enumerateListenersUsingBlock:(void (^)(AXEEventListener *obj))block; 95 | 96 | @end 97 | 98 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEventListener.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEventListener.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEEventListener.h" 10 | #import "AXEEventUserInterfaceState+Event.h" 11 | 12 | @implementation AXEEventListener 13 | 14 | - (void)dispose { 15 | _handler = nil; 16 | [_queue delete:self]; 17 | _queue = nil; 18 | if (_containerStatus) { 19 | [_containerStatus cleanStoredEvents]; 20 | _containerStatus = nil; 21 | } 22 | } 23 | 24 | @end 25 | 26 | 27 | @interface AXEEventListenerPriorityQueue () 28 | 29 | 30 | /** 31 | 使用数组实现队列功能 32 | */ 33 | @property (nonatomic,strong) NSMutableArray *list; 34 | @end 35 | 36 | 37 | @implementation AXEEventListenerPriorityQueue 38 | 39 | - (instancetype)init { 40 | if (self = [super init]) { 41 | _list = [[NSMutableArray alloc] init]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)insert:(AXEEventListener *)item { 47 | @synchronized(self) { 48 | NSInteger index = 0; 49 | for (AXEEventListener *last in _list) { 50 | if (last.priority < item.priority) { 51 | break; 52 | } 53 | index ++; 54 | } 55 | [_list insertObject:item atIndex:index]; 56 | } 57 | } 58 | 59 | - (void)delete:(AXEEventListener *)item { 60 | @synchronized(self) { 61 | [_list removeObject:item]; 62 | } 63 | if (_list.count == 0) { 64 | [AXEEvent removeListenerForEventName:item.eventName]; 65 | } 66 | } 67 | 68 | - (void)enumerateListenersUsingBlock:(void (^)(AXEEventListener *))block { 69 | NSArray *list; 70 | @synchronized(self) { 71 | list = [_list copy]; 72 | } 73 | [list enumerateObjectsUsingBlock:^(AXEEventListener * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 74 | block(obj); 75 | }]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteRequest.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXERouteRequest.h" 10 | #import "AXEDefines.h" 11 | 12 | @interface AXERouteRequest() 13 | @property (nonatomic,copy) NSString *sourceURL; 14 | @property (nonatomic,weak) UIViewController *fromVC; 15 | @property (nonatomic,strong) AXEData *params; 16 | @end 17 | 18 | @implementation AXERouteRequest 19 | 20 | + (instancetype)requestWithSourceURL:(NSString *)sourceURL 21 | params:(AXEData *)params 22 | callback:(AXERouteCallbackBlock)callback 23 | fromVC:(UIViewController *)fromVC { 24 | AXERouteRequest *request = [[AXERouteRequest alloc] init]; 25 | request.sourceURL = sourceURL; 26 | request.valid = YES; 27 | request.params = params; 28 | request.callback = callback; 29 | request.fromVC = fromVC; 30 | request.currentURL = sourceURL; 31 | return request; 32 | } 33 | 34 | 35 | - (void)setCurrentURL:(NSString *)currentURL { 36 | _currentURL = [currentURL copy]; 37 | // 解析url解析。 38 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:_currentURL]; 39 | NSString *protocol = urlComponets.scheme; 40 | if (!protocol) { 41 | AXELogWarn(@"AXERouterRequest 检测失败 ,URL : %@有误!!",_currentURL); 42 | _valid = NO; 43 | } 44 | _protocol = protocol; 45 | if (!_params) { 46 | _params = [AXEData dataForTransmission]; 47 | } 48 | // 解析参数。 49 | NSString *query = urlComponets.query; 50 | if (query) { 51 | // 解析URL中的参数. 52 | for (NSURLQueryItem *item in urlComponets.queryItems) { 53 | [_params setData:item.value forKey:item.name]; 54 | } 55 | } 56 | } 57 | 58 | - (NSString *)description { 59 | return [NSString stringWithFormat:@"",_currentURL]; 60 | } 61 | 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactViewController.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXERouter.h" 11 | #import "AXEEventUserInterfaceState.h" 12 | #import "AXEViewController.h" 13 | 14 | @class RCTRootView; 15 | 16 | /** 17 | React Native 的 VC 基类, 提供了默认的 路由实现,同时为react 提供了 axe 三大组件的支持。 18 | 需要注意,我们推荐 使用基于原生导航栏的多页面应用。 19 | */ 20 | @interface AXEReactViewController : AXEViewController 21 | 22 | /** 23 | 设置回调, 在ViewDidLoad时执行, 以定制AXEReactViewController 24 | 25 | @param block 定制。 26 | */ 27 | + (void)setCustomViewDidLoadBlock:(void(^)(AXEReactViewController *))block; 28 | 29 | /** 30 | 创建viewController 31 | 我们推荐使用 多页面应用 32 | @param url URL 33 | @param moduleName AppRegistry中注册的模块名。 34 | */ 35 | + (instancetype)controllerWithURL:(NSString *)url moduleName:(NSString *)moduleName; 36 | 37 | @property (nonatomic,readonly,copy) NSString *url; 38 | @property (nonatomic,readonly,copy) NSString *page; 39 | 40 | 41 | /** 42 | RCTRootView 43 | */ 44 | @property (nonatomic,readonly) RCTRootView *rctRootView; 45 | 46 | 47 | /** 48 | 为 react native 注册 react 协议。 适用的URL形式为: 49 | react://localhost:8081/index.bundle?platform=ios&module=login 50 | 对于react 页面跳转, 要求在 AXEData或者URL中标明 module , 以加载具体页面 51 | 而实际的请求路径是将 react -> http , reacts -> https. 52 | */ 53 | + (void)registerReactProtocol; 54 | 55 | 56 | 57 | /** 58 | 注册 reacts 协议, 实际访问 https. 59 | */ 60 | + (void)registerReactsProtocol; 61 | 62 | 63 | /** 64 | 创建RCTRootView ,并加载页面 65 | */ 66 | - (void)loadReactWithBundleURL:(NSString *)urlStr moduleName:(NSString *)moduleName; 67 | 68 | @end 69 | 70 | /// 对于react 页面跳转, 要求在 AXEData或者URL中标明 pageName , 以正确展示页面。 key值为page 71 | extern NSString *const AXEReactPageNameKey; 72 | // 对于单页面应用, 默认的 AppRegistry中注册的 moduleName必须为 'App' 73 | extern NSString *const AXEDefaultModuleName; 74 | // react 75 | extern NSString *const AXEReactHttpProtocol; 76 | // reacts 77 | extern NSString *const AXEReactHttpsProtocol; 78 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEBasicTypeData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEBasicTypeData.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEBasicTypeData.h" 10 | 11 | @interface AXEBasicTypeData () 12 | @property (nonatomic,assign) AXEDataBasicType basicType; 13 | @end 14 | 15 | @implementation AXEBasicTypeData 16 | 17 | + (instancetype)basicDataWithNumber:(NSNumber *)number { 18 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:[number copy]]; 19 | data.basicType = AXEDataBasicTypeNumber; 20 | return data; 21 | } 22 | 23 | 24 | + (instancetype)basicDataWithString:(NSString *)string { 25 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:[string copy]]; 26 | data.basicType = AXEDataBasicTypeString; 27 | return data; 28 | } 29 | 30 | + (instancetype)basicDataWithArray:(NSArray *)array { 31 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:[array copy]]; 32 | data.basicType = AXEDataBasicTypeArray; 33 | return data; 34 | } 35 | 36 | + (instancetype)basicDataWithDictionary:(NSDictionary *)dictionary { 37 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:[dictionary copy]]; 38 | data.basicType = AXEDataBasicTypeDictionary; 39 | return data; 40 | } 41 | 42 | 43 | + (instancetype)basicDataWithImage:(UIImage *)image { 44 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:image]; 45 | data.basicType = AXEDataBasicTypeUIImage; 46 | return data; 47 | } 48 | 49 | 50 | + (instancetype)basicDataWithBoolean:(BOOL)boolean { 51 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:@(boolean)]; 52 | data.basicType = AXEDataBasicTypeBoolean; 53 | return data; 54 | } 55 | 56 | + (instancetype)basicDataWithData:(NSData *)data { 57 | AXEBasicTypeData *d = [AXEBasicTypeData dataWithValue:data]; 58 | d.basicType = AXEDataBasicTypeData; 59 | return d; 60 | } 61 | 62 | + (instancetype)basicDataWithDate:(NSDate *)date { 63 | AXEBasicTypeData *data = [AXEBasicTypeData dataWithValue:date]; 64 | data.basicType = AXEDataBasicTypeDate; 65 | return data; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEModuleInitializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEModuleInitializer.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEEvent.h" 11 | 12 | // 模块初始化程序 注册宏。 13 | #define AXEMODULEINITIALIZE_REGISTER()\ 14 | + (void)load{[AXEModuleInitializerManager registerModuleInitializer:[self class]];} 15 | 16 | /** 17 | 模块初始化程序 18 | */ 19 | @protocol AXEModuleInitializer 20 | /** 21 | 初始化时,使用 init函数构建实例。 22 | */ 23 | - (id)init; 24 | /** 25 | 模块将初始化功能放置在该方法中, 一般通过监听AXEEventModulesBeginInitializing ,来实现异步按序初始化所有模块。 26 | */ 27 | - (void)AEXInitialize; 28 | @end 29 | 30 | 31 | /** 32 | * 模块初始化程序。 33 | * 基于AXEEvent 提供便捷的模块初始化功能. 34 | * 35 | * 要使用该功能, 应该在ApplicationDidFinishLaunching时, 调用 36 | * [AXEModuleInitializerManager initializeModules]; 37 | * 38 | * 然后对于一个模块,可以这样编写初始化代码: 39 | 40 | @interface LoginModuleInitializer : NSObject 41 | @end 42 | @implementation LoginModuleInitializer 43 | AXEMODULEINITIALIZE_REGISTER() 44 | 45 | - (void)AEXInitialize { 46 | [AXEAutoreleaseEvent registerSyncListenerForEventName:AXEEventModulesBeginInitializing handler:^(AXEData *payload) { 47 | [[AXERouter sharedRouter] registerPath:@"ios/test" withViewRoute:^UIViewController *(AXERouteRequest *request) { 48 | return [[TestViewController alloc] init]; 49 | }]; 50 | } priority:AXEEventDefaultPriority]; 51 | } 52 | @end 53 | 54 | * 可以更加有效地管理模块的初始化, 通过优先级来控制初始化顺序,将不必须的模块延后和异步初始化。 55 | * 同时实现模块的自注册。 56 | */ 57 | @interface AXEModuleInitializerManager : NSObject 58 | 59 | 60 | /** 61 | 初始化所有模块 。 62 | 使用 AXEModuleInitializerManager 管理模块初始化时,将该函数在ApplicationDidFinishLaunching 时执行。 63 | 该函数会发送 AXEEventModulesBeginInitializing 通知,以替代 ApplicationDidFinishLaunching 帮助组件初始化。 64 | */ 65 | + (void)initializeModules; 66 | 67 | /** 68 | 注册 模块初始器。 69 | 70 | @param initializer 模块初始器。 类需要实现 AXEModuleInitializer 71 | */ 72 | + (void)registerModuleInitializer:(Class)initializer; 73 | 74 | @end 75 | 76 | 77 | 78 | /** 79 | 组件初始化开始通知。 80 | */ 81 | extern NSString *const AXEEventModulesBeginInitializing; 82 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouteRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouteRequest.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/9. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXERouter.h" 11 | 12 | /** 13 | 路由的一次请求信息。 14 | */ 15 | @interface AXERouteRequest : NSObject 16 | 17 | /** 18 | 是否合法,默认是YES. 19 | 可以在 AXERouterPreprocessBlock 中设置,即提供外界拒绝请求的能力。 20 | */ 21 | @property (nonatomic,assign) BOOL valid; 22 | 23 | 24 | /** 25 | 用于重定向, 可以 AXERouterPreprocessBlock 修改URL,以使一个地址指向另外一个路径。 26 | 重定向时,不应该添加参数。修改参数建议直接获取 params 进行设置。 27 | 初始化时, 值为传入的 sourceURL 28 | */ 29 | @property (nonatomic,copy) NSString *currentURL; 30 | 31 | /** 32 | 所在协议 33 | */ 34 | @property (nonatomic,readonly,strong) NSString *protocol; 35 | 36 | /** 37 | 源URL,最初传入的参数URL 38 | */ 39 | @property (nonatomic,readonly,copy) NSString *sourceURL; 40 | 41 | 42 | /** 43 | 参数 44 | */ 45 | @property (nonatomic,readonly,strong) AXEData *params; 46 | 47 | 48 | /** 49 | 回调 50 | */ 51 | @property (nonatomic,copy) AXERouteCallbackBlock callback; 52 | 53 | /** 54 | 所在页面. 55 | */ 56 | @property (nonatomic,readonly,weak) UIViewController *fromVC; 57 | 58 | + (instancetype)requestWithSourceURL:(NSString *)sourceURL 59 | params:(AXEData *)params 60 | callback:(AXERouteCallbackBlock)callback 61 | fromVC:(UIViewController *)fromVC; 62 | 63 | // 以下三个属性,需要路由自己解析并设置。 对于模块化的路由协议,会进行解析。 但是对于URL类型的协议,如http 或者https, 就无法解析的到模块信息了。 64 | // 模块化的路由,要满足我们的要求 : protocol://module/page , 我们按照这种固定形式去解析。 65 | /** 66 | 模块名 , axe://login/register 的module为 login 67 | */ 68 | @property (nonatomic,strong) NSString *module; 69 | 70 | /** 71 | 页面名, axe://login/register 的page为 register 72 | */ 73 | @property (nonatomic,strong) NSString *page; 74 | 75 | /** 76 | 页面路径. 如 axe://login/register , path为 login/register 77 | */ 78 | @property (nonatomic,strong) NSString *path; 79 | 80 | 81 | @end 82 | 83 | // 对请求信息做一些处理或者重定向。 84 | typedef void (^AXERoutePreprocessBlock)(AXERouteRequest *request); 85 | 86 | 87 | 88 | @interface AXERouter(Preprocess) 89 | 90 | 91 | /** 92 | 添加预处理, 以修改请求内容。 或者添加一些检测记录的代码 ,可用于重定向 93 | @param block 预处理。 94 | */ 95 | - (void)addPreprocess:(AXERoutePreprocessBlock)block; 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Axe.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Axe" 3 | s.version = "0.1.0" 4 | s.summary = "Axe is all the reinforcement this army needs" 5 | s.homepage = "https://github.com/axe-org/axe" 6 | s.license = { :type => "MIT"} 7 | s.author = { "luoxianming" => "luoxianmingg@gmail.com" } 8 | s.ios.deployment_target = '8.0' 9 | s.source = { :git => "https://github.com/axe-org/axe.git", :tag => s.version} 10 | s.subspec "Core" do |ss| 11 | ss.source_files = "Axe/Axe/Axe.h" 12 | ss.subspec "Router" do |sss| 13 | sss.source_files = "Axe/Axe/Router/*.{h,m}","Axe/Axe/AXEDefines.h" 14 | end 15 | ss.subspec "Event" do |sss| 16 | sss.source_files = "Axe/Axe/Event/*.{h,m}","Axe/Axe/AXEDefines.h" 17 | end 18 | ss.subspec "Data" do |sss| 19 | sss.source_files = "Axe/Axe/Data/*.{h,m}","Axe/Axe/AXEDefines.h" 20 | end 21 | end 22 | s.subspec "TabBarController" do |ss| 23 | ss.dependency "Axe/Core" 24 | ss.source_files = "Axe/Extension/TabBarController/*.{h,m}" 25 | end 26 | s.subspec "Html" do |ss| 27 | ss.dependency "Axe/Util" 28 | ss.dependency "Axe/JavascriptSupport" 29 | ss.dependency "WebViewJavascriptBridge" 30 | ss.source_files = "Axe/Extension/Html/*.{h,m}" 31 | end 32 | s.subspec "JavascriptSupport" do |ss| 33 | ss.dependency "Axe/Core" 34 | ss.source_files = "Axe/Extension/JavascriptSupport/*.{h,m}" 35 | end 36 | s.subspec "React" do |ss| 37 | ss.dependency "Axe/Util" 38 | ss.dependency "Axe/JavascriptSupport" 39 | ss.dependency "MXReact/CxxBridge" 40 | ss.source_files = "Axe/Extension/React/*.{h,m}" 41 | end 42 | s.subspec "OfflineHtml" do |ss| 43 | ss.dependency "Axe/Html" 44 | ss.dependency "OfflinePackage" 45 | ss.source_files = "Axe/Extension/OfflineHtml/*.{h,m}" 46 | end 47 | s.subspec "OfflineReact" do |ss| 48 | ss.dependency "Axe/React" 49 | ss.dependency "OfflinePackage" 50 | ss.source_files = "Axe/Extension/OfflineReact/*.{h,m}" 51 | end 52 | s.subspec "Util" do |ss| 53 | ss.dependency "Axe/Core" 54 | ss.source_files = "Axe/Extension/Util/*.{h,m}" 55 | end 56 | s.subspec "DynamicRouter" do |ss| 57 | ss.dependency "Axe/Core" 58 | ss.source_files = "Axe/Extension/DynamicRouter/*.{h,m}" 59 | end 60 | end -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEventUserInterfaceState.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEventUserInterfaceStatus.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEEventUserInterfaceState.h" 10 | #import "AXEEvent.h" 11 | #import "AXEEventUserInterfaceState+Event.h" 12 | #import "AXEDefines.h" 13 | 14 | 15 | /** 16 | 本地存储的事件回调。 17 | */ 18 | @interface AXEEventStoredEventHandler : NSObject 19 | 20 | @property (nonatomic,copy) NSString *eventName; 21 | @property (nonatomic,copy) dispatch_block_t handler; 22 | @end 23 | @implementation AXEEventStoredEventHandler 24 | @end 25 | 26 | @interface AXEEventUserInterfaceStatus() 27 | @property (nonatomic,strong) NSMutableArray *storedBlocks; 28 | @end 29 | 30 | @implementation AXEEventUserInterfaceStatus 31 | 32 | + (instancetype)status { 33 | return [[AXEEventUserInterfaceStatus alloc] init]; 34 | } 35 | 36 | - (void)containerWillDisappear { 37 | @synchronized(self) { 38 | _inFront = NO; 39 | } 40 | } 41 | 42 | 43 | - (void)containerDidAppear { 44 | @synchronized(self) { 45 | _inFront = YES; 46 | if (_storedBlocks.count) { 47 | // 如果当前有存储任务,则执行。 48 | NSArray *storedBlocks = _storedBlocks; 49 | _storedBlocks = [[NSMutableArray alloc] init]; 50 | dispatch_async(dispatch_get_main_queue(), ^{ 51 | [storedBlocks enumerateObjectsUsingBlock:^(AXEEventStoredEventHandler * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 52 | AXELogTrace(@"页面回到前台,发送事件 %@",obj.eventName); 53 | obj.handler(); 54 | }]; 55 | }); 56 | } 57 | } 58 | } 59 | 60 | - (instancetype)init { 61 | if (self = [super init]) { 62 | _inFront = YES; 63 | } 64 | return self; 65 | } 66 | 67 | - (void)storeEventName:(NSString *)name handlerBlock:(dispatch_block_t)block { 68 | // 既要保持事件发送顺序,又要覆盖同名事件。 69 | @synchronized(self) { 70 | if (!_storedBlocks) { 71 | _storedBlocks = [[NSMutableArray alloc] init]; 72 | } 73 | for (AXEEventStoredEventHandler *item in _storedBlocks) { 74 | if ([item.eventName isEqualToString:name]) { 75 | [_storedBlocks removeObject:item]; 76 | break; 77 | } 78 | } 79 | AXEEventStoredEventHandler *stored = [[AXEEventStoredEventHandler alloc] init]; 80 | stored.eventName = name; 81 | stored.handler = block; 82 | [_storedBlocks addObject:stored]; 83 | } 84 | } 85 | 86 | - (void)cleanStoredEvents { 87 | @synchronized(self) { 88 | _storedBlocks = nil; 89 | } 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactNavigationModule.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactNavigationModule.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEReactNavigationModule.h" 10 | #import 11 | #import "AXEReactViewController.h" 12 | #import "AXEReactControllerWrapper.h" 13 | #import 14 | #import "AXEDefines.h" 15 | 16 | @interface AXEReactNavigationModule() 17 | @property (nonatomic,strong) AXEReactControllerWrapper *wrapper; 18 | @end 19 | 20 | 21 | @implementation AXEReactNavigationModule 22 | RCT_EXPORT_MODULE(axe_navigation); 23 | @synthesize bridge = _bridge; 24 | - (AXEReactControllerWrapper *)wrapper{ 25 | if (!_wrapper) { 26 | _wrapper = [self.bridge.launchOptions objectForKey:AXEReactControllerWrapperKey]; 27 | } 28 | return _wrapper; 29 | } 30 | 31 | + (BOOL)requiresMainQueueSetup { 32 | return YES; 33 | } 34 | 35 | - (dispatch_queue_t)methodQueue { 36 | return dispatch_get_main_queue(); 37 | } 38 | 39 | // 打开页面。 提供这个工具模块的最主要原因,就是为了这个页面跳转 . 40 | // 需要明确 : 模块间内页面跳转 不能使用 `axe.router` 。 41 | // axe.router 是模块间的交互,模块间的跳转。 42 | RCT_EXPORT_METHOD(push:(NSString *)module){ 43 | AXEReactViewController *current = self.wrapper.controller; 44 | // 跳转时使用 AXEReactViewController , 同模块跳转时,不会检测更新离线包。 45 | AXEReactViewController *vc = [AXEReactViewController controllerWithURL:current.url 46 | moduleName:module]; 47 | // 默认将调用信息也传递给下一个页面。 48 | vc.routeRequest = current.routeRequest; 49 | [current.navigationController pushViewController:vc animated:YES]; 50 | } 51 | 52 | // 重定向,指跳转到新页面后,从历史中将当前页面删除。 53 | RCT_EXPORT_METHOD(redirect:(NSString *)module){ 54 | AXEReactViewController *current = self.wrapper.controller; 55 | AXEReactViewController *vc = [AXEReactViewController controllerWithURL:current.url 56 | moduleName:module]; 57 | // 默认将调用信息也传递给下一个页面。 58 | vc.routeRequest = current.routeRequest; 59 | vc.didAppearBlock = ^{ 60 | NSMutableArray *controllers = [current.navigationController.viewControllers mutableCopy]; 61 | [controllers removeObjectAtIndex:controllers.count - 2]; 62 | [current.navigationController setViewControllers:[controllers copy]]; 63 | }; 64 | [current.navigationController pushViewController:vc animated:YES]; 65 | } 66 | 67 | // 关闭页面 68 | RCT_EXPORT_METHOD(closePage){ 69 | [self.wrapper.controller.navigationController popViewControllerAnimated:YES]; 70 | } 71 | 72 | // 设置标题。 73 | RCT_EXPORT_METHOD(setTitle:(NSString *)title){ 74 | NSParameterAssert([title isKindOfClass:[NSString class]]); 75 | self.wrapper.controller.navigationItem.title = title; 76 | } 77 | 78 | @end 79 | 80 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEData.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AXEBaseData.h" 11 | @class UIImage; 12 | 13 | /** 14 | Data : 数据模块, 解决跨模块数据传递的问题。 15 | Data有两种用法 : 16 | 1. 共享数据, 设置一个单例来存储多模块共享的数据。 17 | 2. 数据传输, 可以在 Event上附带数据,也可以在 Router的跳转和回调上附带数据。 18 | 19 | Data 目前支持以下数据类型 : 20 | 21 | 1. NSNumber 22 | 2. NSString 23 | 3. NSArray : 需要说明, 如果要做到跨语言共享,则不能包含非基础类型 (基础类型为 NSNumber,NSString,NSArray,NSDictionary) 24 | 4. NSDictionary : 需要说明, 如果要做到跨语言共享,则不能包含非基础类型 25 | 26 | 5. UIImage: 最常见也是最需要的 非常规对象 27 | 6. NSData 28 | 7. NSDate 29 | 8. BOOL 30 | 31 | 9。 Model: 在Axe中提及的 Model类型,指可以进行序列化,且一般由后台获取的Model类型。 32 | Model类型的要求 : 如果是要做到跨语言共享,即与JS模块交互,则要求这类Model只能支持 基础类型。 如果只是原生模块共享,则无这个限制。 33 | Model类型的放置 : 要视该 Model的管理模块的数量,如果一个Model只由一个模块创建,则应该归该模块所有。 如果这个Model可以由多个模块创建,或者所有模块都依赖(如用户信息相关的Model), 则应该放到 公共业务部分。 34 | */ 35 | @interface AXEData : NSObject 36 | 37 | /** 38 | 公共数据。 单例。 39 | */ 40 | + (instancetype)sharedData; 41 | 42 | 43 | /** 44 | 创建用于传输数据的data实例 。工厂方法 45 | 46 | @return 传输数据, 用于 事件和路由时传递数据。 47 | */ 48 | + (instancetype)dataForTransmission; 49 | 50 | /** 51 | 设置数据时, 不需要指定数据类型。 52 | 53 | @param data 需要存储的数据 , 如果是 model类型,就必须要满足 AXEDataModelProtocol 54 | @param key 键值 55 | */ 56 | - (void)setData:(id)data forKey:(NSString *)key; 57 | 58 | 59 | /** 60 | 提供一个特殊的方法,用于设置 bool 值。 61 | 62 | @param boo BOOL 63 | @param key 键值。 64 | */ 65 | - (void)setBool:(BOOL)boo forKey:(NSString *)key; 66 | 67 | /** 68 | 删除共享数据 69 | 70 | @param key 键值。 71 | */ 72 | - (void)removeDataForKey:(NSString *)key; 73 | 74 | /** 75 | 获取数据时, 可以不指定数据类型。但是需要判断数据类型, 以避免出错。 76 | @param key 键值 77 | @return 如果当前有数据,则返回。一定要判断数据类型,避免处理出错。 78 | */ 79 | - (AXEBaseData *)dataForKey:(NSString *)key; 80 | 81 | /** 82 | 根据键值, 获取一个 NSNumber 83 | */ 84 | - (NSNumber *)numberForKey:(NSString *)key; 85 | /** 86 | 根据键值, 获取一个 NSString 87 | */ 88 | - (NSString *)stringForKey:(NSString *)key; 89 | /** 90 | 根据键值, 获取一个 NSArray 91 | */ 92 | - (NSArray *)arrayForKey:(NSString *)key; 93 | /** 94 | 根据键值, 获取一个 NSDictionary 95 | */ 96 | - (NSDictionary *)dictionaryForKey:(NSString *)key; 97 | 98 | /** 99 | 根据键值 ,获取一个 UIImage. 100 | */ 101 | - (UIImage *)imageForKey:(NSString *)key; 102 | 103 | /** 104 | 根据键值 ,获取一个 NSData. 105 | */ 106 | - (NSData *)NSDataForKey:(NSString *)key; 107 | 108 | /** 109 | 根据键值 ,获取一个 NSDate. 110 | */ 111 | - (NSDate *)dateForKey:(NSString *)key; 112 | 113 | /** 114 | 根据键值 ,获取一个 bool值. 115 | 如果key 不存在,则返回 false. 116 | */ 117 | - (BOOL)boolForKey:(NSString *)key; 118 | 119 | /** 120 | 根据键值, 获取一个 Model类型。 121 | */ 122 | - (id)modelForKey:(NSString *)key; 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEAutoreleaseEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEAutoreleaseEvent.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/8. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEAutoreleaseEvent.h" 10 | 11 | @implementation AXEAutoreleaseEvent 12 | 13 | + (id)registerListenerForEventName:(NSString *)name 14 | handler:(AXEEventHandlerBlock)handler { 15 | return [self registerSyncListenerForEventName:name 16 | handler:handler 17 | priority:AXEEventDefaultPriority]; 18 | } 19 | 20 | 21 | + (id)registerSyncListenerForEventName:(NSString *)name 22 | handler:(AXEEventHandlerBlock)handler 23 | priority:(NSInteger)priority { 24 | id disposable; 25 | AXEEventHandlerBlock internalHandler = [handler copy]; 26 | disposable = [AXEEvent registerSyncListenerForEventName:name handler:^(AXEData *info) { 27 | internalHandler(info); 28 | [disposable dispose]; 29 | } priority:priority]; 30 | return disposable; 31 | } 32 | 33 | 34 | + (id)registerSerialListenerForEventName:(NSString *)name 35 | handler:(AXEEventHandlerBlock)handler 36 | priority:(NSInteger)priority { 37 | id disposable; 38 | AXEEventHandlerBlock internalHandler = [handler copy]; 39 | disposable = [AXEEvent registerSerialListenerForEventName:name handler:^(AXEData *payload) { 40 | internalHandler(payload); 41 | [disposable dispose]; 42 | } priority:priority]; 43 | return disposable; 44 | } 45 | 46 | 47 | + (id)registerConcurrentListenerForEventName:(NSString *)name 48 | handler:(AXEEventHandlerBlock)handler { 49 | id disposable; 50 | AXEEventHandlerBlock internalHandler = [handler copy]; 51 | disposable = [AXEEvent registerConcurrentListenerForEventName:name handler:^(AXEData *payload) { 52 | internalHandler(payload); 53 | [disposable dispose]; 54 | }]; 55 | return disposable; 56 | } 57 | 58 | 59 | + (id)registerUIListenerForEventName:(NSString *)name 60 | handler:(AXEEventHandlerBlock)handler 61 | priority:(NSInteger)priority 62 | inUIContainer:(id)container { 63 | id disposable; 64 | AXEEventHandlerBlock internalHandler = [handler copy]; 65 | disposable = [AXEEvent registerUIListenerForEventName:name handler:^(AXEData *info) { 66 | internalHandler(info); 67 | [disposable dispose]; 68 | } priority:priority inUIContainer:container]; 69 | return disposable; 70 | } 71 | 72 | /** 73 | 注册监听,使用默认优先级。 74 | */ 75 | + (id)registerUIListenerForEventName:(NSString *)name 76 | handler:(AXEEventHandlerBlock)handler 77 | inUIContainer:(id)container { 78 | return [self registerUIListenerForEventName:name 79 | handler:handler 80 | priority:AXEEventDefaultPriority 81 | inUIContainer:container]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouter.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "AXEData.h" 12 | 13 | @class AXERouteRequest; 14 | // 回调block 15 | typedef void (^AXERouteCallbackBlock)(AXEData *payload); 16 | // 跳转路由 17 | typedef void (^AXEJumpRouteHandler)(AXERouteRequest *request); 18 | // 视图路由 19 | typedef UIViewController *(^AXEViewRouteHandler)(AXERouteRequest *request); 20 | // 协议注册的跳转路由block 21 | typedef void (^AXEProtoclJumpRouterBlock)(AXERouteRequest *request); 22 | // 协议注册的视图路由block 23 | typedef UIViewController *(^AXEProtoclViewRouterBlock)(AXERouteRequest *request); 24 | 25 | 26 | /** 27 | 路由, 负责根据URL实现页面跳转。 28 | 路由模块的URL标准格式是 protocl://moduleName/PageName?params... 29 | 30 | 路由根据表现形式,分为两种 : 31 | 32 | * jump route : 跳转路由 , 即 跳转到目标页面, 一般都是在 NavigationController下的push操作。 33 | * view route : 视图路由 , 即 返回一个目标页面, 暂定为 ViewController 返回值。 一般用于一些特殊页面的展示,如TabBarController, tab页、侧边栏等等。 34 | 35 | 路由根据协议分为两种, 一种是默认协议,即原生界面的路由, 使用默认协议 axe 36 | 37 | 一种是其他协议, 通过注册协议路由实现。 在扩展中,我们的协议路由有 38 | 39 | * axes : 声明路由 40 | * react/reacts : 线上react-native 41 | * http/https: 线上h5 42 | * offline: 离线包 h5 43 | * oprn: 离线包 rn 44 | 45 | */ 46 | @interface AXERouter : NSObject 47 | 48 | /** 49 | 单例 50 | 51 | @return 实例 52 | */ 53 | + (instancetype)sharedRouter; 54 | 55 | 56 | #pragma mark - route 57 | 58 | /** 59 | 跳转 60 | 路由方法, 都应该在主线程中调用。 61 | @param url url 62 | @param fromVC 当前页面 ,跳转路由必须要指定当前所在的ViewController。 63 | @param params 传递参数 64 | @param block 回调。 65 | */ 66 | - (void)jumpTo:(NSString *)url fromViewController:(UIViewController *)fromVC withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block; 67 | 68 | 69 | /** 70 | 跳转 71 | 72 | @param url URL 73 | @param fromVC 当前页面 74 | */ 75 | - (void)jumpTo:(NSString *)url fromViewController:(UIViewController *)fromVC; 76 | 77 | // 视图路由与跳转路由 在跳转逻辑上有一些区别 : 78 | // 跳转路由是 模块内部实现跳转, 所以 模块知道自己是从何处开始, 所以结束时要自己处理 页面的关闭。 79 | // 视图路由 是 由外部控制的界面展示, 所以 模块不知道自己是以何种形式弹出的, 不知道自己是在栈里 还是model形式push , 甚至可能是直接添加到window上的。 所以页面的关闭,应该由调用者来实现,即一般情况下, 由调用者在回调函数中,设置页面的关闭操作。 80 | 81 | /** 82 | 视图路由 83 | 84 | @param url URL 85 | @return 返回 UIViewController, 需要注意 , 如果路由没有注册,这里返回空值时, 要考虑崩溃处理问题。 86 | */ 87 | - (UIViewController *)viewForURL:(NSString *)url; 88 | 89 | 90 | /** 91 | 视图路由 92 | 93 | @param url 路由URL 94 | @param params 参数 95 | @param block 回调 96 | @return 返回 UIViewController. 97 | */ 98 | - (UIViewController *)viewForURL:(NSString *)url withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block; 99 | 100 | #pragma mark - register 101 | 102 | /** 103 | 注册 原生页面的 跳转路由 在默认的 axe协议下 104 | 105 | @param path 路径,这里注册的pagePath,要求为 module/page 的形式 , 则完整的URL为 axe://moduleName/pageName 106 | @param handler 路由处理 107 | */ 108 | - (void)registerPath:(NSString *)path withJumpRoute:(AXEJumpRouteHandler)handler; 109 | 110 | /** 111 | 注册 原生页面的 视图路由, 在axe协议下。 112 | 113 | @param path 页面路径 114 | @param handler 路由处理, 返回一个 UIViewController 115 | */ 116 | - (void)registerPath:(NSString *)path withViewRoute:(AXEViewRouteHandler)handler; 117 | 118 | /** 119 | 注册 基于协议的 跳转路由 120 | 121 | @param protocol 协议名称。 122 | @param handler 路由处理 123 | */ 124 | - (void)registerProtocol:(NSString *)protocol withJumpRoute:(AXEProtoclJumpRouterBlock)handler; 125 | 126 | 127 | /** 128 | 注册 基于协议的 视图路由 129 | 130 | @param protocol 协议名称 131 | @param handler 返回UIViewController的路由处理。 132 | */ 133 | - (void)registerProtocol:(NSString *)protocol withViewRoute:(AXEProtoclViewRouterBlock)handler; 134 | 135 | @end 136 | 137 | // 默认protocol名称 , 由于不是 const ,所以这个协议名称是可以修改的。 138 | extern NSString *AXERouterProtocolName; 139 | 140 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactEventModule.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactEventModule.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEReactEventModule.h" 10 | #import 11 | #import "AXEData+JavaScriptSupport.h" 12 | #import "AXEData+JavaScriptSupport.h" 13 | #import "AXEReactViewController.h" 14 | #import "AXEReactControllerWrapper.h" 15 | #import 16 | #import "AXEDefines.h" 17 | #import "AXEEvent.h" 18 | 19 | @interface AXEReactEventModule() 20 | 21 | @property (nonatomic,strong) AXEReactControllerWrapper *wrapper; 22 | @property (nonatomic,strong) NSMutableDictionary *registeredEvents; 23 | @end 24 | 25 | @implementation AXEReactEventModule 26 | RCT_EXPORT_MODULE(axe_event); 27 | @synthesize bridge = _bridge; 28 | 29 | - (AXEReactControllerWrapper *)wrapper{ 30 | if (!_wrapper) { 31 | _wrapper = [self.bridge.launchOptions objectForKey:AXEReactControllerWrapperKey]; 32 | } 33 | return _wrapper; 34 | } 35 | 36 | - (NSMutableDictionary *)registeredEvents { 37 | if (!_registeredEvents) { 38 | _registeredEvents = [[NSMutableDictionary alloc] initWithCapacity:10]; 39 | } 40 | return _registeredEvents; 41 | } 42 | 43 | // 注册监听。 44 | RCT_EXPORT_METHOD(registerListener:(NSString *)eventName){ 45 | if ([eventName isKindOfClass:[NSString class]]) { 46 | if ([self.registeredEvents objectForKey:eventName]) { 47 | AXELogWarn(@"重复监听 !!!"); 48 | id disposable = [self.registeredEvents objectForKey:eventName]; 49 | [disposable dispose]; 50 | } 51 | @weakify(self); 52 | id disposable = [self.wrapper.controller registerUIEvent:eventName withHandler:^(AXEData *payload) { 53 | @strongify(self); 54 | if (!self) { 55 | AXELogWarn(@"当前页面已释放, 通知还存在,请检测是否存在内存问题!!!"); 56 | return; 57 | } 58 | NSMutableDictionary *post = [[NSMutableDictionary alloc] init]; 59 | [post setObject:eventName forKey:@"name"]; 60 | if (payload) { 61 | // 如果有附带数据,则进行转换。 62 | NSDictionary *javascriptData = [AXEData javascriptDataFromAXEData:payload]; 63 | if ([javascriptData isKindOfClass:[NSDictionary class]]) { 64 | [post setObject:javascriptData forKey:@"payload"]; 65 | } 66 | } 67 | [self.bridge enqueueJSCall:@"axe_event" method:@"callback" args:@[post] completion:nil]; 68 | }]; 69 | [self.registeredEvents setObject:disposable forKey:eventName]; 70 | } else { 71 | AXELogWarn(@"eventName 需要为 NSString 类型!"); 72 | } 73 | } 74 | 75 | 76 | 77 | RCT_EXPORT_METHOD(removeListener:(NSString *)eventName){ 78 | if ([eventName isKindOfClass:[NSString class]]) { 79 | id disposable = self.registeredEvents[eventName]; 80 | if (disposable) { 81 | [self.registeredEvents removeObjectForKey:eventName]; 82 | [disposable dispose]; 83 | } 84 | } else { 85 | AXELogWarn(@"eventName 需要为 NSString 类型!"); 86 | } 87 | 88 | } 89 | 90 | RCT_EXPORT_METHOD(postEvent:(NSDictionary *)event){ 91 | if ([event isKindOfClass:[NSDictionary class]]) { 92 | NSDictionary *payload = [event objectForKey:@"data"]; 93 | AXEData *payloadData; 94 | if (payload) { 95 | payloadData = [AXEData axeDataFromJavascriptData:payload]; 96 | } 97 | [AXEEvent postEventName:[event objectForKey:@"name"] withPayload:payloadData]; 98 | } else { 99 | AXELogWarn(@"event 需要为 NSDictionary 类型!"); 100 | } 101 | 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactRouterModule.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactRouterModule.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEReactRouterModule.h" 10 | #import 11 | #import "AXEData+JavaScriptSupport.h" 12 | #import "AXEReactViewController.h" 13 | #import "AXEReactControllerWrapper.h" 14 | #import 15 | #import "AXEDefines.h" 16 | 17 | @interface AXEReactRouterModule() 18 | 19 | @property (nonatomic,strong) AXEReactControllerWrapper *wrapper; 20 | 21 | @end 22 | 23 | @implementation AXEReactRouterModule 24 | RCT_EXPORT_MODULE(axe_router); 25 | @synthesize bridge = _bridge; 26 | 27 | 28 | - (AXEReactControllerWrapper *)wrapper{ 29 | if (!_wrapper) { 30 | _wrapper = [self.bridge.launchOptions objectForKey:AXEReactControllerWrapperKey]; 31 | } 32 | return _wrapper; 33 | } 34 | 35 | + (BOOL)requiresMainQueueSetup { 36 | return YES; 37 | } 38 | 39 | - (dispatch_queue_t)methodQueue { 40 | return dispatch_get_main_queue(); 41 | } 42 | 43 | 44 | RCT_EXPORT_METHOD(callback:(NSDictionary *)param){ 45 | // 先要检测本地是否有回调。 46 | if (self.wrapper.controller.routeRequest.callback) { 47 | // 有回调才能触发。 48 | AXEData *data; 49 | if ([param isKindOfClass:[NSDictionary class]]) { 50 | data = [AXEData axeDataFromJavascriptData:param]; 51 | } 52 | self.wrapper.controller.routeRequest.callback(data); 53 | }else { 54 | AXELogWarn(@"当前 页面并没有设置路由回调!!!"); 55 | } 56 | } 57 | 58 | 59 | RCT_EXPORT_METHOD(route:(NSDictionary *)param callback:(RCTResponseSenderBlock)callback) { 60 | if ([param isKindOfClass:[NSDictionary class]]) { 61 | if (!self.wrapper.controller) { 62 | return; 63 | } 64 | NSString *url = [param objectForKey:@"url"]; 65 | NSDictionary *jsdata = [param objectForKey:@"param"]; 66 | AXEData *payload; 67 | if (jsdata) { 68 | payload = [AXEData axeDataFromJavascriptData:jsdata]; 69 | } 70 | // 有回调。 71 | AXERouteCallbackBlock routerCallback = ^(AXEData *returnData) { 72 | NSArray *response; 73 | if (returnData) { 74 | response = @[[AXEData javascriptDataFromAXEData:returnData]]; 75 | } 76 | callback(response); 77 | }; 78 | [self.wrapper.controller jumpTo:url withParams:payload finishBlock:routerCallback]; 79 | } else { 80 | AXELogWarn(@"param 需要为 NSDictionary 类型!"); 81 | } 82 | } 83 | 84 | RCT_EXPORT_METHOD(routeWithoutCallback:(NSDictionary *)param) { 85 | if ([param isKindOfClass:[NSDictionary class]]) { 86 | if (!self.wrapper.controller) { 87 | return; 88 | } 89 | NSString *url = [param objectForKey:@"url"]; 90 | NSDictionary *jsdata = [param objectForKey:@"param"]; 91 | AXEData *payload; 92 | if (jsdata) { 93 | payload = [AXEData axeDataFromJavascriptData:jsdata]; 94 | } 95 | [[AXERouter sharedRouter] jumpTo:url fromViewController:self.wrapper.controller withParams:payload finishBlock:nil]; 96 | } else { 97 | AXELogWarn(@"param 需要为 NSDictionary 类型!"); 98 | } 99 | } 100 | 101 | // 将路由信息使用常量进行暴露。 102 | - (NSDictionary *)constantsToExport { 103 | NSMutableDictionary *sourceRouteInfo = [[NSMutableDictionary alloc] init]; 104 | [sourceRouteInfo setObject:self.wrapper.controller.routeRequest.callback ? @"true" : @"false" forKey:@"needCallback"]; 105 | if (self.wrapper.controller.routeRequest.params) { 106 | [sourceRouteInfo setObject:[AXEData javascriptDataFromAXEData:self.wrapper.controller.routeRequest.params] forKey:@"payload"]; 107 | } 108 | return @{@"lastRouteInfo" : sourceRouteInfo}; 109 | } 110 | 111 | 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEXTScope.h: -------------------------------------------------------------------------------- 1 | // 2 | // EXTScope.h 3 | // extobjc 4 | // 5 | // Created by Justin Spahr-Summers on 2011-05-04. 6 | // Copyright (C) 2012 Justin Spahr-Summers. 7 | // Released under the MIT license. 8 | // 9 | 10 | #import "AXEmetamacros.h" 11 | 12 | /** 13 | * \@onExit defines some code to be executed when the current scope exits. The 14 | * code must be enclosed in braces and terminated with a semicolon, and will be 15 | * executed regardless of how the scope is exited, including from exceptions, 16 | * \c goto, \c return, \c break, and \c continue. 17 | * 18 | * Provided code will go into a block to be executed later. Keep this in mind as 19 | * it pertains to memory management, restrictions on assignment, etc. Because 20 | * the code is used within a block, \c return is a legal (though perhaps 21 | * confusing) way to exit the cleanup block early. 22 | * 23 | * Multiple \@onExit statements in the same scope are executed in reverse 24 | * lexical order. This helps when pairing resource acquisition with \@onExit 25 | * statements, as it guarantees teardown in the opposite order of acquisition. 26 | * 27 | * @note This statement cannot be used within scopes defined without braces 28 | * (like a one line \c if). In practice, this is not an issue, since \@onExit is 29 | * a useless construct in such a case anyways. 30 | */ 31 | #define onExit \ 32 | rac_keywordify \ 33 | __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^ 34 | 35 | /** 36 | * Creates \c __weak shadow variables for each of the variables provided as 37 | * arguments, which can later be made strong again with #strongify. 38 | * 39 | * This is typically used to weakly reference variables in a block, but then 40 | * ensure that the variables stay alive during the actual execution of the block 41 | * (if they were live upon entry). 42 | * 43 | * See #strongify for an example of usage. 44 | */ 45 | #define weakify(...) \ 46 | rac_keywordify \ 47 | metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) 48 | 49 | /** 50 | * Like #weakify, but uses \c __unsafe_unretained instead, for targets or 51 | * classes that do not support weak references. 52 | */ 53 | #define unsafeify(...) \ 54 | rac_keywordify \ 55 | metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__) 56 | 57 | /** 58 | * Strongly references each of the variables provided as arguments, which must 59 | * have previously been passed to #weakify. 60 | * 61 | * The strong references created will shadow the original variable names, such 62 | * that the original names can be used without issue (and a significantly 63 | * reduced risk of retain cycles) in the current scope. 64 | * 65 | * @code 66 | 67 | id foo = [[NSObject alloc] init]; 68 | id bar = [[NSObject alloc] init]; 69 | 70 | @weakify(foo, bar); 71 | 72 | // this block will not keep 'foo' or 'bar' alive 73 | BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){ 74 | // but now, upon entry, 'foo' and 'bar' will stay alive until the block has 75 | // finished executing 76 | @strongify(foo, bar); 77 | 78 | return [foo isEqual:obj] || [bar isEqual:obj]; 79 | }; 80 | 81 | * @endcode 82 | */ 83 | #define strongify(...) \ 84 | rac_keywordify \ 85 | _Pragma("clang diagnostic push") \ 86 | _Pragma("clang diagnostic ignored \"-Wshadow\"") \ 87 | metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ 88 | _Pragma("clang diagnostic pop") 89 | 90 | /*** implementation details follow ***/ 91 | typedef void (^rac_cleanupBlock_t)(void); 92 | 93 | static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) { 94 | (*block)(); 95 | } 96 | 97 | #define rac_weakify_(INDEX, CONTEXT, VAR) \ 98 | CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR); 99 | 100 | #define rac_strongify_(INDEX, VAR) \ 101 | __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_); 102 | 103 | // Details about the choice of backing keyword: 104 | // 105 | // The use of @try/@catch/@finally can cause the compiler to suppress 106 | // return-type warnings. 107 | // The use of @autoreleasepool {} is not optimized away by the compiler, 108 | // resulting in superfluous creation of autorelease pools. 109 | // 110 | // Since neither option is perfect, and with no other alternatives, the 111 | // compromise is to use @autorelease in DEBUG builds to maintain compiler 112 | // analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary 113 | // autorelease pools. 114 | #if DEBUG 115 | #define rac_keywordify autoreleasepool {} 116 | #else 117 | #define rac_keywordify try {} @catch (...) {} 118 | #endif 119 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEvent.h 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "AXEData.h" 12 | #import "AXEEXTScope.h" 13 | 14 | /** 15 | 事件回调block。 16 | */ 17 | typedef void(^AXEEventHandlerBlock)(AXEData *payload); 18 | 19 | // 默认事件优先级 ,为1 。 事件支持优先级, 越高就能越快处理。 20 | extern NSInteger const AXEEventDefaultPriority; 21 | 22 | 23 | 24 | /** 25 | 负责监听的释放。 26 | */ 27 | @protocol AXEListenerDisposable 28 | 29 | 30 | /** 31 | 释放监听。 32 | */ 33 | - (void)dispose; 34 | 35 | @end 36 | 37 | 38 | @protocol AXEEventUserInterfaceContainer; 39 | /** 40 | * 事件管理, 简化通知机制,支持block ,同时提供同步、异步、以及优先级控制等。 41 | * 使用block的监听,最重要的是要注意 对象的持有问题。 对于持久监听,建议使用 @weakify 和 @strongify宏。 42 | * @code 43 | 44 | id foo = [[NSObject alloc] init]; 45 | id bar = [[NSObject alloc] init]; 46 | 47 | @weakify(foo, bar); 48 | [AXEEvent registerListenerForEventName: @"foobar" handler:^(){ 49 | @strongify(foo, bar); 50 | 51 | }]; 52 | 53 | * 54 | */ 55 | @interface AXEEvent : NSObject 56 | 57 | 58 | /** 59 | 发送事件通知 60 | 61 | @param name 名称 62 | */ 63 | + (void)postEventName:(NSString *)name; 64 | 65 | 66 | /** 67 | 发送事件通知 68 | 69 | @param name 名称 70 | @param payload 附带的信息。 71 | */ 72 | + (void)postEventName:(NSString *)name withPayload:(AXEData *)payload; 73 | 74 | 75 | 76 | /** 77 | 将 NSNotification的通知转换为 AXEEvent通知。 78 | 需要注意的是, 这里附带的用户信息, 在 NSNotification中发送的是 NSDictionary 79 | 转换存储在 AXEData中, key为 "userInfo" 80 | 81 | @param notificationName 原通知名称。 82 | */ 83 | + (void)translateNotification:(NSString *)notificationName; 84 | 85 | 86 | /** 87 | 注册同步监听, 使用默认优先级。 88 | 同步监听会在当前发送通知的线程执行。 89 | 如果在一个事件回调中添加对这个事件的监听,显然只能在下次生效。 90 | @param name 名称 91 | @param handler 回调 92 | @return 释放器 93 | */ 94 | + (id)registerListenerForEventName:(NSString *)name 95 | handler:(AXEEventHandlerBlock)handler; 96 | 97 | 98 | /** 99 | 注册同步监听。 100 | 101 | @param name 名称 102 | @param handler 回调 103 | @param priority 优先级, 高优先级任务优先处理, 同优先级会根据添加顺序决定。 104 | @return 释放器 105 | */ 106 | + (id)registerSyncListenerForEventName:(NSString *)name 107 | handler:(AXEEventHandlerBlock)handler 108 | priority:(NSInteger)priority; 109 | 110 | 111 | 112 | /** 113 | 注册 异步序列监听。 异步监听分两种 序列与并发, 序列监听回调会在一个异步gcd的queue中按照优先级依次执行。 114 | 不推荐做耗时的操作,会阻塞其他事件的处理。 115 | @param name 名称 116 | @param handler 回调 117 | @param priority 优先级 118 | @return 释放器。 119 | */ 120 | + (id)registerSerialListenerForEventName:(NSString *)name 121 | handler:(AXEEventHandlerBlock)handler 122 | priority:(NSInteger)priority; 123 | 124 | 125 | /** 126 | 注册异步并发监听, 会新建一个 default优先级的gcd来执行回调。 127 | 异步并发的监听,可以做一些比较耗时的操作,毕竟是单独的queue. 128 | @param name 名称 129 | @param handler 回调 130 | @return 释放器 131 | */ 132 | + (id)registerConcurrentListenerForEventName:(NSString *)name 133 | handler:(AXEEventHandlerBlock)handler; 134 | 135 | /** 136 | UI事件监听。(纠结命名) 137 | 该监听的特点是: 138 | 1. 执行在主线程中。 供那些需要在回调中执行UI操作的任务。 139 | 2. 与界面绑定 。UI监听会监控当前界面的状态, 如果界面不在前台,则推迟回调的执行, 直到界面重新回到前台。 140 | 3. 当界面在后台时,对于同名的事件,只会记录和响应最后一次的数据。 保证通知内容保存最新 !!! 141 | @param name 名称 142 | @param handler 回调 143 | @param priority 优先级 144 | @param container 所在容器 , 通过协议AXEUIContainer来进行 界面切后台切换的监控。 可以直接使用util中的 AXEViewController 145 | @return 释放器。 146 | */ 147 | + (id)registerUIListenerForEventName:(NSString *)name 148 | handler:(AXEEventHandlerBlock)handler 149 | priority:(NSInteger)priority 150 | inUIContainer:(id)container; 151 | 152 | /** 153 | 注册监听,使用默认优先级。 154 | */ 155 | + (id)registerUIListenerForEventName:(NSString *)name 156 | handler:(AXEEventHandlerBlock)handler 157 | inUIContainer:(id)container; 158 | @end 159 | 160 | 161 | 162 | // 以下这些通知,在初始化时,被自动接入到 AXEEvent事件通知系统中。 163 | UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification; 164 | UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification; 165 | UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification; 166 | UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification; 167 | UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification; 168 | // NSNotification附带的userInfo ,以该检测转存到 AXEData中。 169 | extern NSString *const AXEEventNotificationUserInfoKey; 170 | -------------------------------------------------------------------------------- /Axe/Extension/JavascriptSupport/AXEJavaScriptModelData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEJavaScriptModelData.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEJavaScriptModelData.h" 10 | #import 11 | #import "AXEDefines.h" 12 | 13 | 14 | /** 15 | 封装真实数据, 同时提供get set 接口,供外部调用, 以模拟真实的model类型。 16 | TODO 添加 performToSelector支持, 以提高稳定性。 17 | */ 18 | @interface AXEJavaScriptModelWrapper : NSObject 19 | 20 | 21 | + (instancetype)modelWithDictionary:(NSDictionary *)dictionary; 22 | 23 | @property (nonatomic,strong) NSMutableDictionary *axe_dictionary; 24 | 25 | 26 | 27 | @end 28 | 29 | 30 | @implementation AXEJavaScriptModelWrapper 31 | 32 | + (instancetype)modelWithDictionary:(NSDictionary *)dictionary { 33 | AXEJavaScriptModelWrapper *wrapper = [[AXEJavaScriptModelWrapper alloc] init]; 34 | wrapper.axe_dictionary = [dictionary mutableCopy]; 35 | return wrapper; 36 | } 37 | 38 | 39 | - (void)axe_modelSetWithJSON:(NSDictionary *)json { 40 | _axe_dictionary = [json mutableCopy]; 41 | } 42 | 43 | - (NSDictionary *)axe_modelToJSONObject { 44 | return [_axe_dictionary copy]; 45 | } 46 | 47 | 48 | - (id)objectForKey:(NSString *)key { 49 | NSParameterAssert([key isKindOfClass:[NSString class]]); 50 | 51 | id object = [_axe_dictionary objectForKey:key]; 52 | if ([object isKindOfClass:[NSNull class]]) { 53 | // 注意处理 NSNull 54 | return nil; 55 | } 56 | return object; 57 | } 58 | 59 | - (void)setObject:(id)object forKey:(NSString *)key { 60 | NSParameterAssert([key isKindOfClass:[NSString class]]); 61 | 62 | if ([_axe_dictionary objectForKey:key]) { 63 | if (object == nil) { 64 | // 设置值为空时, 我们实际设置的是 NSNull类型,通过NSNull以记住完成的model结构。 65 | [_axe_dictionary setObject:[NSNull null] forKey:key]; 66 | }else { 67 | if ([object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNumber class]] 68 | || [object isKindOfClass:[NSArray class]] || [object isKindOfClass:[NSDictionary class]]) { 69 | [_axe_dictionary setObject:object forKey:key]; 70 | }else { 71 | AXELogWarn(@" 对 javascriptModel 设置非基础数据类型 , %@ = %@",key,object); 72 | } 73 | 74 | } 75 | }else { 76 | AXELogTrace(@"当前没找到对应 key : %@",key); 77 | } 78 | } 79 | 80 | // 通过 OC的动态性,将JavaScriptModel 伪装成一个 model类型, 进行get set操作。 81 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 82 | NSString *selectorName = NSStringFromSelector(selector); 83 | if ([selectorName hasPrefix:@"set"] && selectorName.length > 4) { 84 | // set 方法 85 | NSString *ivarName = [selectorName substringWithRange:NSMakeRange(3, selectorName.length - 4)]; 86 | ivarName = [ivarName lowercaseString]; 87 | id item = [_axe_dictionary objectForKey:ivarName]; 88 | if (item) { 89 | NSMethodSignature *signature = [AXEJavaScriptModelWrapper instanceMethodSignatureForSelector:@selector(setObject:forKey:)]; 90 | return signature; 91 | } 92 | }else { 93 | // get 方法。 94 | NSString *ivarName = selectorName; 95 | id item = [_axe_dictionary objectForKey:ivarName]; 96 | if (item ) { 97 | NSMethodSignature *signature = [AXEJavaScriptModelWrapper instanceMethodSignatureForSelector:@selector(objectForKey:)]; 98 | return signature; 99 | } 100 | } 101 | return [super methodSignatureForSelector:selector]; 102 | } 103 | 104 | - (void)forwardInvocation:(NSInvocation *)invocation { 105 | if (invocation) { 106 | NSString *selectorName = NSStringFromSelector(invocation.selector); 107 | if ([selectorName hasPrefix:@"set"] && selectorName.length > 3) { 108 | // set 方法 109 | NSString *ivarName = [selectorName substringWithRange:NSMakeRange(3, selectorName.length - 4)]; 110 | ivarName = [ivarName lowercaseString]; 111 | id item = [_axe_dictionary objectForKey:ivarName]; 112 | if (item) { 113 | // 如果找到方法。 114 | invocation.selector = @selector(setObject:forKey:); 115 | // 0: self, 1:cmd, 2:value , 3:ivarName/key 116 | [invocation setArgument:&ivarName atIndex:3]; 117 | [invocation invoke]; 118 | } 119 | }else { 120 | // get 方法。 121 | NSString *ivarName = selectorName; 122 | id item = [_axe_dictionary objectForKey:ivarName]; 123 | if (item ) { 124 | invocation.selector = @selector(objectForKey:); 125 | [invocation setArgument:&ivarName atIndex:2]; 126 | [invocation invoke]; 127 | } 128 | } 129 | } 130 | } 131 | 132 | - (NSString *)description { 133 | return [NSString stringWithFormat:@" : %@" ,_axe_dictionary]; 134 | } 135 | 136 | @end 137 | 138 | 139 | @implementation AXEJavaScriptModelData 140 | 141 | + (instancetype)javascriptModelWithValue:(NSDictionary *)value { 142 | AXEJavaScriptModelWrapper *wrapper = [AXEJavaScriptModelWrapper modelWithDictionary:value]; 143 | AXEJavaScriptModelData *data = [AXEJavaScriptModelData dataWithValue:wrapper]; 144 | return data; 145 | } 146 | 147 | 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /Axe/Extension/TabBarController/AXETabBarController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXETabBarController.m 3 | // Demo 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXETabBarController.h" 10 | #import "Axe.h" 11 | #import "AxeDefines.h" 12 | #import "AXERouteRequest.h" 13 | 14 | 15 | static NSMutableArray *itemList = nil; 16 | static void (^customDecorateBlock)(AXETabBarController *) = nil; 17 | static Class NavigationControllerClass = NULL; 18 | 19 | @interface AXETabBarController () 20 | 21 | @end 22 | 23 | 24 | 25 | 26 | @implementation AXETabBarController 27 | 28 | + (void)setNavigationControllerClass:(Class)cls { 29 | NavigationControllerClass = cls; 30 | } 31 | 32 | + (void)registerTabBarItem:(AXETabBarItem *)barItem { 33 | NSParameterAssert([barItem isKindOfClass:[AXETabBarItem class]]); 34 | 35 | if (!itemList) { 36 | itemList = [[NSMutableArray alloc] initWithCapacity:10]; 37 | } 38 | [itemList addObject:barItem]; 39 | } 40 | 41 | 42 | 43 | + (void)setCustomDecorateBlock:(void (^)(AXETabBarController *))block { 44 | NSParameterAssert(block); 45 | 46 | customDecorateBlock = [block copy]; 47 | } 48 | 49 | + (instancetype)sharedTabBarController { 50 | static AXETabBarController *instance; 51 | static dispatch_once_t onceToken; 52 | dispatch_once(&onceToken, ^{ 53 | if (!itemList.count) { 54 | AXELogWarn(@"[AXETabBarController tabBarController]: 当前没有设置任何的子界面!!!"); 55 | } 56 | if (NULL == NavigationControllerClass) { 57 | NavigationControllerClass = [UINavigationController class]; 58 | } 59 | instance = [[AXETabBarController alloc] init]; 60 | }); 61 | return instance; 62 | } 63 | 64 | 65 | - (void)viewDidLoad { 66 | [super viewDidLoad]; 67 | self.view.backgroundColor = [UIColor whiteColor]; 68 | NSMutableArray *controllerList = [[NSMutableArray alloc] initWithCapacity:10]; 69 | NSMutableArray *validItemList = [[NSMutableArray alloc] initWithCapacity:10]; 70 | NSInteger index = 0; 71 | for (AXETabBarItem *item in itemList) { 72 | AXEData *data = [AXEData dataForTransmission]; 73 | [data setData:@(index) forKey:AXETabBarRouterIndexKey]; 74 | [data setData:item.path forKey:AXETabBarRouterPathKey]; 75 | UIViewController *controller = [[AXERouter sharedRouter] viewForURL:item.viewRoute withParams:data finishBlock:nil]; 76 | if (!controller) { 77 | AXELogWarn(@" 当前 routeURL : %@ ,未能正确返回ViewController!!!",item.viewRoute); 78 | }else { 79 | index ++; 80 | if (![controller isKindOfClass:[UINavigationController class]]) { 81 | // 则构建一个 navigationController 82 | UINavigationController *navigation = [[NavigationControllerClass alloc] initWithRootViewController:controller]; 83 | controller = navigation; 84 | } 85 | [controllerList addObject:controller]; 86 | [validItemList addObject:item]; 87 | } 88 | } 89 | if (controllerList.count) { 90 | AXELogTrace(@"初始化成功,当前tab页数量为 %@",@(controllerList.count)); 91 | }else { 92 | AXELogWarn(@"当前tab页数量为0,请检测是否配置错误!!!"); 93 | } 94 | self.viewControllers = controllerList; 95 | for (NSInteger i = 0; i < controllerList.count; i ++) { 96 | UIViewController *controller = controllerList[i]; 97 | AXETabBarItem *item = validItemList[i]; 98 | 99 | if (item.title) { 100 | controller.tabBarItem.title = item.title; 101 | } 102 | if (item.normalIcon) { 103 | controller.tabBarItem.image = item.normalIcon; 104 | } 105 | if (item.selectedIcon) { 106 | controller.tabBarItem.selectedImage = item.selectedIcon; 107 | } 108 | __weak AXETabBarController *wself = self; 109 | [[AXERouter sharedRouter] registerPath:[NSString stringWithFormat:@"%@/%@", AXETabBarRouterDefaultProtocolName, item.path] withJumpRoute:^(AXERouteRequest *request) { 110 | [wself backToIndex:i fromViewController:request.fromVC]; 111 | }]; 112 | } 113 | 114 | if (customDecorateBlock) { 115 | customDecorateBlock(self); 116 | customDecorateBlock = nil; 117 | } 118 | } 119 | 120 | - (void)backToIndex:(NSInteger)index fromViewController:(UIViewController *)fromVC { 121 | // 退回首页,需要处理所有的弹出页面。 122 | if (![fromVC isKindOfClass:[UIViewController class]]) { 123 | AXELogWarn(@"当前没有正确设置fromVC"); 124 | return; 125 | } 126 | UIViewController *presented; 127 | while (fromVC.presentedViewController) { 128 | presented = fromVC.presentedViewController; 129 | [fromVC dismissViewControllerAnimated:NO completion:nil]; 130 | fromVC = presented; 131 | } 132 | // 然后如果首页是UINavigationController,则退回到root。 133 | UINavigationController *navigation; 134 | if ([fromVC isKindOfClass:[UINavigationController class]]) { 135 | navigation = (UINavigationController *)fromVC; 136 | }else if(fromVC.navigationController) { 137 | navigation = fromVC.navigationController; 138 | } 139 | if (navigation) { 140 | [navigation popToRootViewControllerAnimated:NO]; 141 | } 142 | // 最后选择到指定的index。 143 | self.selectedIndex = index; 144 | } 145 | 146 | @end 147 | 148 | NSString *AXETabBarRouterDefaultProtocolName = @"home"; 149 | 150 | NSString *const AXETabBarRouterIndexKey = @"index"; 151 | NSString *const AXETabBarRouterPathKey = @"path"; 152 | -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWebViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEWebViewController.h" 10 | #import "Axe.h" 11 | #import "AXEWebViewBridge.h" 12 | #import "AXEDefines.h" 13 | #import "WebViewJavascriptBridge.h" 14 | 15 | 16 | @interface AXEWebViewController () 17 | // 传入URL 18 | @property (nonatomic,copy) NSString *startURL; 19 | 20 | @property (nonatomic,strong) AXEWebViewBridge *bridge; 21 | 22 | @end 23 | 24 | static void (^customViewDidLoadBlock)(AXEWebViewController *); 25 | 26 | @implementation AXEWebViewController 27 | 28 | + (void)setCustomViewDidLoadBlock:(void (^)(AXEWebViewController *))block { 29 | customViewDidLoadBlock = [block copy]; 30 | } 31 | 32 | + (instancetype)webViewControllerWithURL:(NSString *)url { 33 | NSParameterAssert(!url || [url isKindOfClass:[NSString class]]); 34 | AXEWebViewController *controller = [[self alloc] init]; 35 | controller.startURL = url; 36 | return controller; 37 | } 38 | 39 | 40 | - (void)loadView { 41 | _webView = [[UIWebView alloc] init]; 42 | self.view = _webView; 43 | } 44 | 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | if (_startURL) { 48 | [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:_startURL]]]; 49 | } 50 | _bridge = [AXEWebViewBridge bridgeWithUIWebView:_webView]; 51 | _bridge.webviewController = self; 52 | if (customViewDidLoadBlock) { 53 | customViewDidLoadBlock(self); 54 | } 55 | } 56 | 57 | 58 | - (WebViewJavascriptBridge *)javascriptBridge { 59 | return (WebViewJavascriptBridge *)_bridge.javascriptBridge; 60 | } 61 | 62 | - (void)setWebViewDelegate:(id)webViewDelegate { 63 | _webViewDelegate = webViewDelegate; 64 | [(WebViewJavascriptBridge *)_bridge.javascriptBridge setWebViewDelegate:webViewDelegate]; 65 | } 66 | 67 | #pragma mark - router register 68 | + (void)registerUIWebViewForHTTP { 69 | [[AXERouter sharedRouter] registerProtocol:@"http" withJumpRoute:^(AXERouteRequest *request) { 70 | UINavigationController *navigation; 71 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 72 | navigation = (UINavigationController *)request.fromVC; 73 | }else if(request.fromVC.navigationController) { 74 | navigation = request.fromVC.navigationController; 75 | } 76 | if (navigation) { 77 | // 对于 跳转路由, 自动在执行回调时关闭页面。 78 | if (request.callback) { 79 | AXERouteCallbackBlock originCallback = request.callback; 80 | UIViewController *topVC = navigation.topViewController; 81 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 82 | [navigation popToViewController:topVC animated:YES]; 83 | originCallback(data); 84 | }; 85 | request.callback = autoCloseCallback; 86 | } 87 | AXEWebViewController *controller = [AXEWebViewController webViewControllerWithURL:request.currentURL]; 88 | controller.routeRequest = request; 89 | controller.hidesBottomBarWhenPushed = YES; 90 | [navigation pushViewController:controller animated:YES]; 91 | }else { 92 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 93 | } 94 | }]; 95 | [[AXERouter sharedRouter] registerProtocol:@"http" withViewRoute:^UIViewController *(AXERouteRequest *request) { 96 | AXEWebViewController *controller = [AXEWebViewController webViewControllerWithURL:request.currentURL]; 97 | controller.routeRequest = request; 98 | return controller; 99 | }]; 100 | } 101 | 102 | 103 | + (void)registerUIWebViewForHTTPS { 104 | [[AXERouter sharedRouter] registerProtocol:@"https" withJumpRoute:^(AXERouteRequest *request) { 105 | UINavigationController *navigation; 106 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 107 | navigation = (UINavigationController *)request.fromVC; 108 | }else if(request.fromVC.navigationController) { 109 | navigation = request.fromVC.navigationController; 110 | } 111 | if (navigation) { 112 | // 对于 跳转路由, 自动在执行回调时关闭页面。 113 | if (request.callback) { 114 | AXERouteCallbackBlock originCallback = request.callback; 115 | UIViewController *topVC = navigation.topViewController; 116 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 117 | [navigation popToViewController:topVC animated:YES]; 118 | originCallback(data); 119 | }; 120 | request.callback = autoCloseCallback; 121 | } 122 | AXEWebViewController *controller = [AXEWebViewController webViewControllerWithURL:request.currentURL]; 123 | controller.routeRequest = request; 124 | controller.hidesBottomBarWhenPushed = YES; 125 | [navigation pushViewController:controller animated:YES]; 126 | }else { 127 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 128 | } 129 | }]; 130 | [[AXERouter sharedRouter] registerProtocol:@"https" withViewRoute:^UIViewController *(AXERouteRequest *request) { 131 | AXEWebViewController *controller = [AXEWebViewController webViewControllerWithURL:request.currentURL]; 132 | controller.routeRequest = request; 133 | return controller; 134 | }]; 135 | } 136 | 137 | 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axe 2 | 3 | Axe is all the reinforcement this army needs. 4 | 5 | 快速了解 : 6 | 7 | * [demo-app](https://github.com/axe-org/demo-app) 8 | * [组件开发管理平台 Demo](https://demo.axe-org.cn) 9 | * [文档](https://axe-org.cn) (待填坑。。。) 10 | 11 | ## Axe 框架 12 | 13 | `Axe`是一个iOS的业务组件化框架,通过三大基础组件,来规定所有业务组件之间的交互方式, 三大基础组件为 : 14 | 15 | * Router : 路由模块,根据URL获取界面或者进行跳转 16 | * Event : 事件通知, 是业务组件之间的主要交互方式 17 | * Data : 公共数据,以及业务组件之前传递数据的媒介 18 | 19 | 相对于一般的组件化方案,我们多了一个`Event`和`Data`, 这两个组件的出现,使跨语言的统一业务开发模式的统一成为了可能 ,即当我们规定好三种路由、事件和数据后,`h5`的模块和`react-native`的模块可以和原生模块一样通过`Axe`框架与其他业务模块进行交互。 20 | 21 | ## 示例 22 | 23 | 详细示例可以参考[Demo项目](https://github.com/axe-org/demo-app) , 这里简单介绍一下常用方法: 24 | 25 | // 路由 26 | // 路由跳转 27 | [self jumpTo:@"axes://login/register"]; 28 | // 视图路由 29 | UIViewController *controller = [[AXERouter sharedRouter] viewForURL:item.viewRoute 30 | withParams:data finishBlock:nil]; 31 | // 带参数, 带回调 32 | AXEData *data = [AXEData dataForTransmission]; 33 | [data setData:@"12345678" forKey:@"account"]; 34 | [self jumpTo:@"axes://login/login" withParams:data finishBlock:^(AXEData *payload) { 35 | id userInfo = [payload modelForKey:@"userInfo"]; 36 | NSLog(@"%@",userInfo.account); 37 | }]; 38 | 39 | //数据共享 40 | // 设置数据 41 | UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; 42 | [[AXEData sharedData] setData:image forKey:@"image"]; 43 | // 读取数据 44 | UIImage *image = [[AXEData sharedData] imageForKey:@"image"]; 45 | 46 | // 事件通知 47 | // 注册 48 | [wself registerUIEvent:@"LoginStatusChange" withHandler:^(AXEData *payload) { 49 | BOOL login = [payload boolForKey:@"login"]; 50 | if (!login) { 51 | [wself toastMessage:@"退出登录 !!!"]; 52 | } else { 53 | id userInfo = [payload modelForKey:@"userInfo"]; 54 | NSLog(@"%@",userInfo.account); 55 | } 56 | }]; 57 | // 发送通知 58 | [[AXEData sharedData] removeDataForKey:@"userInfo"]; 59 | AXEData *data = [AXEData dataForTransmission]; 60 | [data setBool:NO forKey:@"login"]; 61 | [AXEEvent postEventName:@"LoginStatusChange" withPayload:data]; 62 | 63 | 64 | 65 | 66 | ## Axe框架结构 67 | 68 | 核心功能: 69 | 70 | * Router : 路由, 根据一个`URL`返回一次页面跳转或者一个界面 71 | * Event : 事件通知 72 | * Data : 数据, 既可以作为 路由跳转和事件通知的数据传递媒介,也可以做公共数据的共享 73 | 74 | 扩展功能: 75 | 76 | * TabbarController : TabbarController 管理的简单实现 77 | * JavascriptSupport : 三大基础组件为支持js调用,所做的简单封装,动态性支持。 78 | * Html :提供webview支持,动态性支持。 79 | * React : 提供react-native支持,动态性支持。 80 | * OfflineHtml : h5离线包支持,动态性支持。 81 | * OfflineReact : react-native 离线包支持,动态性支持。 82 | * DynamicRouter : 动态路由,实现完全脱离实现细节的 路由控制。,动态性支持。 83 | 84 | 85 | ## Axe系统 86 | 87 | `Axe`框架是一个组件化的框架,而`Axe`系统是基于`Axe`框架搭建起来的完整的业务模块化管理开发系统。 所以 从`Axe`框架 到系统,分为以下三个层次 : 88 | 89 | ![](axe-system.png) 90 | 91 | * 组件化: 实现业务组件化拆分,实现代码隔离的单独编译 92 | * 动态化: 接入`html` 和`react-native`支持, 同时模块可替换。 93 | * 平台化: 为APP上的业务模块开发提供一个管理平台,管理开发流程。 94 | 95 | ## Axe的动态化 96 | 97 | 动态化指 尽可能地提高`APP`的动态性能力。 已知目前在`iOS`上能用的动态化方案,有两种, `H5`和`react-native` (忽略weex等),则动态化要做到的内容是 : 支持`h5`和`react-native`模块, 使两种模块表现与原生模块相同, 最后做到能够动态切换。 98 | 99 | #### 接入h5和react-native 100 | 101 | 我们为`axe`提供了两个接口 : 102 | 103 | * [axe-js](https://github.com/axe-org/axe-js) : `h5`使用的`axe`接口 104 | * [axe-react](https://github.com/axe-org/axe-react) : `react-native`使用的`axe`接口 105 | 106 | 通过这两个接口,使`js`开发的业务模块也纳入了 组件化的体系中,保持与原生业务模块的一致性, 即一个原生开发的模块,与`js`开发的模块,几乎没有区别。 107 | 108 | 同时,我们为`js`开发的模块提供了离线包功能, 以优化其更新下载过程 : 109 | 110 | * [offline-pack-server](https://github.com/axe-org/offline-pack-server) : 离线包管理平台 111 | * [offline-pack-ios](https://github.com/axe-org/offline-pack-ios) : 离线包的iOS 实现。 112 | 113 | 在`axe`框架的扩展中,`JavascriptSupport` ,`Html`, `React`,`OfflineHtml`,`OfflineReact` ,都是为`js`模块服务的。 114 | 115 | #### 可替换性与动态切换 116 | 117 | 如上所述, 我们说`js`开发的模块接入了组件化体系, 通过提供`接口`(路由、事件、通知) 供其他模块调用,以及通过`接口`调用其他模块。 所以在我们`Axe`的组件化下, `js`模块与原生模块表现一致,可以互相替换。 118 | 119 | 动态切换:指当前APP上如果有一个业务模块的多个实现时, 我们可以控制模块选取具体的实现。 120 | 121 | 一个模块的多个实现,其接口中, 事件和通知都是相同,不同的是`路由` 。 所以我们提出了`声明路由`和`实现路由`的概念: 122 | 123 | ![](router-declaration.png) 124 | 125 | 当APP需要这种动态化能力时, 我们开发模块时会隐藏`实现路由` ,而暴露`声明路由` , 再通过一个下发路由映射规则的服务 : 126 | 127 | [dynamic-router-server](https://github.com/axe-org/dynamic-router-server) 128 | 129 | 做到线上模块动态切换。 130 | 131 | ## Axe的平台化 132 | 133 | 平台化指通过一个平台,来规范模块化APP的开发、构建、测试、接入、发布流程,优化跨小组、团队、部门的协作开发。平台化关注以下问题: 134 | 135 | * 代码管理的规范: 确定`git-flow`, 确定代码检视规则,确定代码提交权限 等等。 136 | * 项目管理的规范: 确定项目时间, 明确测试与发布时间,约定需求接收模式,明确职责划分 等等 137 | * 协作开发的规范: 模块相关信息与接口API的展示,模块版本变更记录, 协作开发时处理模块间的依赖 等等。 138 | * 版本管理的规范: APP的版本规则, 模块版本的规则, 版本如何发布,模块如何接入到APP中 等等。 139 | * 持续集成的规范: 确定构建工具,搭建持续集成平台,管理模块和APP的打包 等等。 140 | * 自动化测试: 确定自动化测试的工具与方式,完成自动化测试系统的搭建, 确保每个模块都进行自动化测试。 141 | 142 | `Axe`系统提供一个平台化的解决方案 [axe-admin](https://github.com/axe-org/axe-admin) , 提供[demo](https://demo.axe-org.cn)以查看效果。 143 | 144 | ## 生态系统 145 | 146 | | Project | Description | 147 | |---------|--------| 148 | | [axe-react](https://github.com/axe-org/axe-react) | 为React Native提供的`Axe`接口 | 149 | | [axe-js](https://github.com/axe-org/axe-js) | 为H5页面提供的`Axe`接口 | 150 | | [offline-pack-server](https://github.com/axe-org/offline-pack-server) | 离线包服务 | 151 | | [offline-pack-ios](https://github.com/axe-org/offline-pack-ios) | 离线包的iOS 实现 | 152 | | [dynamic-router-server](https://github.com/axe-org/dynamic-router-server) | 动态路由服务 | 153 | | [axe-admin-server](https://github.com/axe-org/axe-admin-server) | 组件化管理平台的后端 | 154 | | [axe-admin-web](https://github.com/axe-org/axe-admin-web) | 组件化管理平台的前端 | 155 | | [axe-admin-docker](https://github.com/axe-org/axe-admin-docker) | 使用docker镜像进行发布 | 156 | | [fastlane](https://github.com/axe-org/fastlane) | 组件化管理的辅助脚本 | 157 | 158 | ### License 159 | 160 | Axe is licensed under [The MIT License](LICENSE). -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWKWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWKWebViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEWKWebViewController.h" 10 | #import "Axe.h" 11 | #import "AXEWebViewBridge.h" 12 | #import "AXEDefines.h" 13 | #import "WKWebViewJavascriptBridge.h" 14 | 15 | @interface AXEWKWebViewController() 16 | 17 | @property (nonatomic,strong) AXEWebViewBridge *bridge; 18 | @property (nonatomic,copy) NSString *startURL; 19 | @end 20 | static void (^customViewDidLoadBlock)(AXEWKWebViewController *); 21 | @implementation AXEWKWebViewController 22 | 23 | + (void)setCustomViewDidLoadBlock:(void (^)(AXEWKWebViewController *))block { 24 | customViewDidLoadBlock = [block copy]; 25 | } 26 | 27 | + (instancetype)webViewControllerWithURL:(NSString *)url { 28 | NSParameterAssert(!url || [url isKindOfClass:[NSString class]]); 29 | AXEWKWebViewController *controller = [[self alloc] init]; 30 | controller.startURL = url; 31 | return controller; 32 | } 33 | 34 | - (void)loadView { 35 | _webView = [[WKWebView alloc] init]; 36 | // 启动手势右滑返回。 37 | _webView.allowsBackForwardNavigationGestures = YES; 38 | self.view = _webView; 39 | } 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | if (_startURL) { 44 | [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:_startURL]]]; 45 | } 46 | 47 | _bridge = [AXEWebViewBridge bridgeWithWKWebView:_webView]; 48 | _bridge.webviewController = self; 49 | if (customViewDidLoadBlock) { 50 | customViewDidLoadBlock(self); 51 | } 52 | } 53 | 54 | 55 | - (WKWebViewJavascriptBridge *)javascriptBridge { 56 | return (WKWebViewJavascriptBridge *)_bridge.javascriptBridge; 57 | } 58 | 59 | - (void)setWebViewDelegate:(id)webViewDelegate { 60 | _webViewDelegate = webViewDelegate; 61 | [(WKWebViewJavascriptBridge *)_bridge.javascriptBridge setWebViewDelegate:webViewDelegate]; 62 | } 63 | 64 | 65 | #pragma mark - router register 66 | + (void)registerWKWebViewForHTTP { 67 | [[AXERouter sharedRouter] registerProtocol:@"http" withJumpRoute:^(AXERouteRequest *request) { 68 | UINavigationController *navigation; 69 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 70 | navigation = (UINavigationController *)request.fromVC; 71 | }else if(request.fromVC.navigationController) { 72 | navigation = request.fromVC.navigationController; 73 | } 74 | if (navigation) { 75 | // 对于 跳转路由, 自动在执行回调时关闭页面。 76 | if (request.callback) { 77 | AXERouteCallbackBlock originCallback = request.callback; 78 | UIViewController *topVC = navigation.topViewController; 79 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 80 | [navigation popToViewController:topVC animated:YES]; 81 | originCallback(data); 82 | }; 83 | request.callback = autoCloseCallback; 84 | } 85 | AXEWKWebViewController *controller = [AXEWKWebViewController webViewControllerWithURL:request.currentURL]; 86 | controller.routeRequest = request; 87 | controller.hidesBottomBarWhenPushed = YES; 88 | [navigation pushViewController:controller animated:YES]; 89 | }else { 90 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 91 | } 92 | }]; 93 | [[AXERouter sharedRouter] registerProtocol:@"http" withViewRoute:^UIViewController *(AXERouteRequest *request) { 94 | AXEWKWebViewController *controller = [AXEWKWebViewController webViewControllerWithURL:request.currentURL]; 95 | controller.routeRequest = request; 96 | return controller; 97 | }]; 98 | } 99 | 100 | 101 | + (void)registerWKWebViewForHTTPS { 102 | [[AXERouter sharedRouter] registerProtocol:@"https" withJumpRoute:^(AXERouteRequest *request) { 103 | UINavigationController *navigation; 104 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 105 | navigation = (UINavigationController *)request.fromVC; 106 | }else if(request.fromVC.navigationController) { 107 | navigation = request.fromVC.navigationController; 108 | } 109 | if (navigation) { 110 | // 对于 跳转路由, 自动在执行回调时关闭页面。 111 | if (request.callback) { 112 | AXERouteCallbackBlock originCallback = request.callback; 113 | UIViewController *topVC = navigation.topViewController; 114 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 115 | [navigation popToViewController:topVC animated:YES]; 116 | originCallback(data); 117 | }; 118 | request.callback = autoCloseCallback; 119 | } 120 | AXEWKWebViewController *controller = [AXEWKWebViewController webViewControllerWithURL:request.currentURL]; 121 | controller.routeRequest = request; 122 | controller.hidesBottomBarWhenPushed = YES; 123 | [navigation pushViewController:controller animated:YES]; 124 | }else { 125 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 126 | } 127 | }]; 128 | [[AXERouter sharedRouter] registerProtocol:@"https" withViewRoute:^UIViewController *(AXERouteRequest *request) { 129 | AXEWKWebViewController *controller = [AXEWKWebViewController webViewControllerWithURL:request.currentURL]; 130 | controller.routeRequest = request; 131 | return controller; 132 | }]; 133 | } 134 | 135 | 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineHtml/AXEOfflineWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWebViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/19. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEOfflineWebViewController.h" 10 | #import "OPOfflineManager.h" 11 | #import "AXEOfflineDownloadView.h" 12 | #import "AXEDefines.h" 13 | 14 | @interface AXEOfflineWebViewController() 15 | 16 | @property (nonatomic,copy) NSString *module; 17 | @property (nonatomic,copy) NSString *path; 18 | @property (nonatomic,copy) NSString *page; 19 | @property (nonatomic,strong) NSURL *localURL;// 最终的本地路径。 20 | @property (nonatomic,weak) AXEOfflineDownloadView *downloadView;// 下载进度。 21 | @end 22 | 23 | @implementation AXEOfflineWebViewController 24 | 25 | 26 | + (instancetype)webViewControllerWithFilePath:(NSString *)path 27 | page:(NSString *)page 28 | inModule:(NSString *)module { 29 | NSParameterAssert([path isKindOfClass:[NSString class]]); 30 | NSParameterAssert([module isKindOfClass:[NSString class]]); 31 | NSParameterAssert(!page || [page isKindOfClass:[NSString class]]); 32 | 33 | AXEOfflineWebViewController *webVC = [[self alloc] init]; 34 | webVC.path = path; 35 | webVC.page = page; 36 | webVC.module = module; 37 | return webVC; 38 | } 39 | 40 | - (void)viewDidLoad { 41 | [super viewDidLoad]; 42 | 43 | [self checkUpdate]; 44 | } 45 | 46 | 47 | - (void)checkUpdate { 48 | // 检测离线包模块。 49 | OPOfflineModule *module = [[OPOfflineManager sharedManager] moduleForName:_module]; 50 | // 要检测module ,一般肯定会返回,但是要以防万一。 51 | if (!module) { 52 | // TODO 展示错误页面。 53 | 54 | return; 55 | } 56 | if (module.needCheckUpdate) { 57 | // 如果需要检测更新, 则要展示进度条。 58 | module.delegate = self; 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | self->_downloadView = [AXEOfflineDownloadView showInView:self.view]; 61 | [self->_downloadView setErrorHandlerButtonTitle:@"重试" withBlock:^{ 62 | [self checkUpdate]; 63 | }]; 64 | }); 65 | return; 66 | } 67 | // 否则,就可以直接加载页面了。 68 | NSString *url = [NSString stringWithFormat:@"file://%@/%@", module.path, _path]; 69 | if (_page) { 70 | // 推荐单页面应用。 71 | url = [url stringByAppendingFormat:@"#/%@",_page]; 72 | } 73 | _localURL = [NSURL URLWithString:url]; 74 | [self.webView loadRequest:[NSURLRequest requestWithURL:_localURL]]; 75 | } 76 | 77 | 78 | #pragma mark - AXEOfflineDownloadView 79 | 80 | - (void)module:(OPOfflineModule *)module didDownloadProgress:(NSInteger)progress { 81 | [_downloadView didDownloadProgress:progress]; 82 | } 83 | 84 | 85 | - (void)moduleDidFinishDownload:(OPOfflineModule *)module { 86 | [_downloadView didFinishLoadSuccess]; 87 | NSString *url = [NSString stringWithFormat:@"file://%@/%@", module.path, _path]; 88 | if (_page) { 89 | // 推荐单页面应用。 90 | url = [url stringByAppendingFormat:@"#/%@",_page]; 91 | } 92 | _localURL = [NSURL URLWithString:url]; 93 | [self.webView loadRequest:[NSURLRequest requestWithURL:_localURL]]; 94 | } 95 | 96 | - (void)module:(OPOfflineModule *)module didFailLoadWithError:(NSError *)error { 97 | [_downloadView didFinishLoadFailed]; 98 | } 99 | 100 | #pragma mark - router register 101 | 102 | + (void)registerUIWebVIewForOfflineHtml { 103 | [[AXERouter sharedRouter] registerProtocol:@"ophttp" withJumpRoute:^(AXERouteRequest *request) { 104 | UINavigationController *navigation; 105 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 106 | navigation = (UINavigationController *)request.fromVC; 107 | }else if(request.fromVC.navigationController) { 108 | navigation = request.fromVC.navigationController; 109 | } 110 | if (navigation) { 111 | // 对于 跳转路由, 自动在执行回调时关闭页面。 112 | if (request.callback) { 113 | UIViewController *topVC = navigation.topViewController; 114 | AXERouteCallbackBlock originCallback = request.callback; 115 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 116 | [navigation popToViewController:topVC animated:YES]; 117 | originCallback(data); 118 | }; 119 | request.callback = autoCloseCallback; 120 | } 121 | // 解析URL 122 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 123 | NSString *module = urlComponets.host; 124 | if (!module) { 125 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 126 | return; 127 | } 128 | NSString *page = urlComponets.path; 129 | NSString *path = module; 130 | if (page.length > 1) { 131 | path = [module stringByAppendingString:page]; 132 | page = [page substringFromIndex:1]; 133 | } else { 134 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 135 | return; 136 | } 137 | request.module = module; 138 | request.path = path; 139 | request.page = page; 140 | // filePath 固定为 index.html. 141 | AXEOfflineWebViewController *controller = [AXEOfflineWebViewController webViewControllerWithFilePath:@"index.html" page:page inModule:module]; 142 | controller.hidesBottomBarWhenPushed = YES; 143 | controller.routeRequest = request; 144 | [navigation pushViewController:controller animated:YES]; 145 | }else { 146 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 147 | } 148 | }]; 149 | [[AXERouter sharedRouter] registerProtocol:@"ophttp" withViewRoute:^UIViewController *(AXERouteRequest *request) { 150 | // 解析URL 151 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 152 | NSString *module = urlComponets.host; 153 | if (!module) { 154 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 155 | return nil; 156 | } 157 | NSString *page = urlComponets.path; 158 | NSString *path = module; 159 | if (page.length > 1) { 160 | path = [module stringByAppendingString:page]; 161 | page = [page substringFromIndex:1]; 162 | } else { 163 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 164 | return nil; 165 | } 166 | request.module = module; 167 | request.path = path; 168 | request.page = page; 169 | // filePath 固定为 index.html. 170 | AXEOfflineWebViewController *controller = [AXEOfflineWebViewController webViewControllerWithFilePath:@"index.html" page:page inModule:module]; 171 | controller.routeRequest = request; 172 | return controller; 173 | }]; 174 | } 175 | @end 176 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineReact/AXEOfflineReactViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineReactViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/21. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEOfflineReactViewController.h" 10 | #import "AXEOfflineDownloadView.h" 11 | #import "OPOfflineManager.h" 12 | #import "AXEReactControllerWrapper.h" 13 | #import "AXEDefines.h" 14 | 15 | // 同时我们也规定,入口文件名,也固定 ,为 bundle.js 16 | static NSString *const AXEDefaultBundleName = @"bundle.js"; 17 | 18 | 19 | @interface AXEOfflineReactViewController () 20 | @property (nonatomic,copy) NSString *path; 21 | @property (nonatomic,copy) NSString *offlineModule; 22 | @property (nonatomic,copy) NSString *moduleName; 23 | @property (nonatomic,strong) NSURL *localURL;// 最终的本地路径。 24 | @property (nonatomic,weak) AXEOfflineDownloadView *downloadView;// 下载进度。 25 | 26 | @end 27 | 28 | @implementation AXEOfflineReactViewController 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | // Do any additional setup after loading the view. 33 | [self checkUpdate]; 34 | } 35 | 36 | 37 | + (instancetype)controllerWithBundlePath:(NSString *)path 38 | moduleName:(NSString *)moduleName 39 | inOfflineModule:(NSString *)offlineModule { 40 | NSParameterAssert([path isKindOfClass:[NSString class]]); 41 | NSParameterAssert([moduleName isKindOfClass:[NSString class]]); 42 | NSParameterAssert([offlineModule isKindOfClass:[NSString class]]); 43 | 44 | AXEOfflineReactViewController *controller = [[AXEOfflineReactViewController alloc] init]; 45 | controller.path = path; 46 | controller.moduleName = moduleName; 47 | controller.offlineModule = offlineModule; 48 | return controller; 49 | } 50 | 51 | 52 | - (void)checkUpdate { 53 | // 检测离线包模块。 54 | OPOfflineModule *module = [[OPOfflineManager sharedManager] moduleForName:_offlineModule]; 55 | // 要检测module ,一般肯定会返回,但是要以防万一。 56 | if (!module) { 57 | // TODO 展示错误页面。 58 | 59 | return; 60 | } 61 | if (module.needCheckUpdate) { 62 | // 如果需要检测更新, 则要展示进度条。 63 | module.delegate = self; 64 | dispatch_async(dispatch_get_main_queue(), ^{ 65 | self->_downloadView = [AXEOfflineDownloadView showInView:self.view]; 66 | [self->_downloadView setErrorHandlerButtonTitle:@"重试" withBlock:^{ 67 | [self checkUpdate]; 68 | }]; 69 | }); 70 | return; 71 | } 72 | // 否则,就可以直接加载页面了。 73 | [self loadRCTViewWithModule:module]; 74 | } 75 | 76 | - (void)loadRCTViewWithModule:(OPOfflineModule *)module { 77 | NSString *url = [module.path stringByAppendingPathComponent:_path]; 78 | url = [@"file://" stringByAppendingString:url]; 79 | 80 | [self loadReactWithBundleURL:url moduleName:_moduleName]; 81 | } 82 | 83 | 84 | #pragma mark - AXEOfflineDownloadView 85 | 86 | - (void)module:(OPOfflineModule *)module didDownloadProgress:(NSInteger)progress { 87 | [_downloadView didDownloadProgress:progress]; 88 | } 89 | 90 | 91 | - (void)moduleDidFinishDownload:(OPOfflineModule *)module { 92 | [_downloadView didFinishLoadSuccess]; 93 | [self loadRCTViewWithModule:module]; 94 | } 95 | 96 | - (void)module:(OPOfflineModule *)module didFailLoadWithError:(NSError *)error { 97 | [_downloadView didFinishLoadFailed]; 98 | } 99 | 100 | #pragma mark router register 101 | + (void)registerOfflineReactProtocol { 102 | [[AXERouter sharedRouter] registerProtocol:AXEOfflineReactProtocol withJumpRoute:^(AXERouteRequest *request) { 103 | UINavigationController *navigation; 104 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 105 | navigation = (UINavigationController *)request.fromVC; 106 | }else if(request.fromVC.navigationController) { 107 | navigation = request.fromVC.navigationController; 108 | } 109 | if (navigation) { 110 | // 对于 跳转路由, 自动在执行回调时关闭页面。 111 | if (request.callback) { 112 | AXERouteCallbackBlock originCallback = request.callback; 113 | UIViewController *topVC = navigation.topViewController; 114 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 115 | [navigation popToViewController:topVC animated:YES]; 116 | originCallback(data); 117 | }; 118 | request.callback = autoCloseCallback; 119 | } 120 | // 解析URL 121 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 122 | NSString *module = urlComponets.host; 123 | if (!module) { 124 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 125 | return; 126 | } 127 | NSString *page = urlComponets.path; 128 | NSString *path = module; 129 | if (page.length > 1) { 130 | path = [module stringByAppendingString:page]; 131 | page = [page substringFromIndex:1]; 132 | } 133 | request.module = module; 134 | request.path = path; 135 | request.page = page; 136 | 137 | 138 | AXEOfflineReactViewController *controller = [AXEOfflineReactViewController controllerWithBundlePath:AXEDefaultBundleName 139 | moduleName:page 140 | inOfflineModule:module]; 141 | controller.hidesBottomBarWhenPushed = YES; 142 | controller.routeRequest = request; 143 | [navigation pushViewController:controller animated:YES]; 144 | }else { 145 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 146 | } 147 | }]; 148 | [[AXERouter sharedRouter] registerProtocol:AXEOfflineReactProtocol withViewRoute:^UIViewController *(AXERouteRequest *request) { 149 | // 解析URL 150 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 151 | NSString *module = urlComponets.host; 152 | if (!module) { 153 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 154 | return nil; 155 | } 156 | NSString *page = urlComponets.path; 157 | NSString *path = module; 158 | if (page.length > 1) { 159 | path = [module stringByAppendingString:page]; 160 | page = [page substringFromIndex:1]; 161 | } 162 | request.module = module; 163 | request.path = path; 164 | request.page = page; 165 | 166 | AXEOfflineReactViewController *controller = [AXEOfflineReactViewController controllerWithBundlePath:AXEDefaultBundleName 167 | moduleName:page 168 | inOfflineModule:module]; 169 | controller.routeRequest = request; 170 | return controller; 171 | }]; 172 | } 173 | 174 | 175 | @end 176 | 177 | NSString *const AXEOfflineReactProtocol = @"oprn"; 178 | -------------------------------------------------------------------------------- /Axe/Extension/DynamicRouter/AXEDynamicRouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEDynamicRouter.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/4/16. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEDynamicRouter.h" 10 | #import "AXERouteRequest.h" 11 | #import "Axe.h" 12 | #import "AXEDefines.h" 13 | 14 | 15 | 16 | 17 | static NSString *const SavedDynamicSettingKey = @"AXE_SavedDynamicSettingKey"; 18 | static NSString *const SavedLastAPPVersionKey = @"AXE_DynamicRouter_LastVersion"; 19 | 20 | @interface AXEDynamicRouter () 21 | 22 | @property (nonatomic,strong) NSDictionary *dynamicSetting; 23 | 24 | @property (nonatomic,strong) NSURLSession *session; 25 | 26 | @property (nonatomic,strong) NSDate *lastCheckTime; 27 | 28 | @property (nonatomic,strong) NSURL *serverURL; 29 | 30 | @end 31 | 32 | @implementation AXEDynamicRouter 33 | 34 | 35 | - (instancetype)init { 36 | if (self = [super init]) { 37 | _checkTimeInterval = 10 * 60; 38 | _lastCheckTime = [NSDate dateWithTimeIntervalSince1970:0]; 39 | _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 40 | _appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 41 | _tags = @[]; 42 | } 43 | return self; 44 | } 45 | 46 | 47 | + (instancetype)sharedDynamicRouter { 48 | static AXEDynamicRouter *router; 49 | static dispatch_once_t onceToken; 50 | dispatch_once(&onceToken, ^{ 51 | router = [[AXEDynamicRouter alloc] init]; 52 | }); 53 | return router; 54 | } 55 | 56 | - (void)setupWithURL:(NSString *)serverURL defaultSetting:(NSDictionary *)setting { 57 | NSParameterAssert(!serverURL || [serverURL isKindOfClass:[NSString class]]); 58 | NSParameterAssert([setting isKindOfClass:[NSDictionary class]]); 59 | static dispatch_once_t onceToken; 60 | // 确保只执行一次。 61 | dispatch_once(&onceToken, ^{ 62 | // 加载本地数据 63 | NSDictionary *saved = [[NSUserDefaults standardUserDefaults] dictionaryForKey:SavedDynamicSettingKey]; 64 | if (saved) { 65 | // 检测版本是否变更。 66 | NSString *lastAppVersion = [[NSUserDefaults standardUserDefaults] objectForKey:SavedLastAPPVersionKey]; 67 | if ([self->_appVersion isEqualToString:lastAppVersion]) { 68 | self->_dynamicSetting = saved; 69 | } else { 70 | self->_dynamicSetting = [setting copy]; 71 | [[NSUserDefaults standardUserDefaults] setObject:self->_dynamicSetting forKey:SavedDynamicSettingKey]; 72 | [[NSUserDefaults standardUserDefaults] setObject:self->_appVersion forKey:SavedLastAPPVersionKey]; 73 | } 74 | } else { 75 | self->_dynamicSetting = [setting copy]; 76 | [[NSUserDefaults standardUserDefaults] setObject:self->_dynamicSetting forKey:SavedDynamicSettingKey]; 77 | [[NSUserDefaults standardUserDefaults] setObject:self->_appVersion forKey:SavedLastAPPVersionKey]; 78 | } 79 | if (serverURL) { 80 | // 如果设置了URL ,设置监听 81 | self->_serverURL = [NSURL URLWithString:serverURL]; 82 | [AXEEvent registerSerialListenerForEventName:UIApplicationWillEnterForegroundNotification handler:^(AXEData *payload) { 83 | [self checkUpdate]; 84 | } priority:AXEEventDefaultPriority]; 85 | [self checkUpdate]; 86 | } 87 | // 动态路由设置。 88 | [[AXERouter sharedRouter] addPreprocess:^(AXERouteRequest *request) { 89 | if ([request.protocol isEqualToString:AXEStatementRouterProtocol]) { 90 | // 如果是声明路由则处理。 91 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 92 | NSString *module = urlComponets.host; 93 | if (!module) { 94 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 95 | return; 96 | } 97 | NSString *page = urlComponets.path; 98 | NSString *path = module; 99 | if (page.length > 1) { 100 | path = [module stringByAppendingString:page]; 101 | page = [page substringFromIndex:1]; 102 | } 103 | request.module = module; 104 | request.path = path; 105 | request.page = page; 106 | 107 | NSString *redirectPath = [self->_dynamicSetting objectForKey:request.module]; 108 | if (redirectPath) { 109 | redirectPath = [redirectPath stringByAppendingString:request.page]; 110 | AXELogTrace(@"动态路由进行重定向 : %@ => %@", request.currentURL,redirectPath); 111 | request.currentURL = redirectPath; 112 | } else { 113 | AXELogWarn(@"当前启用动态路由,但是 %@ 并没有查找到映射规则,路由处理失败!!", request); 114 | request.valid = NO; 115 | } 116 | } 117 | 118 | }]; 119 | }); 120 | } 121 | 122 | - (void)checkUpdate { 123 | NSDate *checkTime = [NSDate date]; 124 | if ([checkTime timeIntervalSinceDate:_lastCheckTime] < _checkTimeInterval) { 125 | // 检测时间间隔。 126 | return; 127 | } 128 | _lastCheckTime = checkTime; 129 | 130 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_serverURL]; 131 | request.HTTPMethod = @"POST"; 132 | if (!_tags || !_appVersion) { 133 | AXELogWarn(@"tags 或 appVersion 设置错误 !!!"); 134 | return; 135 | } 136 | NSDictionary *query = @{ 137 | @"tags": _tags, 138 | @"version": _appVersion 139 | }; 140 | NSError *error; 141 | NSData *data = [NSJSONSerialization dataWithJSONObject:query options:0 error:&error]; 142 | if (error) { 143 | AXELogWarn(@"转换为json失败 :%@",error); 144 | return; 145 | } 146 | request.HTTPBody = data; 147 | [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 148 | [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; 149 | 150 | [[_session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 151 | if (error) { 152 | AXELogWarn(@" 请求动态路由, 发生网络异常 %@", error); 153 | } else { 154 | NSError *err; 155 | NSDictionary *dynamicSetting = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err]; 156 | if (err) { 157 | AXELogWarn(@" 解析返回数据失败 :%@", err); 158 | } else { 159 | // 检测业务异常 , 所以不能设置一个模块叫做 error ..... 160 | if (dynamicSetting[@"error"]) { 161 | AXELogWarn(@" 服务器报错 : %@", dynamicSetting[@"error"]); 162 | } else { 163 | self->_dynamicSetting = dynamicSetting; 164 | AXELogTrace(@"更新动态路由规则: %@", dynamicSetting); 165 | // 本地存储 166 | [[NSUserDefaults standardUserDefaults] setObject:dynamicSetting forKey:SavedDynamicSettingKey]; 167 | [[NSUserDefaults standardUserDefaults] synchronize]; 168 | } 169 | } 170 | } 171 | }] resume]; 172 | 173 | } 174 | 175 | @end 176 | 177 | // axe + s , s 表示 statement ,声明。 178 | NSString *AXEStatementRouterProtocol = @"axes"; 179 | -------------------------------------------------------------------------------- /Axe/Axe/Data/AXEData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEData.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEData.h" 10 | #import "AXEBaseData.h" 11 | #import "AXEBasicTypeData.h" 12 | #import "AXEModelTypeData.h" 13 | #import "AXEDefines.h" 14 | 15 | @interface AXEData() 16 | 17 | @property (nonatomic,strong) NSMutableDictionary *storedDatas; 18 | 19 | @end 20 | 21 | @implementation AXEData 22 | 23 | + (instancetype)sharedData { 24 | static AXEData *instance; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | instance = [[AXEData alloc] init]; 28 | instance.storedDatas = [[NSMutableDictionary alloc] initWithCapacity:100]; 29 | }); 30 | return instance; 31 | } 32 | 33 | + (instancetype)dataForTransmission { 34 | AXEData *data = [[AXEData alloc] init]; 35 | data.storedDatas = [[NSMutableDictionary alloc] init]; 36 | return data; 37 | } 38 | 39 | - (void)setData:(id)data forKey:(NSString *)key { 40 | NSParameterAssert([key isKindOfClass:[NSString class]]); 41 | NSParameterAssert(data); 42 | if ([data isKindOfClass:[NSNumber class]]) { 43 | AXELogTrace(@"存储NSNumber : %@ = %@",key,data); 44 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithNumber:data] forKey:key]; 45 | }else if ([data isKindOfClass:[NSString class]]) { 46 | AXELogTrace(@"存储NSString : %@ = %@",key,data); 47 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithString:data] forKey:key]; 48 | }else if ([data isKindOfClass:[NSArray class]]) { 49 | AXELogTrace(@"存储NSArray : %@ = %@",key,data); 50 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithArray:data] forKey:key]; 51 | }else if([data isKindOfClass:[NSDictionary class]]) { 52 | AXELogTrace(@"存储NSDictionary : %@ = %@",key,data); 53 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithDictionary:data] forKey:key]; 54 | }else if ([data isKindOfClass:[UIImage class]]) { 55 | AXELogTrace(@"存储UIImage : %@ = %@",key,data); 56 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithImage:data] forKey:key]; 57 | }else if ([data isKindOfClass:[NSData class]]) { 58 | AXELogTrace(@"存储NSData : %@ = %@",key,data); 59 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithData:data] forKey:key]; 60 | }else if ([data isKindOfClass:[NSDate class]]) { 61 | AXELogTrace(@"存储NSDate : %@ = %@",key,data); 62 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithDate:data] forKey:key]; 63 | }else if ([data conformsToProtocol:@protocol(AXEDataModelProtocol)] ) { 64 | AXELogTrace(@"存储Model : %@ = %@",key,data); 65 | [_storedDatas setObject:[AXEModelTypeData modelDataWithValue:data] forKey:key]; 66 | }else { 67 | AXELogWarn(@"检测数据不合法 , %@ : %@!!!",key,data); 68 | } 69 | } 70 | 71 | - (void)setBool:(BOOL)boo forKey:(NSString *)key { 72 | NSParameterAssert([key isKindOfClass:[NSString class]]); 73 | 74 | AXELogTrace(@"存储Boolean : %@ = %@",key,boo ? @"true" : @"false"); 75 | [_storedDatas setObject:[AXEBasicTypeData basicDataWithBoolean:boo] forKey:key]; 76 | } 77 | 78 | - (void)removeDataForKey:(NSString *)key { 79 | NSParameterAssert([key isKindOfClass:[NSString class]]); 80 | 81 | AXELogTrace(@"删除数据 %@ ",key); 82 | [_storedDatas removeObjectForKey:key]; 83 | } 84 | 85 | - (AXEBaseData *)dataForKey:(NSString *)key { 86 | NSParameterAssert([key isKindOfClass:[NSString class]]); 87 | 88 | return [_storedDatas objectForKey:key]; 89 | } 90 | 91 | 92 | - (NSNumber *)numberForKey:(NSString *)key { 93 | NSParameterAssert([key isKindOfClass:[NSString class]]); 94 | 95 | AXEBaseData *data = [_storedDatas objectForKey:key]; 96 | NSNumber *value = data.value; 97 | if (value && ![value isKindOfClass:[NSNumber class]]) { 98 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSNumber ,值为 %@",key,value); 99 | return nil; 100 | } 101 | return value; 102 | } 103 | 104 | - (NSString *)stringForKey:(NSString *)key { 105 | NSParameterAssert([key isKindOfClass:[NSString class]]); 106 | 107 | AXEBaseData *data = [_storedDatas objectForKey:key]; 108 | NSString *value = data.value; 109 | if (value && ![value isKindOfClass:[NSString class]]) { 110 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSString ,值为 %@",key,value); 111 | return nil; 112 | } 113 | return value; 114 | } 115 | 116 | - (NSArray *)arrayForKey:(NSString *)key { 117 | NSParameterAssert([key isKindOfClass:[NSString class]]); 118 | 119 | AXEBaseData *data = [_storedDatas objectForKey:key]; 120 | NSArray *value = data.value; 121 | if (value && ![value isKindOfClass:[NSArray class]]) { 122 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSArray ,值为 %@",key,value); 123 | return nil; 124 | } 125 | return value; 126 | } 127 | 128 | - (NSDictionary *)dictionaryForKey:(NSString *)key { 129 | NSParameterAssert([key isKindOfClass:[NSString class]]); 130 | 131 | AXEBaseData *data = [_storedDatas objectForKey:key]; 132 | NSDictionary *value = data.value; 133 | if (value && ![value isKindOfClass:[NSDictionary class]]) { 134 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSDictionary ,值为 %@",key,value); 135 | return nil; 136 | } 137 | return value; 138 | } 139 | 140 | - (UIImage *)imageForKey:(NSString *)key { 141 | NSParameterAssert([key isKindOfClass:[NSString class]]); 142 | 143 | AXEBaseData *data = [_storedDatas objectForKey:key]; 144 | UIImage *value = data.value; 145 | if (value && ![value isKindOfClass:[UIImage class]]) { 146 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 UIImage ,值为 %@",key,value); 147 | return nil; 148 | } 149 | return value; 150 | } 151 | 152 | 153 | - (NSData *)NSDataForKey:(NSString *)key { 154 | NSParameterAssert([key isKindOfClass:[NSString class]]); 155 | 156 | AXEBaseData *data = [_storedDatas objectForKey:key]; 157 | NSData *value = data.value; 158 | if (value && ![value isKindOfClass:[NSData class]]) { 159 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSData ,值为 %@",key,value); 160 | return nil; 161 | } 162 | return value; 163 | } 164 | 165 | 166 | - (NSDate *)dateForKey:(NSString *)key { 167 | NSParameterAssert([key isKindOfClass:[NSString class]]); 168 | 169 | AXEBaseData *data = [_storedDatas objectForKey:key]; 170 | NSDate *value = data.value; 171 | if (value && ![value isKindOfClass:[NSDate class]]) { 172 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 NSDate ,值为 %@",key,value); 173 | return nil; 174 | } 175 | return value; 176 | } 177 | 178 | 179 | - (BOOL)boolForKey:(NSString *)key { 180 | NSParameterAssert([key isKindOfClass:[NSString class]]); 181 | 182 | AXEBasicTypeData *data = (AXEBasicTypeData *)[_storedDatas objectForKey:key]; 183 | NSNumber *value = data.value; 184 | if (data && data.basicType != AXEDataBasicTypeBoolean) { 185 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 Boolean ,值为 %@",key,value); 186 | return false; 187 | } 188 | return [value boolValue]; 189 | } 190 | 191 | 192 | - (id)modelForKey:(NSString *)key { 193 | NSParameterAssert([key isKindOfClass:[NSString class]]); 194 | 195 | AXEBaseData *data = [_storedDatas objectForKey:key]; 196 | if (!data) { 197 | AXELogTrace(@" 未查到 key值为 %@ 的model数据",key); 198 | return nil; 199 | } 200 | if ([data isKindOfClass:[AXEModelTypeData class]]) { 201 | return data.value; 202 | }else { 203 | AXELogWarn(@" 查找共享数据 key : %@ , 但是数据格式不是 Model类型 ,值为 %@",key,data.value); 204 | return nil; 205 | } 206 | } 207 | 208 | @end 209 | -------------------------------------------------------------------------------- /Axe/Extension/OfflineHtml/AXEOfflineWKWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWKWebViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/19. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEOfflineWKWebViewController.h" 10 | #import "OPOfflineManager.h" 11 | #import "AXEOfflineDownloadView.h" 12 | #import "AXEDefines.h" 13 | 14 | @interface AXEOfflineWKWebViewController() 15 | 16 | @property (nonatomic,copy) NSString *module; 17 | @property (nonatomic,copy) NSString *path; 18 | @property (nonatomic,copy) NSString *page; 19 | @property (nonatomic,strong) NSURL *localURL;// 最终的本地路径。 20 | @property (nonatomic,weak) AXEOfflineDownloadView *downloadView;// 下载进度。 21 | @end 22 | 23 | @implementation AXEOfflineWKWebViewController 24 | 25 | + (instancetype)webViewControllerWithFilePath:(NSString *)path 26 | page:(NSString *)page 27 | inModule:(NSString *)module { 28 | NSParameterAssert([path isKindOfClass:[NSString class]]); 29 | NSParameterAssert([module isKindOfClass:[NSString class]]); 30 | NSParameterAssert(!page || [page isKindOfClass:[NSString class]]); 31 | 32 | AXEOfflineWKWebViewController *webVC = [[self alloc] init]; 33 | webVC.path = path; 34 | webVC.page = page; 35 | webVC.module = module; 36 | return webVC; 37 | } 38 | 39 | - (void)viewDidLoad { 40 | [super viewDidLoad]; 41 | 42 | [self checkUpdate]; 43 | } 44 | 45 | 46 | - (void)checkUpdate { 47 | // 检测离线包模块。 48 | OPOfflineModule *module = [[OPOfflineManager sharedManager] moduleForName:_module]; 49 | // 要检测module ,一般肯定会返回,但是要以防万一。 50 | if (!module) { 51 | // TODO 展示错误页面。 52 | 53 | return; 54 | } 55 | if (module.needCheckUpdate) { 56 | // 如果需要检测更新, 则要展示进度条。 57 | module.delegate = self; 58 | dispatch_async(dispatch_get_main_queue(), ^{ 59 | self->_downloadView = [AXEOfflineDownloadView showInView:self.view]; 60 | [self->_downloadView setErrorHandlerButtonTitle:@"重试" withBlock:^{ 61 | [self checkUpdate]; 62 | }]; 63 | }); 64 | return; 65 | } 66 | // 否则,就可以直接加载页面了。 67 | NSString *url = [NSString stringWithFormat:@"file://%@/%@", module.path, _path]; 68 | 69 | if (_page) { 70 | // 推荐单页面应用。 71 | url = [url stringByAppendingFormat:@"#/%@",_page]; 72 | } 73 | _localURL = [NSURL URLWithString:url]; 74 | 75 | [self loadLocalHtml:module]; 76 | } 77 | 78 | - (void)loadLocalHtml:(OPOfflineModule *)module { 79 | NSString *accessPath = [NSString stringWithFormat:@"file://%@/", module.path]; 80 | NSURL *accessURL = [NSURL URLWithString:accessPath]; 81 | 82 | if (@available(iOS 9.0, *)) { 83 | [self.webView loadFileURL:_localURL allowingReadAccessToURL:accessURL]; 84 | } else { 85 | // 8.x 系统,需要将文件复制到 /temp/www 中 86 | NSFileManager *fileManager= [NSFileManager defaultManager]; 87 | NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"www"]; 88 | NSString *package = [accessURL lastPathComponent]; 89 | NSString *distPath = [tmpPath stringByAppendingPathComponent:package]; 90 | if (![fileManager fileExistsAtPath:tmpPath]) { 91 | [fileManager createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:nil]; 92 | } 93 | if (![fileManager fileExistsAtPath:[tmpPath stringByAppendingPathComponent:package]]) { 94 | // 复制文件 95 | [fileManager copyItemAtPath:module.path toPath:distPath error:nil]; 96 | } 97 | 98 | NSString *newpath = [NSString stringWithFormat:@"file://%@/index.html#/%@", distPath, _page]; 99 | NSURLRequest *requst = [NSURLRequest requestWithURL:[NSURL URLWithString:newpath]]; 100 | [self.webView loadRequest:requst]; 101 | } 102 | } 103 | 104 | 105 | #pragma mark - AXEOfflineDownloadView 106 | 107 | - (void)module:(OPOfflineModule *)module didDownloadProgress:(NSInteger)progress { 108 | [_downloadView didDownloadProgress:progress]; 109 | } 110 | 111 | 112 | - (void)moduleDidFinishDownload:(OPOfflineModule *)module { 113 | [_downloadView didFinishLoadSuccess]; 114 | NSString *url = [NSString stringWithFormat:@"file://%@/%@", module.path, _path]; 115 | if (_page) { 116 | // 推荐单页面应用。 117 | url = [url stringByAppendingFormat:@"/#/%@",_page]; 118 | } 119 | _localURL = [NSURL URLWithString:url]; 120 | 121 | [self loadLocalHtml:module]; 122 | } 123 | 124 | - (void)module:(OPOfflineModule *)module didFailLoadWithError:(NSError *)error { 125 | [_downloadView didFinishLoadFailed]; 126 | } 127 | 128 | #pragma mark - router register 129 | 130 | + (void)registerWKWebVIewForOfflineHtml { 131 | [[AXERouter sharedRouter] registerProtocol:@"ophttp" withJumpRoute:^(AXERouteRequest *request) { 132 | UINavigationController *navigation; 133 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 134 | navigation = (UINavigationController *)request.fromVC; 135 | }else if(request.fromVC.navigationController) { 136 | navigation = request.fromVC.navigationController; 137 | } 138 | if (navigation) { 139 | // 对于 跳转路由, 自动在执行回调时关闭页面。 140 | if (request.callback) { 141 | UIViewController *topVC = navigation.topViewController; 142 | AXERouteCallbackBlock originCallback = request.callback; 143 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 144 | [navigation popToViewController:topVC animated:YES]; 145 | originCallback(data); 146 | }; 147 | request.callback = autoCloseCallback; 148 | } 149 | // 解析URL 150 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 151 | NSString *module = urlComponets.host; 152 | if (!module) { 153 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 154 | return; 155 | } 156 | NSString *page = urlComponets.path; 157 | NSString *path = module; 158 | if (page.length > 1) { 159 | path = [module stringByAppendingString:page]; 160 | page = [page substringFromIndex:1]; 161 | } else { 162 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 163 | return; 164 | } 165 | request.module = module; 166 | request.path = path; 167 | request.page = page; 168 | // filePath 固定为 index.html. 169 | AXEOfflineWKWebViewController *controller = [AXEOfflineWKWebViewController webViewControllerWithFilePath:@"index.html" page:page inModule:module]; 170 | controller.hidesBottomBarWhenPushed = YES; 171 | controller.routeRequest = request; 172 | [navigation pushViewController:controller animated:YES]; 173 | }else { 174 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 175 | } 176 | }]; 177 | [[AXERouter sharedRouter] registerProtocol:@"ophttp" withViewRoute:^UIViewController *(AXERouteRequest *request) { 178 | // 解析URL 179 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 180 | NSString *module = urlComponets.host; 181 | if (!module) { 182 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 183 | return nil; 184 | } 185 | NSString *page = urlComponets.path; 186 | NSString *path = module; 187 | if (page.length > 1) { 188 | path = [module stringByAppendingString:page]; 189 | page = [page substringFromIndex:1]; 190 | } else { 191 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 192 | return nil; 193 | } 194 | request.module = module; 195 | request.path = path; 196 | request.page = page; 197 | // filePath 固定为 index.html. 198 | AXEOfflineWKWebViewController *controller = [AXEOfflineWKWebViewController webViewControllerWithFilePath:@"index.html" page:page inModule:module]; 199 | controller.routeRequest = request; 200 | return controller; 201 | }]; 202 | } 203 | @end 204 | 205 | -------------------------------------------------------------------------------- /Axe/Extension/React/AXEReactViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEReactViewController.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/13. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEReactViewController.h" 10 | #import 11 | #import "AXEDefines.h" 12 | #import "Axe.h" 13 | #import "AXEReactControllerWrapper.h" 14 | 15 | #import "AXEData+JavaScriptSupport.h" 16 | 17 | NSString *const AXEReactHttpProtocol = @"react"; 18 | NSString *const AXEReactHttpsProtocol = @"reacts"; 19 | NSString *const AXEReactModuleNameKey = @"module"; 20 | 21 | @interface AXEReactViewController () 22 | 23 | 24 | @property (nonatomic,copy) NSString *module; 25 | @property (nonatomic,copy) NSString *url; 26 | 27 | @end 28 | 29 | static void (^CustomViewDidLoadBlock)(AXEReactViewController *); 30 | 31 | @implementation AXEReactViewController 32 | 33 | + (void)setCustomViewDidLoadBlock:(void(^)(AXEReactViewController *))block { 34 | CustomViewDidLoadBlock = [block copy]; 35 | } 36 | 37 | + (instancetype)controllerWithURL:(NSString *)url moduleName:(NSString *)moduleName { 38 | NSParameterAssert(!url || [url isKindOfClass:[NSString class]]); 39 | NSParameterAssert([moduleName isKindOfClass:[NSString class]]); 40 | 41 | AXEReactViewController *controller = [[self alloc] init]; 42 | controller.url = url; 43 | controller.module = moduleName; 44 | return controller; 45 | } 46 | 47 | 48 | - (void)viewDidLoad { 49 | [super viewDidLoad]; 50 | self.edgesForExtendedLayout = UIRectEdgeNone; 51 | self.extendedLayoutIncludesOpaqueBars = false; 52 | self.view.backgroundColor = [UIColor whiteColor]; 53 | if (_url) { 54 | [self loadReactWithBundleURL:_url moduleName:_module]; 55 | } 56 | 57 | if (CustomViewDidLoadBlock) { 58 | CustomViewDidLoadBlock(self); 59 | } 60 | } 61 | 62 | - (void)loadReactWithBundleURL:(NSString *)urlStr moduleName:(NSString *)moduleName { 63 | if (_rctRootView) { 64 | [_rctRootView removeFromSuperview]; 65 | } 66 | 67 | _url = urlStr; 68 | _module = moduleName; 69 | AXEReactControllerWrapper *wrapper = [AXEReactControllerWrapper wrapperWithController:self]; 70 | NSURL *url = [NSURL URLWithString:urlStr]; 71 | NSDictionary *launchOptions = @{AXEReactControllerWrapperKey : wrapper}; 72 | _rctRootView = [[RCTRootView alloc] initWithBundleURL:url 73 | moduleName:_module 74 | initialProperties:nil 75 | launchOptions:launchOptions]; 76 | 77 | [self.view addSubview:_rctRootView]; 78 | 79 | _rctRootView.translatesAutoresizingMaskIntoConstraints = NO; 80 | NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_rctRootView); 81 | NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[_rctRootView]-0-|" 82 | options:0 metrics:nil views:viewsDictionary]; 83 | [self.view addConstraints:constraints]; 84 | constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[_rctRootView]-0-|" 85 | options:0 metrics:nil views:viewsDictionary]; 86 | [self.view addConstraints:constraints]; 87 | } 88 | 89 | 90 | 91 | #pragma mark - router register 92 | + (void)registerReactProtocol { 93 | [[AXERouter sharedRouter] registerProtocol:AXEReactHttpProtocol withJumpRoute:^(AXERouteRequest *request) { 94 | UINavigationController *navigation; 95 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 96 | navigation = (UINavigationController *)request.fromVC; 97 | }else if(request.fromVC.navigationController) { 98 | navigation = request.fromVC.navigationController; 99 | } 100 | if (navigation) { 101 | // 对于 跳转路由, 自动在执行回调时关闭页面。 102 | if (request.callback) { 103 | UIViewController *topVC = navigation.topViewController; 104 | AXERouteCallbackBlock originCallback = request.callback; 105 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 106 | [navigation popToViewController:topVC animated:YES]; 107 | originCallback(data); 108 | }; 109 | request.callback = autoCloseCallback; 110 | } 111 | // 对于react 协议的请求。 如 react://localhost:8081/index.bundle?platform=ios&module=register 112 | // 我们约定, URL中必须声明 module ,以表示 单页面中的具体展示页面。 113 | NSString *module = [request.params stringForKey:AXEReactModuleNameKey]; 114 | NSParameterAssert([module isKindOfClass:[NSString class]]); 115 | // 将 react -> http 116 | NSString *url = [request.currentURL stringByReplacingCharactersInRange:NSMakeRange(0, 5) withString:@"http"]; 117 | AXEReactViewController *controller = [AXEReactViewController controllerWithURL:url moduleName:module]; 118 | controller.routeRequest = request; 119 | controller.hidesBottomBarWhenPushed = YES; 120 | [navigation pushViewController:controller animated:YES]; 121 | }else { 122 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 123 | } 124 | }]; 125 | [[AXERouter sharedRouter] registerProtocol:AXEReactHttpProtocol withViewRoute:^UIViewController *(AXERouteRequest *request) { 126 | NSString *module = [request.params stringForKey:AXEReactModuleNameKey]; 127 | NSParameterAssert([module isKindOfClass:[NSString class]]); 128 | // 将 react -> http 129 | NSString *url = [request.currentURL stringByReplacingCharactersInRange:NSMakeRange(0, 5) withString:@"http"]; 130 | AXEReactViewController *controller = [AXEReactViewController controllerWithURL:url moduleName:module]; 131 | controller.routeRequest = request; 132 | return controller; 133 | }]; 134 | } 135 | 136 | + (void)registerReactsProtocol { 137 | [[AXERouter sharedRouter] registerProtocol:AXEReactHttpsProtocol withJumpRoute:^(AXERouteRequest *request) { 138 | UINavigationController *navigation; 139 | if ([request.fromVC isKindOfClass:[UINavigationController class]]) { 140 | navigation = (UINavigationController *)request.fromVC; 141 | }else if(request.fromVC.navigationController) { 142 | navigation = request.fromVC.navigationController; 143 | } 144 | if (navigation) { 145 | // 对于 跳转路由, 自动在执行回调时关闭页面。 146 | if (request.callback) { 147 | UIViewController *topVC = navigation.topViewController; 148 | AXERouteCallbackBlock originCallback = request.callback; 149 | AXERouteCallbackBlock autoCloseCallback = ^(AXEData *data) { 150 | [navigation popToViewController:topVC animated:YES]; 151 | originCallback(data); 152 | }; 153 | request.callback = autoCloseCallback; 154 | } 155 | // 对于react 协议的请求。 如 reacts://localhost:8081/index.bundle?platform=ios&module=register 156 | // 我们约定, URL中必须声明 module ,以表示 单页面中的具体展示页面。 157 | NSString *module = [request.params stringForKey:AXEReactModuleNameKey]; 158 | NSParameterAssert([module isKindOfClass:[NSString class]]); 159 | // 将 reacts -> https 160 | NSString *url = [request.currentURL stringByReplacingCharactersInRange:NSMakeRange(0, 6) withString:@"https"]; 161 | AXEReactViewController *controller = [AXEReactViewController controllerWithURL:url moduleName:module]; 162 | controller.routeRequest = request; 163 | controller.hidesBottomBarWhenPushed = YES; 164 | [navigation pushViewController:controller animated:YES]; 165 | }else { 166 | AXELogWarn(@"当前 fromVC 设置有问题,无法进行跳转 !!!fromVC : %@",request.fromVC); 167 | } 168 | }]; 169 | [[AXERouter sharedRouter] registerProtocol:AXEReactHttpsProtocol withViewRoute:^UIViewController *(AXERouteRequest *request) { 170 | NSString *module = [request.params stringForKey:AXEReactModuleNameKey]; 171 | NSParameterAssert([module isKindOfClass:[NSString class]]); 172 | // 将 reacts -> https 173 | NSString *url = [request.currentURL stringByReplacingCharactersInRange:NSMakeRange(0, 6) withString:@"https"]; 174 | AXEReactViewController *controller = [AXEReactViewController controllerWithURL:url moduleName:module]; 175 | controller.routeRequest = request; 176 | return controller; 177 | }]; 178 | } 179 | 180 | 181 | 182 | @end 183 | 184 | -------------------------------------------------------------------------------- /Axe/Extension/Html/AXEWebViewBridge.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEWebViewBridge.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/10. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEWebViewBridge.h" 10 | #import "WebViewJavascriptBridge.h" 11 | #import "WKWebViewJavascriptBridge.h" 12 | #import "AXEDefines.h" 13 | #import "AXEBasicTypeData.h" 14 | #import "AXEJavaScriptModelData.h" 15 | #import "AXEData+JavaScriptSupport.h" 16 | #import "AXEEvent.h" 17 | 18 | 19 | @interface AXEWebViewBridge() 20 | 21 | /** 22 | 当前注册的事件。 23 | */ 24 | @property (nonatomic,strong) NSMutableDictionary *registeredEvents; 25 | 26 | @end 27 | 28 | @implementation AXEWebViewBridge 29 | 30 | + (instancetype)bridgeWithUIWebView:(UIWebView *)webView { 31 | NSParameterAssert([webView isKindOfClass:[UIWebView class]]); 32 | 33 | return [[self alloc] initWithWebView:webView]; 34 | } 35 | 36 | + (instancetype)bridgeWithWKWebView:(WKWebView *)webView { 37 | NSParameterAssert([webView isKindOfClass:[WKWebView class]]); 38 | 39 | return [[self alloc] initWithWebView:webView]; 40 | } 41 | 42 | - (instancetype)initWithWebView:(UIView *)webView { 43 | if (self = [super init]) { 44 | _registeredEvents = [[NSMutableDictionary alloc] init]; 45 | if ([webView isKindOfClass:[UIWebView class]]) { 46 | _javascriptBridge = (id) [WebViewJavascriptBridge bridgeForWebView:webView]; 47 | }else if ([webView isKindOfClass:[WKWebView class]]) { 48 | _javascriptBridge = (id) [WKWebViewJavascriptBridge bridgeForWebView:(WKWebView *)webView]; 49 | } 50 | [self setupBrige]; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)setupBrige { 56 | @weakify(self); 57 | // 初始化 jsbridge ,注入相关方法。 58 | // 设置共享数据数据 59 | [_javascriptBridge registerHandler:@"axe_data_set" handler:^(id data, WVJBResponseCallback responseCallback) { 60 | if ([data isKindOfClass:[NSDictionary class]]) { 61 | [[AXEData sharedData] setJavascriptData:data forKey:[data objectForKey:@"key"]]; 62 | } else { 63 | AXELogWarn(@"axe_data_set 应该传入 Object/NSDictionary 类型数据"); 64 | } 65 | }]; 66 | [_javascriptBridge registerHandler:@"axe_data_remove" handler:^(id data, WVJBResponseCallback responseCallback) { 67 | if ([data isKindOfClass:[NSString class]]) { 68 | [[AXEData sharedData] removeDataForKey:data]; 69 | } else { 70 | AXELogWarn(@"axe_data_remove 应该传入 string 类型数据"); 71 | } 72 | }]; 73 | [_javascriptBridge registerHandler:@"axe_data_get" handler:^(id data, WVJBResponseCallback responseCallback) { 74 | if ([data isKindOfClass:[NSString class]]) { 75 | NSDictionary *value = [[AXEData sharedData] javascriptDataForKey:data]; 76 | if (value) { 77 | responseCallback(value); 78 | }else { 79 | responseCallback([NSNull null]); 80 | } 81 | } else { 82 | AXELogWarn(@"axe_data_get 应该传入 string 类型数据"); 83 | } 84 | 85 | }]; 86 | 87 | 88 | // 事件通知 89 | [_javascriptBridge registerHandler:@"axe_event_register" handler:^(id data, WVJBResponseCallback responseCallback) { 90 | NSString *eventName = data; 91 | if ([eventName isKindOfClass:[NSString class]]) { 92 | @strongify(self); 93 | if ([self->_registeredEvents objectForKey:eventName]) { 94 | // 重复注册的问题,一要有工具,二要有好的开发习惯。 95 | AXELogWarn(@"重复监听 !!!"); 96 | id disposable = [self->_registeredEvents objectForKey:eventName]; 97 | [disposable dispose]; 98 | } 99 | // 注册 UI 监听。 100 | id disposable = [self.webviewController registerUIEvent:eventName withHandler:^(AXEData *payload) { 101 | NSMutableDictionary *post = [[NSMutableDictionary alloc] init]; 102 | [post setObject:eventName forKey:@"name"]; 103 | if (payload) { 104 | // 如果有附带数据,则进行转换。 105 | // 转换图片,阻塞主线程,所以大型图片,还是不要往JS里面传了,传些小头像就差不多了。 106 | NSDictionary *javascriptData = [AXEData javascriptDataFromAXEData:payload]; 107 | if ([javascriptData isKindOfClass:[NSDictionary class]]) { 108 | [post setObject:javascriptData forKey:@"payload"]; 109 | } 110 | } 111 | [self->_javascriptBridge callHandler:@"axe_event_callback" data:post]; 112 | }]; 113 | [self->_registeredEvents setObject:disposable forKey:eventName]; 114 | } else { 115 | AXELogWarn(@"axe_event_register 应该传入 string 类型数据"); 116 | } 117 | }]; 118 | [_javascriptBridge registerHandler:@"axe_event_remove" handler:^(id data, WVJBResponseCallback responseCallback) { 119 | @strongify(self); 120 | if ([data isKindOfClass:[NSString class]]) { 121 | // 取消监听 122 | id disposable = self->_registeredEvents[data]; 123 | if (disposable) { 124 | [self->_registeredEvents removeObjectForKey:data]; 125 | [disposable dispose]; 126 | } 127 | } else { 128 | AXELogWarn(@"axe_event_remove 应该传入 string 类型数据"); 129 | } 130 | 131 | }]; 132 | [_javascriptBridge registerHandler:@"axe_event_post" handler:^(id data, WVJBResponseCallback responseCallback) { 133 | if ([data isKindOfClass:[NSDictionary class]]) { 134 | NSDictionary *payload = [data objectForKey:@"data"]; 135 | AXEData *payloadData; 136 | if (payload) { 137 | payloadData = [AXEData axeDataFromJavascriptData:payload]; 138 | } 139 | [AXEEvent postEventName:[data objectForKey:@"name"] withPayload:payloadData]; 140 | } else { 141 | AXELogWarn(@"axe_event_post 应该传入 Object/NSDictionary 类型数据"); 142 | } 143 | }]; 144 | 145 | 146 | // 路由跳转 147 | [_javascriptBridge registerHandler:@"axe_router_route" handler:^(id data, WVJBResponseCallback responseCallback) { 148 | @strongify(self); 149 | if ([data isKindOfClass:[NSDictionary class]]) { 150 | NSString *url = [data objectForKey:@"url"]; 151 | NSDictionary *param = [data objectForKey:@"param"]; 152 | AXEData *payload; 153 | if (param) { 154 | payload = [AXEData axeDataFromJavascriptData:param]; 155 | } 156 | // 是否有回调。 157 | AXERouteCallbackBlock callback; 158 | BOOL needCallback = [data objectForKey:@"callback"]; 159 | if (needCallback) { 160 | callback = ^(AXEData *returnData) { 161 | NSDictionary *returnPayload; 162 | if (returnData) { 163 | returnPayload = [AXEData javascriptDataFromAXEData:returnData]; 164 | } 165 | responseCallback(returnPayload); 166 | }; 167 | } 168 | [self.webviewController jumpTo:url withParams:payload finishBlock:callback]; 169 | } else { 170 | AXELogWarn(@"axe_router_route 应该传入 Object/NSDictionary 类型数据"); 171 | } 172 | }]; 173 | // 路由回调。 174 | [_javascriptBridge registerHandler:@"axe_router_callback" handler:^(id data, WVJBResponseCallback responseCallback) { 175 | @strongify(self); 176 | AXERouteCallbackBlock callback = self.webviewController.routeRequest.callback; 177 | if (!callback) { 178 | // 如果当前没有设置回调,则表示 这里不能进行回调, 业务模块间的交互定义存在问题。 179 | AXELogWarn(@"H5模块调用路由回调, 但是调用者 %@ 并没有设置回调 。 请检测业务逻辑!!!", self.webviewController.routeRequest.fromVC); 180 | return; 181 | } 182 | AXEData *payload; 183 | if ([data isKindOfClass:[NSDictionary class]]) { 184 | payload = [AXEData axeDataFromJavascriptData:data]; 185 | } 186 | callback(payload); 187 | }]; 188 | // 获取路由信息,即参数以及来源。 189 | [_javascriptBridge registerHandler:@"axe_router_source" handler:^(id data, WVJBResponseCallback responseCallback) { 190 | @strongify(self); 191 | NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:2]; 192 | if (self.webviewController.routeRequest.params) { 193 | NSDictionary *javascriptData = [AXEData javascriptDataFromAXEData:self.webviewController.routeRequest.params]; 194 | [ret setObject:javascriptData forKey:@"payload"]; 195 | } 196 | if (self.webviewController.routeRequest.callback) { 197 | [ret setObject:@"true" forKey:@"needCallback"]; 198 | }else { 199 | [ret setObject:@"false" forKey:@"needCallback"]; 200 | } 201 | responseCallback(ret); 202 | }]; 203 | } 204 | 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /Axe/Axe/Router/AXERouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXERouter.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXERouter.h" 10 | #import "AXERouteRequest.h" 11 | #import "AXERouteDefinition.h" 12 | #import "AXERouteProtocolDefinition.h" 13 | #import "AXEDefines.h" 14 | 15 | @interface AXERouter() 16 | // 跳转路由 17 | @property (nonatomic,strong) NSMutableDictionary *protocolJumpRoutes; 18 | @property (nonatomic,strong) NSMutableDictionary *jumpRoutes; 19 | // 视图路由。 20 | @property (nonatomic,strong) NSMutableDictionary *protocolViewRoutes; 21 | @property (nonatomic,strong) NSMutableDictionary *viewRoutes; 22 | 23 | @property (nonatomic,strong) NSMutableArray *preprocesses; 24 | 25 | // 上次路由跳转时间, 在跳转路由上做一个简单的控制,时间间隔小于 0.3秒,就不跳转了。 避免用户频繁点击与响应速度较慢导致的多页面同时弹出的不合理情况。 26 | @property (nonatomic,strong) NSDate *lastJumpTime; 27 | 28 | @end 29 | 30 | @implementation AXERouter 31 | 32 | - (instancetype)init { 33 | if (self = [super init]) { 34 | _protocolJumpRoutes = [[NSMutableDictionary alloc] initWithCapacity:10]; 35 | _jumpRoutes = [[NSMutableDictionary alloc] initWithCapacity:100]; 36 | _protocolViewRoutes = [[NSMutableDictionary alloc] initWithCapacity:10]; 37 | _viewRoutes = [[NSMutableDictionary alloc] initWithCapacity:100]; 38 | _preprocesses = [[NSMutableArray alloc] initWithCapacity:10]; 39 | _lastJumpTime = [NSDate dateWithTimeIntervalSince1970:0]; 40 | } 41 | return self; 42 | } 43 | 44 | + (instancetype)sharedRouter { 45 | static AXERouter *router; 46 | static dispatch_once_t onceToken; 47 | dispatch_once(&onceToken, ^{ 48 | router = [[AXERouter alloc] init]; 49 | }); 50 | return router; 51 | } 52 | 53 | #pragma mark - register 54 | 55 | - (void)registerPath:(NSString *)path withJumpRoute:(AXEJumpRouteHandler)handler { 56 | NSParameterAssert([path isKindOfClass:[NSString class]]); 57 | NSParameterAssert(handler); 58 | NSAssert(![_jumpRoutes objectForKey:path], @"当前路径 %@ 已被注册,请检查!!",path); 59 | 60 | AXERouteDefinition *definition = [AXERouteDefinition defineJumpRouteForPath:path withRouteHandler:handler]; 61 | [_jumpRoutes setObject:definition forKey:path]; 62 | } 63 | 64 | - (void)registerPath:(NSString *)path withViewRoute:(AXEViewRouteHandler)handler { 65 | NSParameterAssert([path isKindOfClass:[NSString class]]); 66 | NSParameterAssert(handler); 67 | NSAssert(![_viewRoutes objectForKey:path], @"当前路径 %@ 已被注册,请检查!!",path); 68 | 69 | AXERouteDefinition *definition = [AXERouteDefinition defineViewRouteForPath:path withRouteHandler:handler]; 70 | [_viewRoutes setObject:definition forKey:path]; 71 | } 72 | 73 | - (void)registerProtocol:(NSString *)protocol withJumpRoute:(AXEProtoclJumpRouterBlock)handler { 74 | NSParameterAssert([protocol isKindOfClass:[NSString class]]); 75 | NSParameterAssert(handler); 76 | NSAssert(![_protocolJumpRoutes objectForKey:protocol], @"当前协议 %@ 已被注册,请检查!!",protocol); 77 | 78 | AXERouteProtocolDefinition *definition = [AXERouteProtocolDefinition defineJumpRouteForProtocol:protocol withRouteHandler:handler]; 79 | [_protocolJumpRoutes setObject:definition forKey:protocol]; 80 | } 81 | 82 | 83 | - (void)registerProtocol:(NSString *)protocol withViewRoute:(AXEProtoclViewRouterBlock)handler { 84 | NSParameterAssert([protocol isKindOfClass:[NSString class]]); 85 | NSParameterAssert(handler); 86 | NSAssert(![_protocolViewRoutes objectForKey:protocol], @"当前协议 %@ 已被注册,请检查!!",protocol); 87 | 88 | AXERouteProtocolDefinition *definition = [AXERouteProtocolDefinition defineViewRouteForProtocol:protocol withRouteHandler:handler]; 89 | [_protocolViewRoutes setObject:definition forKey:protocol]; 90 | } 91 | 92 | 93 | 94 | #pragma mark - route 95 | 96 | static NSTimeInterval const RouteMinInterval = 0.3; 97 | 98 | - (void)jumpTo:(NSString *)url fromViewController:(UIViewController *)fromVC withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block { 99 | NSParameterAssert([url isKindOfClass:[NSString class]]); 100 | NSParameterAssert([fromVC isKindOfClass:[UIViewController class]]); 101 | NSParameterAssert(!params || [params isKindOfClass:[AXEData class]]); 102 | 103 | NSDate *now = [NSDate date]; 104 | if ([now timeIntervalSinceDate:_lastJumpTime] < RouteMinInterval) { 105 | AXELogWarn(@"这里做一个简单防误点的功能, 防止由于系统反应慢,导致用户多次点击按钮,导致页面多次弹出"); 106 | return; 107 | } 108 | _lastJumpTime = now; 109 | 110 | AXERouteRequest *request = [AXERouteRequest requestWithSourceURL:url params:params callback:block fromVC:fromVC]; 111 | [_preprocesses enumerateObjectsUsingBlock:^(AXERoutePreprocessBlock _Nonnull preprocess, NSUInteger idx, BOOL * _Nonnull stop) { 112 | preprocess(request); 113 | }]; 114 | if (request.valid) { 115 | [self jumpForRequest:request]; 116 | } 117 | } 118 | 119 | - (void)jumpTo:(NSString *)url fromViewController:(UIViewController *)vc { 120 | return [self jumpTo:url fromViewController:vc withParams:nil finishBlock:nil]; 121 | } 122 | 123 | - (void)jumpForRequest:(AXERouteRequest *)request { 124 | // 首先查看协议。 125 | if ([request.protocol isEqualToString:AXERouterProtocolName]) { 126 | // 如果是axe协议, 先把模块、页面信息解析一下。 127 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 128 | NSString *module = urlComponets.host; 129 | if (!module) { 130 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 131 | return; 132 | } 133 | NSString *page = urlComponets.path; 134 | NSString *path = module; 135 | if (page.length > 1) { 136 | path = [module stringByAppendingString:page]; 137 | page = [page substringFromIndex:1]; 138 | } 139 | request.module = module; 140 | request.path = path; 141 | request.page = page; 142 | 143 | AXERouteDefinition *definition = _jumpRoutes[request.path]; 144 | if (!definition) { 145 | AXELogWarn(@"当前未支持路由跳转: %@",request.currentURL); 146 | }else { 147 | [definition jumpForRequest:request]; 148 | } 149 | }else { 150 | // 在协议注册中寻找 151 | AXERouteProtocolDefinition *definition = _protocolJumpRoutes[request.protocol]; 152 | if (!definition) { 153 | AXELogWarn(@"当前未支持协议: %@",request.protocol); 154 | }else{ 155 | [definition jumpForRequest:request]; 156 | } 157 | } 158 | } 159 | 160 | - (UIViewController *)viewForURL:(NSString *)url withParams:(AXEData *)params finishBlock:(AXERouteCallbackBlock)block { 161 | NSParameterAssert([url isKindOfClass:[NSString class]]); 162 | NSParameterAssert(!params || [params isKindOfClass:[AXEData class]]); 163 | 164 | AXERouteRequest *request = [AXERouteRequest requestWithSourceURL:url params:params callback:block fromVC:nil]; 165 | [_preprocesses enumerateObjectsUsingBlock:^(AXERoutePreprocessBlock _Nonnull preprocess, NSUInteger idx, BOOL * _Nonnull stop) { 166 | preprocess(request); 167 | }]; 168 | if (request.valid) { 169 | if ([request.protocol isEqualToString:AXERouterProtocolName]) { 170 | // 如果是axe协议, 先把模块、页面信息解析一下。 171 | NSURLComponents *urlComponets = [NSURLComponents componentsWithString:request.currentURL]; 172 | NSString *module = urlComponets.host; 173 | if (!module) { 174 | AXELogWarn(@"当前URL 设置出错! %@",request.currentURL); 175 | return nil; 176 | } 177 | NSString *page = urlComponets.path; 178 | NSString *path = module; 179 | if (page.length > 1) { 180 | path = [module stringByAppendingString:page]; 181 | page = [page substringFromIndex:1]; 182 | } 183 | request.module = module; 184 | request.path = path; 185 | request.page = page; 186 | 187 | AXERouteDefinition *definition = _viewRoutes[request.path]; 188 | if (!definition) { 189 | AXELogWarn(@"当前未支持路由跳转: %@",request.currentURL); 190 | return nil; 191 | }else { 192 | return [definition viewForRequest:request]; 193 | } 194 | }else { 195 | // 在协议注册中寻找 196 | AXERouteProtocolDefinition *definition = _protocolViewRoutes[request.protocol]; 197 | if (!definition) { 198 | AXELogWarn(@"当前未支持协议: %@",request.protocol); 199 | return nil; 200 | }else{ 201 | return [definition viewForRequest:request]; 202 | } 203 | } 204 | }else { 205 | AXELogWarn(@"当前 request : %@ 检测失败 ,无法跳转",request); 206 | return nil; 207 | } 208 | } 209 | 210 | - (UIViewController *)viewForURL:(NSString *)url { 211 | return [self viewForURL:url withParams:nil finishBlock:nil]; 212 | } 213 | 214 | 215 | 216 | - (void)addPreprocess:(AXERoutePreprocessBlock)block { 217 | NSParameterAssert(block); 218 | 219 | [_preprocesses addObject:[block copy]]; 220 | } 221 | 222 | @end 223 | 224 | 225 | // 默认协议名称为 axe 。 226 | NSString *AXERouterProtocolName = @"axe"; 227 | -------------------------------------------------------------------------------- /Axe/Extension/JavascriptSupport/AXEData+JavaScriptSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEData+JavaScriptSupport.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/11. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEData+JavaScriptSupport.h" 10 | #import "AXEJavaScriptModelData.h" 11 | #import "AXEDefines.h" 12 | #import "AXEBasicTypeData.h" 13 | #import 14 | 15 | @interface AXEBaseData (JavaScriptSupport) 16 | //储存原始数据, 以避免 js模块互相传递数据时的额外消耗。 17 | @property (nonatomic,strong) NSDictionary *javascriptData; 18 | @end 19 | 20 | @implementation AXEBaseData(JavaScriptSupport) 21 | - (void)setJavascriptData:(NSDictionary *)raw { 22 | objc_setAssociatedObject(self, @selector(javascriptData), raw, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 23 | } 24 | 25 | - (NSDictionary *)javascriptData { 26 | return objc_getAssociatedObject(self, @selector(javascriptData)); 27 | } 28 | @end 29 | 30 | 31 | // 获取私有接口。 32 | @interface AXEData(JavaScriptSupportPrivate) 33 | @property (nonatomic,strong) NSMutableDictionary *storedDatas; 34 | @end 35 | 36 | @implementation AXEData(JavaScriptSupport) 37 | 38 | - (void)setJavascriptData:(NSDictionary *)data forKey:(NSString *)key{ 39 | if ([data isKindOfClass:[NSDictionary class]] && [key isKindOfClass:[NSString class]]) { 40 | NSString *value = [data objectForKey:@"value"]; 41 | NSString *type = [data objectForKey:@"type"]; 42 | AXEBaseData *saved; 43 | if ([type isEqualToString:@"Number"]) { 44 | saved = [AXEBasicTypeData basicDataWithNumber:[NSDecimalNumber decimalNumberWithString:value]]; 45 | }else if ([type isEqualToString:@"String"]) { 46 | saved = [AXEBasicTypeData basicDataWithString:value]; 47 | }else if ([type isEqualToString:@"Array"]) { 48 | NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; 49 | NSError *error; 50 | NSArray *list = [NSJSONSerialization JSONObjectWithData:valueData options:0 error:&error]; 51 | if (error || ![list isKindOfClass:[NSArray class]]) { 52 | AXELogWarn(@" 设置AXEData, 设定类型为Array,但是当前数据格式校验错误 。 数据为 %@",data); 53 | return; 54 | } 55 | saved = [AXEBasicTypeData basicDataWithArray:list]; 56 | }else if ([type isEqualToString:@"Object"]) { 57 | NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; 58 | NSError *error; 59 | NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:valueData options:0 error:&error]; 60 | if (error || ![dic isKindOfClass:[NSDictionary class]]) { 61 | AXELogWarn(@" 设置AXEData, 设定类型为Object,但是当前数据格式校验错误 。 数据为 %@",data); 62 | return; 63 | } 64 | saved = [AXEBasicTypeData basicDataWithDictionary:dic]; 65 | }else if([type isEqualToString:@"Image"]) { 66 | // javascript中实际存储的格式为 : data:image/jpeg;base64,xxxx 67 | NSRange range = [value rangeOfString:@";base64,"]; 68 | if (range.location == NSNotFound) { 69 | AXELogWarn(@"设置的图片格式不正确, 需要为 data:image/jpeg;base64, 开头的base64字符串!!"); 70 | return; 71 | } 72 | value = [value substringFromIndex:range.location + range.length]; 73 | NSData *reserved = [[NSData alloc] initWithBase64EncodedString:value options:NSDataBase64DecodingIgnoreUnknownCharacters]; 74 | if ([reserved isKindOfClass:[NSData class]]) { 75 | saved = [AXEBasicTypeData basicDataWithImage:[UIImage imageWithData:reserved]]; 76 | } 77 | }else if ([type isEqualToString:@"Data"]) { 78 | NSData *reserved = [[NSData alloc] initWithBase64EncodedString:value options:NSDataBase64DecodingIgnoreUnknownCharacters]; 79 | if ([reserved isKindOfClass:[NSData class]]) { 80 | saved = [AXEBasicTypeData basicDataWithData:reserved]; 81 | } 82 | }else if ([type isEqualToString:@"Date"]) { 83 | long long time = [value longLongValue]; 84 | NSTimeInterval timeInterval = time / 1000.; 85 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval]; 86 | saved = [AXEBasicTypeData basicDataWithDate:date]; 87 | }else if ([type isEqualToString:@"Boolean"]) { 88 | BOOL boo = [value boolValue]; 89 | saved = [AXEBasicTypeData basicDataWithBoolean:boo]; 90 | } 91 | if (saved) { 92 | [saved setJavascriptData:data]; 93 | [self.storedDatas setObject:saved forKey:key]; 94 | return; 95 | } 96 | if ([type isEqualToString:@"Model"]) { 97 | // model 类型。 还要再特殊处理一下 98 | NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding]; 99 | NSError *error; 100 | NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:valueData options:0 error:&error]; 101 | if (error || ![dic isKindOfClass:[NSDictionary class]]) { 102 | AXELogWarn(@" 设置AXEData, 设定类型为Model,但是当前数据格式校验错误 。 数据为 %@",data); 103 | return; 104 | } 105 | AXEModelTypeData *currentModelData = (AXEModelTypeData *)[self dataForKey:key]; 106 | if ([currentModelData isMemberOfClass:[AXEModelTypeData class]]) { 107 | // 根据传入的json 重新设置model类型的值。 108 | id currentModel = currentModelData.value; 109 | [currentModel axe_modelSetWithJSON:dic]; 110 | }else { 111 | // 否则为 当前无model, 或者是 js的model, 则创建一个新的jsmodel. 112 | AXEJavaScriptModelData *modelData = [AXEJavaScriptModelData javascriptModelWithValue:dic]; 113 | [self.storedDatas setObject:modelData forKey:key]; 114 | } 115 | } 116 | } else { 117 | AXELogWarn(@"setJavascriptData 传入错误的参数 : %@ ",key); 118 | } 119 | } 120 | 121 | 122 | - (NSDictionary *)javascriptDataForKey:(NSString *)key { 123 | if (![key isKindOfClass:[NSString class]]) { 124 | AXELogWarn(@"key 需要为字符串类型!"); 125 | return nil; 126 | } 127 | AXEBaseData *data = [self.storedDatas objectForKey:key]; 128 | if (!data) { 129 | return nil; 130 | } 131 | NSMutableDictionary *javascriptData = [[NSMutableDictionary alloc] initWithCapacity:2]; 132 | // 检测数据类型,并做相应的转换。 133 | if ([data isKindOfClass:[AXEBasicTypeData class]]) { 134 | // 基础数据类型。 135 | if (data.javascriptData) { 136 | // 直接返回, 避免js之间的额外转换 137 | return data.javascriptData; 138 | } 139 | 140 | AXEDataBasicType type = [(AXEBasicTypeData *)data basicType]; 141 | if (type == AXEDataBasicTypeNumber) { 142 | javascriptData[@"type"] = @"Number"; 143 | javascriptData[@"value"] = [data.value stringValue]; 144 | }else if (type == AXEDataBasicTypeString) { 145 | javascriptData[@"type"] = @"String"; 146 | javascriptData[@"value"] = data.value; 147 | }else if (type == AXEDataBasicTypeArray) { 148 | javascriptData[@"type"] = @"Array"; 149 | NSError *error; 150 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data.value options:0 error:&error]; 151 | if (error) { 152 | AXELogWarn(@" javascript 所需要的 Array类型,必须能转换为json, 当前json转换出错 %@",error); 153 | return nil; 154 | } 155 | javascriptData[@"value"] = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 156 | }else if (type == AXEDataBasicTypeDictionary) { 157 | javascriptData[@"type"] = @"Object"; 158 | NSError *error; 159 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data.value options:0 error:&error]; 160 | if (error) { 161 | AXELogWarn(@" javascript 所需要的 Object类型,必须能转换为json, 当前json转换出错 %@",error); 162 | return nil; 163 | } 164 | javascriptData[@"value"] = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 165 | }else if (type == AXEDataBasicTypeUIImage) { 166 | javascriptData[@"type"] = @"Image"; 167 | UIImage *image = data.value; 168 | // 图片固定格式 jpeg 。 169 | NSData *imageData = UIImageJPEGRepresentation(image, 1.0); 170 | NSString *base64Data = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; 171 | javascriptData[@"value"] = [@"data:image/jpeg;base64," stringByAppendingString:base64Data]; 172 | }else if (type == AXEDataBasicTypeData) { 173 | javascriptData[@"type"] = @"Data"; 174 | javascriptData[@"value"] = [data.value base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; 175 | }else if (type == AXEDataBasicTypeDate) { 176 | javascriptData[@"type"] = @"Date"; 177 | NSDate *date = data.value; 178 | long long value = [date timeIntervalSince1970] * 1000; 179 | javascriptData[@"value"] = [@(value) stringValue]; 180 | }else if (type == AXEDataBasicTypeBoolean) { 181 | javascriptData[@"type"] = @"Boolean"; 182 | javascriptData[@"value"] = [data.value boolValue] ? @"true":@"false"; 183 | } 184 | }else if ([data isKindOfClass:[AXEModelTypeData class]]) { 185 | // model类型。 186 | javascriptData[@"type"] = @"Model"; 187 | id model = data.value; 188 | NSDictionary *modeDict = [model axe_modelToJSONObject]; 189 | NSError *error; 190 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:modeDict options:0 error:&error]; 191 | if (error) { 192 | AXELogWarn(@" javascript 所需要的 Model类型,必须能转换为json, 当前json转换出错 %@",error); 193 | return nil; 194 | } 195 | javascriptData[@"value"] = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 196 | } 197 | return javascriptData; 198 | } 199 | 200 | 201 | + (NSDictionary *)javascriptDataFromAXEData:(AXEData *)data { 202 | if ([data isKindOfClass:[AXEData class]]) { 203 | NSMutableDictionary *javascriptData = [[NSMutableDictionary alloc] init]; 204 | [data.storedDatas enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, AXEBaseData * _Nonnull obj, BOOL * _Nonnull stop) { 205 | NSDictionary *singleData = [data javascriptDataForKey:key]; 206 | if (singleData) { 207 | [javascriptData setObject:singleData forKey:key]; 208 | } 209 | }]; 210 | return javascriptData; 211 | }else { 212 | return nil; 213 | } 214 | } 215 | 216 | + (AXEData *)axeDataFromJavascriptData:(NSDictionary *)javascriptData { 217 | if ([javascriptData isKindOfClass:[NSDictionary class]]) { 218 | AXEData *data = [AXEData dataForTransmission]; 219 | [javascriptData enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 220 | [data setJavascriptData:obj forKey:key]; 221 | }]; 222 | return data; 223 | } 224 | return nil; 225 | } 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /Axe/Axe/Event/AXEEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEEvent.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/7. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEEvent.h" 10 | #import "AXEEventListener.h" 11 | #import "AXEEventUserInterfaceState+Event.h" 12 | #import "AXEDefines.h" 13 | 14 | NSInteger const AXEEventDefaultPriority = 1; 15 | 16 | @interface AXEEvent() 17 | 18 | // 串行回调,目前提供两个队列执行. 19 | @property (nonatomic,strong) dispatch_queue_t serialQueueA; 20 | @property (nonatomic,strong) dispatch_queue_t serialQueueB; 21 | @property (nonatomic,assign) BOOL chooseQueueA; 22 | @property (nonatomic,strong) dispatch_semaphore_t queueSemaphore; 23 | 24 | @property (nonatomic,strong) dispatch_queue_t concurrentQueue; 25 | 26 | @property (nonatomic,strong) NSMutableDictionary *listeners; 27 | 28 | @end 29 | 30 | @implementation AXEEvent 31 | 32 | 33 | 34 | + (AXEEvent *)sharedInstance { 35 | static AXEEvent *instance; 36 | static dispatch_once_t onceToken; 37 | dispatch_once(&onceToken, ^{ 38 | instance = [[AXEEvent alloc] init]; 39 | [instance translateDefaultEvents]; 40 | }); 41 | return instance; 42 | } 43 | 44 | - (instancetype)init { 45 | if (self = [super init]) { 46 | // 这里,我们创建了两个序列化的队列,简单地优化一下序列执行,即事件监听会分配到两个队列中,避免一个任务造成的堵塞。 47 | _serialQueueA = dispatch_queue_create("org.axe.event.serail_queue_a", DISPATCH_QUEUE_SERIAL); 48 | _serialQueueB = dispatch_queue_create("org.axe.event.serail_queue_b", DISPATCH_QUEUE_SERIAL); 49 | _chooseQueueA = YES; 50 | _queueSemaphore = dispatch_semaphore_create(1); 51 | _concurrentQueue = dispatch_queue_create("org.axe.event.concurrent_queue", DISPATCH_QUEUE_CONCURRENT); 52 | _listeners = [[NSMutableDictionary alloc] initWithCapacity:100]; 53 | } 54 | return self; 55 | } 56 | 57 | #pragma mark - register 58 | + (id)registerListenerForEventName:(NSString *)name 59 | handler:(AXEEventHandlerBlock)handler { 60 | return [self registerSyncListenerForEventName:name handler:handler priority:AXEEventDefaultPriority]; 61 | } 62 | 63 | 64 | + (id)registerSyncListenerForEventName:(NSString *)name 65 | handler:(AXEEventHandlerBlock)handler 66 | priority:(NSInteger)priority { 67 | NSParameterAssert([name isKindOfClass:[NSString class]]); 68 | NSParameterAssert(handler); 69 | 70 | AXEEventListener *listener = [[AXEEventListener alloc] init]; 71 | listener.eventName = name; 72 | listener.handler = handler; 73 | listener.priority = priority; 74 | listener.asynchronous = NO; 75 | [[AXEEvent sharedInstance] addListener:listener]; 76 | return listener; 77 | } 78 | 79 | + (id)registerAsyncListenerForEventName:(NSString *)name 80 | handler:(AXEEventHandlerBlock)handler 81 | priority:(NSInteger)priority 82 | inSerialQueue:(BOOL)isSerial { 83 | NSParameterAssert([name isKindOfClass:[NSString class]]); 84 | NSParameterAssert(handler); 85 | 86 | AXEEventListener *listener = [[AXEEventListener alloc] init]; 87 | listener.eventName = name; 88 | listener.handler = handler; 89 | listener.priority = priority; 90 | listener.asynchronous = YES; 91 | listener.serial = isSerial; 92 | [[AXEEvent sharedInstance] addListener:listener]; 93 | return listener; 94 | } 95 | 96 | 97 | + (id)registerSerialListenerForEventName:(NSString *)name 98 | handler:(AXEEventHandlerBlock)handler 99 | priority:(NSInteger)priority { 100 | return [self registerAsyncListenerForEventName:name handler:handler priority:priority inSerialQueue:YES]; 101 | } 102 | 103 | 104 | + (id)registerConcurrentListenerForEventName:(NSString *)name 105 | handler:(AXEEventHandlerBlock)handler { 106 | return [self registerAsyncListenerForEventName:name handler:handler priority:AXEEventDefaultPriority inSerialQueue:NO]; 107 | } 108 | 109 | + (id)registerUIListenerForEventName:(NSString *)name 110 | handler:(AXEEventHandlerBlock)handler 111 | inUIContainer:(id)container { 112 | return [self registerUIListenerForEventName:name 113 | handler:handler 114 | priority:AXEEventDefaultPriority 115 | inUIContainer:container]; 116 | } 117 | 118 | + (id)registerUIListenerForEventName:(NSString *)name 119 | handler:(AXEEventHandlerBlock)handler 120 | priority:(NSInteger)priority 121 | inUIContainer:(id)container { 122 | NSParameterAssert([name isKindOfClass:[NSString class]]); 123 | NSParameterAssert(handler); 124 | NSParameterAssert([container performSelector:@selector(AXEContainerStatus)]); 125 | 126 | AXEEventListener *listener = [[AXEEventListener alloc] init]; 127 | listener.eventName = name; 128 | listener.handler = handler; 129 | listener.priority = priority; 130 | listener.asynchronous = YES; 131 | listener.userInterface = YES; 132 | listener.containerStatus = container.AXEContainerStatus; 133 | [[AXEEvent sharedInstance] addListener:listener]; 134 | return listener; 135 | } 136 | 137 | 138 | - (void)addListener:(AXEEventListener *)listener { 139 | @synchronized(self) { 140 | AXEEventListenerPriorityQueue *queue; 141 | queue = _listeners[listener.eventName]; 142 | if (!queue) { 143 | queue = [[AXEEventListenerPriorityQueue alloc] init]; 144 | _listeners[listener.eventName] = queue; 145 | } 146 | [queue insert:listener]; 147 | listener.queue = queue; 148 | } 149 | } 150 | 151 | + (void)removeListenerForEventName:(NSString *)name { 152 | AXEEvent *event = [AXEEvent sharedInstance]; 153 | @synchronized(event) { 154 | [event.listeners removeObjectForKey:name]; 155 | } 156 | } 157 | 158 | #pragma mark - post 159 | 160 | + (void)postEventName:(NSString *)name { 161 | return [[AXEEvent sharedInstance] dispatchEventName:name withPayload:nil]; 162 | } 163 | 164 | + (void)postEventName:(NSString *)name withPayload:(AXEData *)payload { 165 | NSParameterAssert([name isKindOfClass:[NSString class]]); 166 | NSParameterAssert(!payload || [payload isKindOfClass:[AXEData class]]); 167 | 168 | [[AXEEvent sharedInstance] dispatchEventName:name withPayload:payload]; 169 | } 170 | 171 | - (void)dispatchEventName:(NSString *)name withPayload:(AXEData *)payload { 172 | AXEEventListenerPriorityQueue *listeners = _listeners[name]; 173 | AXELogTrace(@"发送事件 %@ 。" , name); 174 | if (listeners) { 175 | NSMutableArray *asyncListeners = [[NSMutableArray alloc] init]; 176 | NSMutableArray *syncListeners = [[NSMutableArray alloc] init]; 177 | [listeners enumerateListenersUsingBlock:^(AXEEventListener *listener) { 178 | if (listener.userInterface) { 179 | // 要在主线程中执行。 180 | if (!listener.containerStatus) { 181 | // 如果state不存在,表示UI被释放,则取消监听. 182 | [listener dispose]; 183 | }else { 184 | // UI线程事件处理。 185 | dispatch_block_t block= ^{ 186 | listener.handler(payload); 187 | }; 188 | if (listener.containerStatus.inFront) { 189 | dispatch_async(dispatch_get_main_queue(), block); 190 | }else { 191 | [listener.containerStatus storeEventName:listener.eventName handlerBlock:block]; 192 | } 193 | } 194 | }else if (!listener.asynchronous) { 195 | [syncListeners addObject:listener]; 196 | }else { 197 | // 异步执行。 198 | if (listener.serial) { 199 | // 序列执行先记录一下。 200 | [asyncListeners addObject:listener]; 201 | }else { 202 | // 并发执行 203 | dispatch_async(self->_concurrentQueue, ^{ 204 | listener.handler(payload); 205 | }); 206 | } 207 | } 208 | }]; 209 | // 先调用好异步任务 210 | if (asyncListeners.count) { 211 | dispatch_queue_t queue = [self getSerailQueue]; 212 | dispatch_async(queue, ^{ 213 | [asyncListeners enumerateObjectsUsingBlock:^(AXEEventListener *listener, NSUInteger idx, BOOL * _Nonnull stop) { 214 | listener.handler(payload); 215 | }]; 216 | }); 217 | } 218 | //然后才执行同步任务 219 | if (syncListeners.count) { 220 | [syncListeners enumerateObjectsUsingBlock:^(AXEEventListener *listener, NSUInteger idx, BOOL * _Nonnull stop) { 221 | listener.handler(payload); 222 | }]; 223 | } 224 | } 225 | } 226 | 227 | 228 | 229 | - (dispatch_queue_t)getSerailQueue { 230 | dispatch_semaphore_wait(_queueSemaphore, DISPATCH_TIME_FOREVER); 231 | dispatch_queue_t queue = _chooseQueueA ? _serialQueueA : _serialQueueB; 232 | _chooseQueueA = !_chooseQueueA; 233 | dispatch_semaphore_signal(_queueSemaphore); 234 | return queue; 235 | } 236 | 237 | #pragma mark - Notification Translate 238 | 239 | + (void)translateNotification:(NSString *)notificationName { 240 | NSParameterAssert([notificationName isKindOfClass:[NSString class]]); 241 | 242 | [[AXEEvent sharedInstance] translateNotification:notificationName]; 243 | } 244 | 245 | - (void)translateNotification:(NSString *)notificationName { 246 | [[NSNotificationCenter defaultCenter] addObserver:self 247 | selector:@selector(dispatchNotification:) 248 | name:notificationName 249 | object:nil]; 250 | } 251 | 252 | 253 | - (void)dispatchNotification:(NSNotification *)notification { 254 | AXEData *data; 255 | if ([notification.userInfo isKindOfClass:[NSDictionary class]]) { 256 | data = [AXEData dataForTransmission]; 257 | [data setData:notification.userInfo forKey:AXEEventNotificationUserInfoKey]; 258 | } 259 | [[AXEEvent sharedInstance] dispatchEventName:notification.name withPayload:data]; 260 | } 261 | 262 | - (void)translateDefaultEvents { 263 | [self translateNotification:UIApplicationDidEnterBackgroundNotification]; 264 | [self translateNotification:UIApplicationWillEnterForegroundNotification]; 265 | [self translateNotification:UIApplicationDidBecomeActiveNotification]; 266 | [self translateNotification:UIApplicationWillResignActiveNotification]; 267 | [self translateNotification:UIApplicationWillTerminateNotification]; 268 | } 269 | @end 270 | 271 | NSString *const AXEEventNotificationUserInfoKey = @"userInfo"; 272 | 273 | -------------------------------------------------------------------------------- /Axe/Extension/Util/AXEOfflineDownloadView.m: -------------------------------------------------------------------------------- 1 | // 2 | // AXEOfflineWebViewDownloadView.m 3 | // Axe 4 | // 5 | // Created by 罗贤明 on 2018/3/20. 6 | // Copyright © 2018年 罗贤明. All rights reserved. 7 | // 8 | 9 | #import "AXEOfflineDownloadView.h" 10 | /** 11 | 渐变色的进度条 12 | */ 13 | @interface AXEOfflineGradientProgressView : UIView 14 | 15 | @property (nonatomic,assign) float progress; 16 | 17 | @property (nonatomic,weak) CAGradientLayer *glayer; 18 | 19 | 20 | 21 | @end 22 | 23 | 24 | 25 | @implementation AXEOfflineGradientProgressView 26 | 27 | 28 | - (void)setProgress:(float)progress { 29 | _progress = progress; 30 | _glayer.frame = CGRectMake(0, 0, self.frame.size.width * _progress , self.frame.size.height); 31 | } 32 | 33 | 34 | - (instancetype)initWithFrame:(CGRect)frame { 35 | if (self = [super initWithFrame:frame]) { 36 | self.backgroundColor = [UIColor colorWithRed:230 / 255.0 green:235 / 255.0 blue:240 / 255.0 alpha:1]; 37 | self.layer.cornerRadius = 4; 38 | _progress = 0.0; 39 | CAGradientLayer *layer = [[CAGradientLayer alloc] init]; 40 | UIColor *startColor = [UIColor colorWithRed:108 / 255.0 green:230 / 255.0 blue:255/255.0 alpha:1]; 41 | UIColor *endColor = [UIColor colorWithRed:96 / 255.0 green:163 / 255.0 blue:255/255.0 alpha:1]; 42 | layer.colors = @[(__bridge id)startColor.CGColor, (__bridge id)endColor.CGColor]; 43 | layer.startPoint = CGPointMake(0, 0.5); 44 | layer.endPoint = CGPointMake(1, 0.5); 45 | layer.locations = @[@0.25,@0.7]; 46 | layer.cornerRadius = 4; 47 | [self.layer addSublayer:layer]; 48 | layer.frame = CGRectZero; 49 | _glayer = layer; 50 | } 51 | return self; 52 | } 53 | @end 54 | 55 | @interface AXEOfflineDownloadView() 56 | 57 | @property (nonatomic,weak) AXEOfflineGradientProgressView *progressView; 58 | @property (nonatomic,weak) UIView *loadingView; 59 | @property (nonatomic,weak) UILabel *title; 60 | @property (nonatomic,copy) NSString *retryTitle; 61 | @property (nonatomic,copy) void (^retryBlock)(void); 62 | 63 | @end 64 | 65 | static AXEOfflineDownloadView *(^implemation)(UIView *); 66 | 67 | @implementation AXEOfflineDownloadView 68 | 69 | + (void)setCustomImplemation:(AXEOfflineDownloadView *(^)(UIView *))block { 70 | implemation = [block copy]; 71 | } 72 | 73 | + (AXEOfflineDownloadView *)showInView:(UIView *)view { 74 | NSParameterAssert([view isKindOfClass:[UIView class]]); 75 | if (implemation) { 76 | return implemation(view); 77 | } 78 | AXEOfflineDownloadView *downloadView = [[AXEOfflineDownloadView alloc] initWithFrame:view.bounds]; 79 | downloadView.retryTitle = @"确认"; 80 | downloadView.retryBlock = ^{}; 81 | 82 | [view addSubview:downloadView]; 83 | downloadView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]; 84 | downloadView.frame = view.bounds; 85 | 86 | UIView *loadingView = [[UIView alloc] init]; 87 | loadingView.backgroundColor = [UIColor whiteColor]; 88 | loadingView.layer.cornerRadius = 4; 89 | loadingView.translatesAutoresizingMaskIntoConstraints = NO; 90 | [downloadView addSubview:loadingView]; 91 | downloadView.loadingView = loadingView; 92 | [downloadView addConstraint:[NSLayoutConstraint constraintWithItem:downloadView 93 | attribute:NSLayoutAttributeCenterX 94 | relatedBy:NSLayoutRelationEqual 95 | toItem:loadingView 96 | attribute:NSLayoutAttributeCenterX 97 | multiplier:1 98 | constant:0]]; 99 | [downloadView addConstraint:[NSLayoutConstraint constraintWithItem:downloadView 100 | attribute:NSLayoutAttributeCenterY 101 | relatedBy:NSLayoutRelationEqual 102 | toItem:loadingView 103 | attribute:NSLayoutAttributeCenterY 104 | multiplier:1 105 | constant:0]]; 106 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:loadingView 107 | attribute:NSLayoutAttributeWidth 108 | relatedBy:NSLayoutRelationEqual 109 | toItem:nil 110 | attribute:NSLayoutAttributeNotAnAttribute 111 | multiplier:1.0f 112 | constant:180.0f]]; 113 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:loadingView 114 | attribute:NSLayoutAttributeHeight 115 | relatedBy:NSLayoutRelationEqual 116 | toItem:nil 117 | attribute:NSLayoutAttributeNotAnAttribute 118 | multiplier:1.0f 119 | constant:90.0f]]; 120 | 121 | 122 | UILabel *title = [[UILabel alloc] init]; 123 | title.textAlignment = NSTextAlignmentCenter; 124 | title.font = [UIFont systemFontOfSize:17]; 125 | title.textColor = [UIColor colorWithRed:128/255. green:128/255. blue:128/255. alpha:1]; 126 | title.translatesAutoresizingMaskIntoConstraints = NO; 127 | title.text = @"资源下载中"; 128 | [loadingView addSubview:title]; 129 | downloadView.title = title; 130 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:title 131 | attribute:NSLayoutAttributeWidth 132 | relatedBy:NSLayoutRelationEqual 133 | toItem:loadingView 134 | attribute:NSLayoutAttributeWidth 135 | multiplier:1.0f 136 | constant:0]]; 137 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:title 138 | attribute:NSLayoutAttributeHeight 139 | relatedBy:NSLayoutRelationEqual 140 | toItem:nil 141 | attribute:NSLayoutAttributeNotAnAttribute 142 | multiplier:1.0f 143 | constant:50.0f]]; 144 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:title 145 | attribute:NSLayoutAttributeLeft 146 | relatedBy:NSLayoutRelationEqual 147 | toItem:loadingView 148 | attribute:NSLayoutAttributeLeft 149 | multiplier:1.0f 150 | constant:0]]; 151 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:title 152 | attribute:NSLayoutAttributeTop 153 | relatedBy:NSLayoutRelationEqual 154 | toItem:loadingView 155 | attribute:NSLayoutAttributeTop 156 | multiplier:1.0f 157 | constant:0]]; 158 | 159 | AXEOfflineGradientProgressView *progressView = [[AXEOfflineGradientProgressView alloc] init]; 160 | progressView.translatesAutoresizingMaskIntoConstraints = NO; 161 | [loadingView addSubview:progressView]; 162 | downloadView.progressView = progressView; 163 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:progressView 164 | attribute:NSLayoutAttributeCenterX 165 | relatedBy:NSLayoutRelationEqual 166 | toItem:loadingView 167 | attribute:NSLayoutAttributeCenterX 168 | multiplier:1.0f 169 | constant:0]]; 170 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:progressView 171 | attribute:NSLayoutAttributeHeight 172 | relatedBy:NSLayoutRelationEqual 173 | toItem:nil 174 | attribute:NSLayoutAttributeNotAnAttribute 175 | multiplier:1.0f 176 | constant:10.0f]]; 177 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:progressView 178 | attribute:NSLayoutAttributeLeft 179 | relatedBy:NSLayoutRelationEqual 180 | toItem:loadingView 181 | attribute:NSLayoutAttributeLeft 182 | multiplier:1.0f 183 | constant:15.f]]; 184 | [loadingView addConstraint:[NSLayoutConstraint constraintWithItem:progressView 185 | attribute:NSLayoutAttributeBottom 186 | relatedBy:NSLayoutRelationEqual 187 | toItem:loadingView 188 | attribute:NSLayoutAttributeBottom 189 | multiplier:1.0f 190 | constant:-15.f]]; 191 | return downloadView; 192 | } 193 | 194 | 195 | - (void)setErrorHandlerButtonTitle:(NSString *)title withBlock:(void(^)(void))block { 196 | self.retryTitle = title; 197 | self.retryBlock = block; 198 | } 199 | 200 | - (void)retryClick { 201 | [self removeFromSuperview]; 202 | if (_retryBlock) { 203 | _retryBlock(); 204 | } 205 | } 206 | 207 | #pragma mark - response 208 | 209 | 210 | - (void)didDownloadProgress:(NSInteger)progress { 211 | [_progressView setProgress:progress/100.]; 212 | } 213 | 214 | 215 | - (void)didFinishLoadSuccess { 216 | _title.text = @"下载完成"; 217 | CGRect frame = self.frame; 218 | frame.origin.y += self.frame.size.height; 219 | [UIView animateWithDuration:0.3 animations:^{ 220 | self.frame = frame; 221 | } completion:^(BOOL finished) { 222 | [self removeFromSuperview]; 223 | }]; 224 | } 225 | 226 | 227 | - (void)didFinishLoadFailed { 228 | // 错误页面,暂时错误标题与按钮。 229 | _title.text = @"下载失败,稍后重试"; 230 | [_progressView removeFromSuperview]; 231 | 232 | UIView *split = [[UIView alloc] init]; 233 | split.backgroundColor = [UIColor colorWithRed:51/255. green:51/255. blue:51/255. alpha:0.5]; 234 | split.frame = CGRectMake(0, 50, 180, 0.5); 235 | [_loadingView addSubview:split]; 236 | 237 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 238 | [btn setTitle:_retryTitle forState:UIControlStateNormal]; 239 | [btn setTitleColor:[UIColor colorWithRed:79/255. green:148/255. blue:205/255. alpha:1] forState:UIControlStateNormal]; 240 | btn.backgroundColor = [UIColor clearColor]; 241 | btn.frame = CGRectMake(0, 50, 180, 40); 242 | [_loadingView addSubview:btn]; 243 | [btn addTarget:self action:@selector(retryClick) forControlEvents:UIControlEventTouchUpInside]; 244 | } 245 | 246 | @end 247 | --------------------------------------------------------------------------------