├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------