零、说点什么吧

周末是一个轻松的日子,于是决定写点什么。名字取得比较大,暂时也没有想到应该怎么命名。刚刚开始仔细的看了一下 MGJRouter 中的代码,所以一边看,也就一边的做点笔记。现在看完了,整理了一下就发出来了。都是以我看代码的顺序来描述的,建议各位大神自行下载代码细细品读!!

也是因为看了一圈的组件化、我更不知道 组件化 到底是什么。尽然这样、那就忘记组件化,看看在代码方面是如何实现组件化的。仅仅是通过 Demo 看大神们的代码到底使用了什么技术点。

蘑菇街的代码,在这里:MGJRouter 下载下来一起看看吧。

本介绍,仅仅是针对 Demo 的代码。

一、Demo 的概要

打开项目 MGJRouterDemo,看到两个控制器 DemoListViewController 与 DemoDetailViewController,结合项目运行起来的效果很好的能看出这两个控制器的实现逻辑。

1.1 List 控制器的列表数据源

当前就控制器的数据源是 titleWithHandlers 与 titles,仔细研究发现 DemoListViewController 中 +registerWithTitle: handler: 被调用于 DemoDetailViewController 中的 +load 方法中。

看完这个逻辑,发现了一个特点:在整个APP 的运行中仅创建了一个 DemoDetailViewController 对象,是一个 target 与 block 相结合的一个优秀技巧。很值得学习与借鉴。

1.2 Detail 控制器的实现

这个有点绕、但是经典就是经典。首先你会发现在 .h 文件中几乎什么都没有,但是能做出不同的显示。这个还是要归功于 +load 中 target 与 block 的巧妙使用。

二、MGJRouter 核心实现

先瞄一眼 MGJRouter 的头文件,清一色的 Class 方法,对于一个工具 Class,我也喜欢这么去设计。首先是因为这样使用特别的方便,其次尽然是工具就尽量不要拖泥带水的搞一些属性在那里。当然、技术是不能一概而论的,但是很多的时候也会发现很多的小伙伴根本不会写工具。关于 MGJRouter 还有一个巧妙的设计, 那就是本质是一个单例,只是没有被暴露。在我看这个代码之前,我还以为这种方式是我的独创,我之前也会这么干。看到一个单例的第一个正常反应是感觉看一下是否重写了类似 -init 这样的方法。可喜可贺,MGJRouter 是一个单纯的单例,没有重写这些方法。

大概看了一下头文件中各个方法的简单注释介绍,看完之后就可以开始分析具体的实现了。具体的入口的都在 DemoDetailViewController 中。

demoBasicUsage 方法

这个方法的原型如下:

- (void)demoBasicUsage

{

[MGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) {

[self appendLog:@"匹配到了 url,以下是相关信息"];

[self appendLog:[NSString stringWithFormat:@"routerParameters:%@", routerParameters]];

}];

[MGJRouter openURL:@"mgj://foo/bar"];

}

上面的代码中做了两件事:注册与调用。在这里我们要注意的是,在很多的时候我们在看优秀的代码的时候,可以先看方法,然后猜想一下大神的逻辑是什么。看到上面的代码,我的猜想是:通过一个 URL 注册信息,后期会通过这个 URL 找到注册的这些信息。,那么问题来了,到底是通过什么样的规则来注册呢?这些信息注册到哪里了呢?带着这些问题,我们开始打断点、并运行代码。

这里需要说明一点:这里仅仅是一个示例,并不是说注册就应该与具体的 openURL:写到一起。如果说在实际的项目开发中写到一起,那还叫 组件化 么?

2.1 注册实现

当代码运行起来之后,你的断点可能会在这里停留一会了:

pathComponentsFromURL:

我们先来看一看 pathComponentsFromURL: 方法中都做了什么事:

- (NSArray*)pathComponentsFromURL:(NSString*)URL

{

NSMutableArray *pathComponents = [NSMutableArray array];

if ([URL rangeOfString:@"://"].location != NSNotFound) {

NSArray *pathSegments = [URL componentsSeparatedByString:@"://"];

// 如果 URL 包含协议,那么把协议作为第一个元素放进去

[pathComponents addObject:pathSegments[0]];

// 如果只有协议,那么放一个占位符

URL = pathSegments.lastObject;

if (!URL.length) {

[pathComponents addObject:MGJ_ROUTER_WILDCARD_CHARACTER];

}

}

for (NSString *pathComponent in [[NSURL URLWithString:URL] pathComponents]) {

if ([pathComponent isEqualToString:@"/"]) continue;

if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;

[pathComponents addObject:pathComponent];

}

return [pathComponents copy];

}

这个方法,就是对一个字符串做一个解析操作,那么是如何解析的呢?我先把本 Demo 中用到的解析结果,在这里 Copy 一下:

