Tetris 0.7.2

Tetris 0.7.2

jrwong 维护。



Tetris 0.7.2

  • jr-wong

Tetris

CI Status Version License Platform

示例

要运行示例项目,请克隆仓库,然后首先从 Example 目录运行 pod install

要求

安装

Tetris 可以通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中

pod 'Tetris'

描述

模块化开发工具,包括VC跳转方式控制,路由跳转,拦截器,依赖注入,等功能

使用

1、生命周期

一个APP管理生命周期的对象为AppDelegate,但是模块化开发后,壳工程的Delegate属于最下层应用,无法被上层业务依赖。但是模块需要依赖app生命周期做一些初始化或者监听代码。库提供了生命周期分发的功能

1、通过接管AppDelegate,内部调用 trigger 的方法进行声明周期分发。

// 分发生命周期,需要接管appDelegate的方法,壳工程的delegate中添加需要分发的代码
@implementation TSBaseApplicationDelegate

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    TSModuleContext *context = [TSModuleContext shared];
    context.launchOptions = launchOptions;
    [_Tetris.modular.trigger tetrisModuleInit:context];
    [_Tetris.modular.trigger tetrisModuleSetup:context];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_Tetris.modular.trigger tetrisModuleSplash:context];
    });
    [_Tetris.modular.trigger application:application willFinishLaunchingWithOptions:launchOptions];
    
    return YES;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [_Tetris.modular.trigger application:application didFinishLaunchingWithOptions:launchOptions];
    return YES;
}

2、模块需要通过注册生命周期接收器来实现生命周期监听

Objective-C

@interface TSDemoModules () <TSModularComposable>
@end
  
@implementation TSDemoModules
  
TS_MODULE(TSModulePriorityHigh)// 通过注解注册生命周期监听

- (void)tetrisModuleInit:(TSModuleContext *)context {
    NSLog(@"%@, %s", self, __FUNCTION__);
}
- (void)tetrisModuleSetup:(TSModuleContext *)context {
    NSLog(@"%@, %s", self, __FUNCTION__);
}
- (void)tetrisModuleSplash:(TSModuleContext *)context {
    NSLog(@"%@, %s", self, __FUNCTION__);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
		// do something at launch in your module
    return YES;
}

Swift

class SwiftModules1: NSObject, Modularable {
    required override init() {
        super.init()
    } 
	  var priority: ModulePriority = TSModulePriorityNormal
  	func applicationDidBecomeActive(_ application: UIApplication) {
  			// do something on become active      
    }
}

2、依赖注入,IOC

模块化拆分后,存在跨模块调用的问题。上下依赖的模块能直接代码依赖,但是横向模块间理论上来说不能进行代码依赖。

所以跨模块数据访问我们提供了一个思路就是底层定义接口,上层分别实现和调用接口。

1、定义接口

@protocol TSServiceA
- (void)methodA;
@end

2、实现接口,并且注册

OC

// 对象实现ServiceA, 并且需要实现TSServiceable接口即可
@interface TSServiceAImpl : NSObject<TSServiceA, TSServiceable>
@end
@implementation TSServiceAImpl
TS_SERVICE(TSServiceA, YES)// 注册服务,Bool参数为是否单例
- (void)methodA {
    NSLog(@"method a execute");
}
@end

Swift

// 实现IServiceable, Component, TSServiceA 协议
class Services : NSObject, IServiceable, Component, TSServiceA {
    required override init() {
        super.init()
    }
    static var interface: Protocol? = TestProtocolA.self
    static var name: String?
    static var singleton: Bool = false
    func methodA() {
        print("--swift servcie---")
    }
}

3、获取对象,进行调用

OC

- (void)someMethod {
  	// 获取实现,调用方法
		[TS_GET_SERVICE(TSServiceA) methodA];
}

Swift

func someMethod() {
	  let service: TSServiceA? = Tetris.getService(TSServiceA.self)
  	service?.methodA()
}

3、跳转控制

iOSVC栈存在两种默认情况,每个VC都提供一个展示另一个VC的能力(Present),内置一个控制器栈(NavigationController)。不同的栈进入和退出的方式都不一样。如何进入,就需要对应的方式如何退出。

比如有一个公共的VC,可能有人需要push,有人需要present。那我们只能从最原始的获取VC,然后手动跳转。这会导致一个障碍,因为进入方式VC不可见,所以VC不能自我销毁,每个销毁操作都需要依赖自己的上层。

本库提供一个Intent,通过Intent描述你的跳转操作。进而通过Intent进行操作跳转,达到VC自我控制销毁操作。

能跳转的VC都需要实现 TSIntentable 协议

NS_SWIFT_NAME(Intentable)
@protocol TSIntentable <TSCreatable, TSViewControllable, NSObject>
  
@property (nonatomic, strong, nullable) TSIntent *ts_sourceIntent;

- (instancetype)initWithIntent:(TSIntent *)intent;

@optional
// 拦截器相关
+ (nullable id<TSIntercepter>)ts_selfIntercepter;