@[@"mgj", @"foo", @"bar"]

@[@"mgj"]

@[@"mgj", @"search", @":query"]

@[@"mgj", @"~"] 是不是有一种通配符的感觉,对、没错。

如果对上面的结果有歧义,那么可以从上依次的学习一下这个方法中用到的方法:

NSString 中的 pathComponentsFromURL: 方法

关于这个方法,我给出的一个示例如下:

// 定义一个字符串

NSString* URL = @"Coder";

NSArray *pathSegments = [URL componentsSeparatedByString:@"://"];

NSLog(@"%@", pathSegments);

/** 打印结果:

(

Coder

)

*/

// 修改字符串

URL = @"Coder://";

pathSegments = [URL componentsSeparatedByString:@"://"];

NSLog(@"%@", pathSegments);

/** 打印结果:

(

Coder,

""

)

*/

// 修改字符串

URL = @"Coder://HG";

pathSegments = [URL componentsSeparatedByString:@"://"];

NSLog(@"%@", pathSegments);

/** 打印结果:

(

Coder,

HG

)

*/

综上可知:这是一个NSString 与 NSArray 之间的转换。具体的转换规则在上面的这个示例中已经完全的介绍了。上面的是 NSString 转换成 NSArray ,那么 NSArray 又是如何转成 NSString 的呢?

NSURL 的 pathComponents 方法

关于这个方法,我给出的示例代码如下:

// 定义一个字符串

NSURL* URL = [NSURL URLWithString:@"CoderHG/iOS/8968"];

// 执行方法

NSArray* pathComponents = [URL pathComponents];

// 打印

NSLog(@"%@", pathComponents);

/** 打印结果:

(

CoderHG,

iOS,

8968

)

*/

// 修改字符串 带中文

URL = [NSURL URLWithString:@"CoderHG/iOS/8968/朱鸿"];

pathComponents = [URL pathComponents];

// 打印

NSLog(@"%@", pathComponents);

/** 打印结果:

nil

*/

综上所述:这个方法是针对字符串中带有 / 而言的,一旦 URL 中带有中文,则返回为 nil。

NSString 的 substring 方法

关于这个方法,我给出的示例代码如下:

// 定义一个字符串

NSString* coder = @"coder";

NSString* substringToIndex = [coder substringToIndex:1];

NSString* substringFromIndex = [coder substringFromIndex:3];

NSLog(@"To = %@, From = %@", substringToIndex, substringFromIndex);

/** 打印结果:

To = c, From = er

*/

// 直接 crash

NSString* crashString = [coder substringToIndex:10];

NSLog(@"%@", crashString);

终上所述:就是一个字符剪切的方法,但是一定要注意 crash 的情况。

addURLPattern: 方法

上一个方法执行结束之后,就会回到这个方法中来, 这个方法很有意思,具体如下:

- (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern

{

NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];

NSMutableDictionary* subRoutes = self.routes;

for (NSString* pathComponent in pathComponents) {

if (![subRoutes objectForKey:pathComponent]) {

subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];

}

subRoutes = subRoutes[pathComponent];

}

return subRoutes;

}

当从 pathComponentsFromURL: 中返回 pathComponents 数据之后, 对 self.routes 做了一系列的赋值处理。其中 routes 是一个关键的字典,就是用来承载注册信息的,具体是如何承载的呢?

主要是的代码就是在哪个 for 语句中,这是又是一个很奇妙的设计方式。第一次注册

image.png

这个数据结构,看起来 玄之又玄 ,弄了一个字典中的字典。

总之,就是通过 pathComponents 中的元素,在 self.routes 中布了一个局,里面什么都没有,都是空字典。 还有一个特点是,将最后一个字典做了 return 了。欲知有何用途,请看下面分解。

通过上面的方法之后, 会到这个方法中来:

addURLPattern: andHandler:

这个方法的原型是:

- (void)addURLPattern:(NSString *)URLPattern andHandler:(MGJRouterHandler)handler

{

NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];

if (handler && subRoutes) {

subRoutes[@"_"] = [handler copy];

}

}

通过 addURLPattern: 返回的 subRoutes 是根据 URLPattern 在 self.routes 中注册的最后一个字典。

看了这个方法,终于明白返回最后一个字典的原因就是将 注册的 handler 放到通过 _ 作为 key 值放到这个字典中。

到这里,一个简单的注册流程,就走完了。那么问题来了,这个注册流程到底都干了什么事情呢?总结如下:

通过 URLPattern 按照一定的规则解析成一个数组,然后将数组又按照一定的规则布局于 self.routes 中,最后将注册的 handler 放到通过 _ 作为 key 值放到最后一个字典中。

那么问题又来了:注册之后,又该如何使用呢?我的猜想是这样的:

注册仅仅是通过一定的规则,将注册的信息暂存于 self.routes 中,这些信息将服务于后期的 openURL: 操作。具体是如何服务的呢?请看下回分解。

以上是注册的整个流程,接下来看一下在调用 openURL 中又做了什么事?

2.2 openURL 流程

代码一路执行,最终到这里看到了一个莫名的转换,先来看看:

image.png

字符串的 stringByAddingPercentEscapesUsingEncoding: 方法,就这个方法,我给出的示例代码如下:

NSString* URL = @"鸿哥最帅";

NSLog(@"%@", URL);

// 打印结果: 鸿哥最帅

URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSLog(@"%@", URL);

// 打印结果: %E9%B8%BF%E5%93%A5%E6%9C%80%E5%B8%85

URL = [URL stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSLog(@"%@", URL);

// 打印结果:鸿哥最帅

这个方法就是一个字符转义用的,主要是处理中文的情况。

下一步,跳进这个方法中来:

image.png

这里需要注意的是,这个方法传进来的 URL 是经过转义的。在往下,是一个 for 循环。主要就是查看当前传进来的 URL 是否被注册过,如果在 self.routes 任意一个节点中没有找到像匹配的节点,那么就直接返回 nil。在这个 for 循环中的技术点,数组排序的很简单,可以忽略,先来看一下这个方法:

+ (BOOL)checkIfContainsSpecialCharacter:(NSString *)checkedString {

NSCharacterSet *specialCharactersSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];

return [checkedString rangeOfCharacterFromSet:specialCharactersSet].location != NSNotFound;

}

看方法的命名就知道,这个方法是用来检查 checkedString 是否包含特殊字符串的 ** specialCharacters(/?&.),的确,NSCharacterSet** 这东西挺厉害的,至少面试的时候面试官可能会这样的问:你用过 OC 中的集合么?如果说你回答了字典与数组,那么就等于面试官什么都没有问,如果说你回答了 NSSet,在强调一下 NSCharacterSet,恐怕面试官会爱上你的。关于这个,我给出的示例代码如下:

NSCharacterSet *specialCharactersSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];

NSString* text = @"CoderHG";

NSRange rang = [text rangeOfCharacterFromSet:specialCharactersSet];

NSLog(@"%zd, %zd", rang.location, rang.length);

// 打印结果: 9223372036854775807, 0

text = @"Coder?HG";

rang = [text rangeOfCharacterFromSet:specialCharactersSet];

NSLog(@"%zd, %zd", rang.location, rang.length);

// 打印结果: 5, 1

text = @"C/?oder/?HG?";

rang = [text rangeOfCharacterFromSet:specialCharactersSet options:NSBackwardsSearch];

NSLog(@"%zd, %zd", rang.location, rang.length);

// 打印结果: 11, 1

关于 NSURLComponents,我的示例代码如下:

NSString* url = @"http://coder.com?coder=iOS&name=HG";

// Extract Params From Query.

NSArray *queryItems = [[NSURLComponents alloc] initWithURL:[[NSURL alloc] initWithString:url] resolvingAgainstBaseURL:false].queryItems;

NSMutableDictionary* parameters = [NSMutableDictionary dictionary];

for (NSURLQueryItem *item in queryItems) {

parameters[item.name] = item.value;

}

NSLog(@"%@", parameters);

/** 打印结果:

{

coder = iOS;

name = HG;

}

*/

明白了,原来是为了获取 URL 后面的参数的。想当年某些人使用一个一个的截取,然后一个一个的去拼接的。

2.3 注销操作

注销方法 +deregisterURLPattern:

主要是这个方法:

- (void)removeURLPattern:(NSString *)URLPattern

{

NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:[self pathComponentsFromURL:URLPattern]];

// 只删除该 pattern 的最后一级

if (pathComponents.count >= 1) {

// 假如 URLPattern 为 a/b/c, components 就是 @"a.b.c" 正好可以作为 KVC 的 key

NSString *components = [pathComponents componentsJoinedByString:@"."];

NSMutableDictionary *route = [self.routes valueForKeyPath:components];

if (route.count >= 1) {

NSString *lastComponent = [pathComponents lastObject];

[pathComponents removeLastObject];

// 有可能是根 key,这样就是 self.routes 了

route = self.routes;

if (pathComponents.count) {

NSString *componentsWithoutLast = [pathComponents componentsJoinedByString:@"."];

route = [self.routes valueForKeyPath:componentsWithoutLast];

}

[route removeObjectForKey:lastComponent];

}

}

}

在注册分析的过程中已经发现,所谓的注册就是通过 URL 按照一定的规则将消息存于 self.routes 中,那么销毁正好相反。

至此整个流程也就差不多了, 还有没介绍到的,其实都有包括了。

三、想法

看完之后,也发现在 MGJRouter 中也没有什么高深莫测的技术点,但是这是一个很经典的代码。经典于设计思想、经典于使用小技术解决大问题。