@end
- (void)func {
    TSIntent *intent = [TSIntent intentWithClass:[UIViewController class]];
    intent.displayer = [TSPushPopDisplayer new];
    [[_Tetris.router prepare:intent source:self] subscribeNext:^(TSRouteResult * _Nullable obj) {
        // 跳转成功
    } error:^(NSError * _Nonnull error) {
        // 跳转出错
    }];  
  	
  	// UIViewController 已经有分类方法提供 ts_start: 进行跳转
	//[self ts_start:intent];
}
Displayer

之前说到若我们想VC自我控制销毁操作,我们必须依赖之前的进入方式。为了将两个VC能够实现一定程度的解耦以及自我销毁的操作。这里提供一个Displayer接口,来实现跳转方式。

NS_SWIFT_NAME(IIntentDisplayer)
@protocol TSIntentDisplayerProtocol

@required

/**
 Show a viewController
 
 @param fromVC ViewController that transition from
 @param toVC ViewController where transition to
 @param animated If animated?
 @param completion Completion handler
 */
- (void)ts_displayFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC animated:(BOOL)animated completion:(void (^ _Nullable)(void))completion;

/**
 Finish display given ViewController
 
 @param vc ViewController that will be finished
 @param animated If animated
 @param completion Completion handler
 */
- (void)ts_finishDisplayViewController:(UIViewController *)vc animated:(BOOL)animated completion:(void (^ _Nullable)(void))completion;


/**
 Make sure the given ViewController show on the top hierarchy;
 But not work if the vc not on the view hierachy;
 
 @param vc vc that need to display
 @param animated If animated
 @param completion Completion handler
 */
- (void)ts_setNeedDisplay:(UIViewController *)vc animated:(BOOL)animated completion:(void (^ _Nullable)(void))completion;

@end

NS_ASSUME_NONNULL_END

可以继承自 TSDisplayerAdapter 后自己实现自定义的转场动画,并且可以抽离出来,达到代码共用效果。

TSPushPopDisplayer TSPresentDismissDisplayer

4、路由跳转

Intent提供多种方式创建

@interface TSIntent (Creations)
- (instancetype)initWithUrl:(NSString *)urlString; // 指定Url
- (instancetype)initWithClass:(Class<TSIntentable>)aClass; // 指定class
- (instancetype)initWithDisplayer:(id<TSIntentDisplayerProtocol>)displayer; // 指定Displayer
- (instancetype)initWithFactory:(TSIntentableFactoryBlock)factory; // 指定工厂方法
@end

1、URL绑定方式

通过指定URL,即指定了将VC的操作绑定到该URL。

// 创建一个VC
@interface TSDemo1ViewController ()
@end

@implementation TSDemo1ViewController
// 通过注解绑定VC和路由
TS_ROUTE_MSG(@"/demo1", @"Message that descripe this vc")
@end

5、拦截器(Intercepter)

在进行跳转时,某些界面可能需要满足前置条件,例如需要登录才能进入某个界面。如果有很多地方需要进入,则需要在每个入口都检测是否已经登录,调用登录界面,然后在进行目标界面跳转。这样会导致大量重复代码。

这里提供一个用于拦截通过Intent执行的所有跳转的拦截器组件。(注意:通过工厂方法创建的intent,暂时不会被拦截:未来可能提供拦截token支持)

NS_SWIFT_NAME(Intercepter)
@protocol TSIntercepter <TSCreatable>
// 优先级
@property (nonatomic, assign) TSIntercepterPriority priority;

/**
 Do intercepter logic;
 In intercepter life cycle, should call adjudger's doSwitch or doReject or doContinue once;
 // 在一次拦截周期内,至少调用judger.doSwitch or doReject or doContinue 其中一个一次
 @param judger The judger
 */
- (void)ts_judgeIntent:(id<TSIntercepterJudger>)judger;

@end

如果设置了拦截器,则所有通过Intent执行的跳转都将被拦截器拦截,并且拦截顺序将根据拦截器的优先级执行。

注册拦截器

// 继承adapter,或者自己实现接口都可以
@interface RIDemo14Intercepter : TSIntercepterAdapter
@end
@implementation RIDemo14Intercepter
// 通过注解注册拦截器,指定优先级
TS_INTERCEPTER(TSIntercepterPriorityHigh)
  
- (void)ts_judgeIntent:(id<TSIntercepterJudger>)adjudgement {
    if ([self hasLogin]) {
      	// 若已经登录,则继续执行后面的拦截器
        [adjudgement doContinue];
    } else {
      	// 若未登录,则创建需要登录界面,
        TSIntent *loginIntent; //xxxxxx
        [loginIntent.onSuccess subscribeNext:^(TSResult * _Nullable obj) {、
          	// 当登录完成时,重启之前的intent
            [adjudgement restart];
        }];
      	// 切换到登录界面
        [adjudgement doSwitch:loginIntent];
    }
}
@end

作者

[email protected]

许可

Tetris可在MIT许可下获得。有关更多信息,请参阅LICENSE文件。