建议大家下载源码仔细品读,会让你收获更多!

很多的时候我们不是不懂技术,而是不知道如何是使用我们已知的技术。

谢谢!

android 组件化 蘑菇街,组件化在蘑菇街相关推荐

  1. android 蘑菇街组件化,4. IOS 组件化(蘑菇街的路由+协议式)

    为了研究组件化,我们主要是讨论 蘑菇街的路由+协议式 和 中间件 讨论第一种方式,并参考 蘑菇街IOS组件化 ,我们来实现一个可以运行的demo,并讨论优缺点. 路由 用MGJRouter 单例,通过 ...

  2. Android组件化与插件化开发项目实战整理分享(含支付宝、360、美团、滴滴等大厂项目实战)

    小公司不说,但是在大公司的项目发展到一定程度,就必须进行模块的拆分.模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 开发的实践,目前有两种途径来实现,一个是组件化,一个是 ...

  3. 【Android 插件化】插件化简介 ( 组件化与插件化 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. Android组件化专题 - 组件化配置

    demo地址 Android组件化专题,详细讲解组件化的使用及配置,以及实现的原理. 本文章讲解了组件化的由来及配置,下期讲解页面路由跳转及路由原理与apt 1. 组件化的由来 模块化.组件化和插件化 ...

  5. Android 基于注解IOC组件化/模块化的架构实践

    当前参与的项目历史也很久远,第一行代码据说是写于2014年的某一天,那时Android用的ide还是Eclipse.那时Android还没有很好的架构指导(mvp.mvvm).那时Android最新的 ...

  6. Android 组件化代码中心化问题之.api化方案

    theme: channing-cyan highlight: a11y-light 一.代码中心化问题 将一个大型的项目拆分成多个Module或者新开的组件化项目,想要的预期是这些module之间是 ...

  7. android 电商app组件化,APICloud AVM多端开发案例深度解析(一)--生鲜电商app开发

    AVM多端开发是APICloud定义的一套新的代码编写标准(DSL):基于标准Web Components组件化思想,兼容Vue / React语法特性,通过一次编码,分别编译为Android和iOS ...

  8. Android组件化与插件化的差别在哪里,该怎么选型?

    面试官: 组件化如何实现,组件化与插件化的差别在哪里,该怎么选型? 心理分析:面试官从架构层次 了解求职者是否用过 模块化 组件化 和插件化,在过去经验有没有运用过这些技术到项目中,这道题属于一个连环 ...

  9. Android组件化和插件化开发

    项目发展到一定程度,就必须进行模块的拆分.模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 工程实践,目前有两种途径,一个是组件化,一个是插件化. 组件化开发 说起组件化少 ...

  10. Android组件化和插件化开发,android开发工程师月薪

    开发调试时不需要对整个项目进行编译,每个模块可独立编译,提高了编译速度. 多人合作时可以只关注自己的业务模块,把某一业务当成单一项目来开发,可以提升开发,测试效率. 可以灵活的对业务模块进行组装和拆分 ...

最新文章

  1. node都会 react_学react需要node吗
  2. Asp.net Web API实战
  3. python 计算两个日期相差多少个月
  4. (未写)tyvj-1333- Coder Space的邀请
  5. 多台电脑共用一个耳机、音箱
  6. Java 如何抛出异常、自定义异常、手动或主动抛出异常
  7. 2018年美国专利数量公布:IBM夺榜首 华为排第19
  8. android sdk dns,Android SDK 导入
  9. Excel导入SQL datetime的处理
  10. 小谈wagaa的下载速度
  11. sxe增加服务器,sXe Injected服务端使用说明
  12. 33. secure world对smc请求的处理------invoke command操作在OP-TEE中的实现
  13. 巨头哄抢有声书流量,谁会是耳朵经济最终收割者?
  14. CET-6--2018.6--2
  15. 1330: PIPI的乐高积木
  16. 中文姓名按照拼音排序-python
  17. JAVA每日学习 Day31---抽象类和接口的含义、共性、区别
  18. 学习单片机的几点经验之谈
  19. Debian squeeze 美化字体
  20. 解决cumcm17问题的代码记录(待改正)

热门文章

  1. 【Python】-表格拆分工具
  2. python列表类型pop_为什么python列表具有pop()但没有push()
  3. 【引用】益和VA走出国门,中新企业交易会硕果累出
  4. matplotlib 子图超过4个_Matplotlib+Pandas:子图创建
  5. xindi数据分析记录
  6. “互联网+”已死 腾讯、阿里巅峰已过
  7. 儿童节,送你一批值得关注的公众号!
  8. C++:查验身份证(团体程序设计天梯赛)
  9. 【算法】算法设计与分析试题(含答案)
  10. 4 ZooKeeper