一步步构建iOS路由

接上一篇移动端路由层设计
为啥要说iOS路由呢?
路由层其实在逻辑上的设计都是一样的,关于对界面跳转的实现部分却与Android平台和iOS平台上的导航机制有着非常紧密的关系,Android操作系统有着天然的架构优势,Intent机制可以协助应用间的交互与通讯,是对调用组件和数据传递的描述,本身这种机制就解除了代码逻辑和界面之间的依赖关系,只有数据依赖。而iOS的界面导航和转场机制则大部分依赖UI组件各自的实现,所以如何解决这个问题,iOS端路由的实现则比较有代表性。
其实说白一点,路由层解决的核心问题就是原来界面或者组件之间相互调用都必须相互依赖,需要导入目标的头文件、需要清楚目标对象的逻辑,而现在全部都通过路由中转,只依赖路由,或者依靠一些消息传递机制连路由都不依赖。其次,路由的核心逻辑就是目标匹配,对于外部调用的情况来说,URL如何匹配Handler是最为重要的,匹配就必然用到正则表达式。了解这些关键点以后就有了设计的目的性,let‘s do it~
设计类图:
设计类图
RouteClassMap.png
这里面有如下几个类:
WLRRouteRequest,路由层的请求,无论是跨应用的外部调用还是内部调用,最后都形成一个路由请求,该请求包含了URL上的queryparameters和路径参数,还有内部调用时直接传入的原生参数,还有请求发起者对目标预留的回调block
WLRRouteHandler,路由层的handler处理,handler接收一个WLRRouteRequest对象,来完成是否是界面跳转,还是组件加载,还是内部逻辑
WLRRouter,路由核心对象,内部持有注册的Handler,比方说负责界面跳转的Handler,负责组件加载的Handler,负责API的Handler等等,路由的作用就是将外部调用传入的URL或者是内部调用传入的target,在内部匹配上对应的handler,然后调用生命周期方法,完成处理过程,当然,图中还有route的中间件,实际上是预留AOP的口子,方面后期扩展
WLRRouteMatcher,用以处理外部调用的URL是否能与预设的正则表达式匹配,在WLRRouter中,每一次注册一个handler,都会用一个URL匹配的表达式生成一个WLRRouteMatcher
WLRRegularExpression,继承NSRegularExpression,用以匹配URL,WLRRouteMatcher内部有一个WLRRegularExpression对象,WLRRouteMatcher接受一个URL,会使用WLRRegularExpression生成一个WLRMatchResult对象,来确定是否匹配成功,如果匹配成果则将URL上的路径参数给取出来
WLRMatchResult,用以描述WLRRegularExpression的匹配结果,包含路径参数
工作流程:
App启动实例化WLRRouter对象
实例化WLRRouteHandler对象
WLRRouter对象挂载WLRRouteHandler实例与URL的表达式相对应,WLRRouter内部生成一个WLRRouteMatcher对象,与URL的表达式相对应
外部调用的URL和callback传入WLRRouter对象
WLRRouter对象遍历内部持有的URL的匹配表达式,并找到每一个WLRRouteMatcher对象,将URL传入看是否能返回WLRRouteRequest对象
将WLRRouteRequest对象传入对应的WLRRouteHandler对象
WLRRouteHandler对象根据WLRRouteRequest寻找到TargetViewController和SourceViewController,在生命周期函数里,完成参数传递与视图转场
WLRRouteRequest:
了解了以上,我们从WLRRouteRequest入手。
其实WLRRouteRequest跟NSURLRequest差不多,不过WLRRouteRequest继承NSObject,实现NSCopying协议,大概如下:

import

@interface WLRRouteRequest : NSObject
//外部调用的URL
@property (nonatomic, copy, readonly) NSURL URL;
//URL表达式,比方说调用登录界面的表达式可以为:AppScheme://user/login/138
*,那URL的匹配表达式可以是:/login/:phone([0-9]+),路径必须以/login开头,后面接0-9的电话号码数字,当然你也可以直接把电话号码的正则匹配写全
@property(nonatomic,copy)NSString * routeExpression;
//如果URL是AppScheme://user/login/138?/callBack="",那么这个callBack就出现在这
@property (nonatomic, copy, readonly) NSDictionary queryParameters;
//这里面会出现{@"phone":@"138
*"}
@property (nonatomic, copy, readonly) NSDictionary *routeParameters;
//这里面存放的是内部调用传递的原生参数
@property (nonatomic, copy, readonly) NSDictionary *primitiveParams;
//自动检测窃取回调的callBack 的Url
@property (nonatomic, strong) NSURL *callbackURL;
//目标的viewcontrolller或者是组件可以通过这个
@property(nonatomic,copy)void(^targetCallBack)(NSError *error,id responseObject);
//用以表明该request是否被消费
@property(nonatomic)BOOL isConsumed;
//简便方法,用以下标法取参数

  • (id)objectForKeyedSubscript:(NSString *)key;
    //初始化方法
    -(instancetype)initWithURL:(NSURL *)URL routeExpression:(NSString *)routeExpression routeParameters:(NSDictionary *)routeParameters primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError * error,id responseObject))targetCallBack;
    -(instancetype)initWithURL:(NSURL *)URL;
    //默认完成目标的回调
    -(void)defaultFinishTargetCallBack;
    @end
    NSURLRequest其实应该是个值类型的对象,所以实现拷贝协议,该对象的实现部分没有什么可讲的,对照源代码查阅即可。
    WLRRouteHandler
    #import
    @class WLRRouteRequest;
    @interface WLRRouteHandler : NSObject
    //即将handle某一个请求
  • (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;
    //根据request取出调用的目标视图控制器
    -(UIViewController *)targetViewControllerWithRequest:(WLRRouteRequest *)request;
    //根据request取出来源的视图控制器
    -(UIViewController *)sourceViewControllerForTransitionWithRequest:(WLRRouteRequest *)request;
    //开始进行转场
    -(BOOL)transitionWithRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error;
    @end
    当WLRRouter对象完成了URL的匹配生成Request,并寻找到Handler的时候,首先会调用- (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;,来确定handler是否愿意处理,如果愿意,则调用-(BOOL)transitionWithRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error;,内部则通过便利方法获取targetViewController与SourceViewController,然后进行转场,核心方法的实现为:
    -(BOOL)transitionWithRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error{
    UIViewController * sourceViewController = [self sourceViewControllerForTransitionWithRequest:request];
    UIViewController * targetViewController = [self targetViewControllerWithRequest:request];
    if ((![sourceViewController isKindOfClass:[UIViewController class]])||(![targetViewController isKindOfClass:[UIViewController class]])) {
    *error = [NSError WLRTransitionError];
    return NO;
    }
    if (targetViewController != nil) {
    targetViewController.wlr_request = request;
    }
    if ([self preferModalPresentationWithRequest:request]||![sourceViewController isKindOfClass:[UINavigationController class]]) {
    [sourceViewController presentViewController:targetViewController animated:YES completion:nil];
    }
    else if ([sourceViewController isKindOfClass:[UINavigationController class]]){
    UINavigationController * nav = (UINavigationController *)sourceViewController;
    [nav pushViewController:targetViewController animated:YES];
    }
    return YES;
    }
  • (BOOL)preferModalPresentationWithRequest:(WLRRouteRequest )request;{
    return NO;
    }
    这里根据SourceController的类型进行判断,其实request对象的信息足够可以判断目标视图应该如何打开,从本质上来讲,URL的匹配表达式是跟业务强关联的也是跟UI交互逻辑强关联的,transitionWithRequest方法实现里,你大可以继承一下,然后重写转场过程,甚至你可以在这自己设置iOS7自定义的转场,提供动画控制器和实现转场协议的对象,进而可以整体的控制Appp内部的实现。
    WLRRegularExpression
    该类继承NSRegularExpression
    #import
    @class WLRMatchResult;
    @interface WLRRegularExpression : NSRegularExpression
    //传入一个URL返回一个匹配结果
    -(WLRMatchResult *)matchResultForString:(NSString *)string;
    //根据一个URL的表达式创建一个WLRRegularExpression实例
    +(WLRRegularExpression *)expressionWithPattern:(NSString *)pattern;
    @end
    该对象主要的功能是将一个URL传入查看是否匹配,并且将表达式上声明的路径参数从URL上取下来。
    比说,我们设置的URL匹配的表达式是: login/:phone([0-9]+),那AppScheme://user/login/138
    * 这样的URL应该是匹配,并且将138的手机号取出来,对应到phone上,这个过程必须用到正则表达式的分组提取子串的功能,:phone是约定好的提取子串的值对应的key的名字,其实这个url的正则表达式应该是: /login/([0-9]+)$,那么WLRRegularExpression对象需要知道需要提取所有子串的key还有将URL匹配的表达式转换为真正的正则表达式。
    -(instancetype)initWithPattern:(NSString )pattern options:(NSRegularExpressionOptions)options error:(NSError * _Nullable __autoreleasing *)error{
    //初始化方法中将URL匹配的表达式pattern转换为真正的正则表达式
    NSString *transformedPattern = [WLRRegularExpression transfromFromPattern:pattern];
    //用转化后的结果初始化父类
    if (self = [super initWithPattern:transformedPattern options:options error:error]) {
    //同时将需要提取的子串的值的Key保存到数组中
    self.routerParamNamesArr = [[self class] routeParamNamesFromPattern:pattern];
    }
    return self;
    }
    //转换为正则表达式
    +(NSString
    )transfromFromPattern:(NSString *)pattern{
    //将pattern拷贝
    NSString * transfromedPattern = [NSString stringWithString:pattern];
    //利用:[a-zA-Z0-9-_][^/]+这个正则表达式,将URL匹配的表达式的子串key提取出来,也就是像 /login/:phone([0-9]+)/:name[a-zA-Z-_]这样的pattern,需要将:phone([0-9]+)和:name[a-zA-Z-_]提取出来
    NSArray * paramPatternStrings = [self paramPatternStringsFromPattern:pattern];
    NSError * err;
    //再根据:[a-zA-Z0-9-_]+这个正则表达式,将带有提取子串的key全部去除,比如将:phone([0-9]+)去除:phone改成([0-9]+)
    NSRegularExpression * paramNamePatternEx = [NSRegularExpression regularExpressionWithPattern:WLRRouteParamNamePattern options:NSRegularExpressionCaseInsensitive error:&err];
    for (NSString * paramPatternString in paramPatternStrings) {
    NSString * replaceParamPatternString = [paramPatternString copy];
    NSTextCheckingResult * foundParamNamePatternResult =[paramNamePatternEx matchesInString:paramPatternString options:NSMatchingReportProgress range:NSMakeRange(0, paramPatternString.length)].firstObject;
    if (foundParamNamePatternResult) {
    NSString *paramNamePatternString =[paramPatternString substringWithRange: foundParamNamePatternResult.range];
    replaceParamPatternString = [replaceParamPatternString stringByReplacingOccurrencesOfString:paramNamePatternString withString:@""];
    }
    if (replaceParamPatternString.length == 0) {
    replaceParamPatternString = WLPRouteParamMatchPattern;
    }
    transfromedPattern = [transfromedPattern stringByReplacingOccurrencesOfString:paramPatternString withString:replaceParamPatternString];
    }
    if (transfromedPattern.length && !([transfromedPattern characterAtIndex:0] == '/')) {
    transfromedPattern = [@"^" stringByAppendingString:transfromedPattern];
    }
    //最后结尾要用$符号
    transfromedPattern = [transfromedPattern stringByAppendingString:@"$"];
    //最后会将/login/:phone([0-9]+)转换为login/([0-9]+)$
    return transfromedPattern;
    }
    在Matcher对象匹配一个URL的时候
    -(WLRMatchResult *)matchResultForString:(NSString *)string{
    //首先通过自身方法将URL进行匹配得出NSTextCheckingResult结果的数组
    NSArray * array = [self matchesInString:string options:0 range:NSMakeRange(0, string.length)];
    WLRMatchResult * result = [[WLRMatchResult alloc]init];
    if (array.count == 0) {
    return result;
    }
    result.match = YES;
    NSMutableDictionary * paramDict = [NSMutableDictionary dictionary];
    //遍历NSTextCheckingResult结果
    for (NSTextCheckingResult * paramResult in array) {
    //再便利根据初始化的时候提取的子串的Key的数组
    for (int i = 1; i1?[parts firstObject]:nil;
    _routeExpressionPattern =[parts lastObject];
    //将path部分当做URL匹配表达式生成WLRRegularExpression实例
    _regexMatcher = [WLRRegularExpression expressionWithPattern:_routeExpressionPattern];
    }
    return self;
    }
    匹配方法:
    -(WLRRouteRequest )createRequestWithURL:(NSURL )URL primitiveParameters:(NSDictionary )primitiveParameters targetCallBack:(void (^)(NSError , id))targetCallBack{
    NSString * urlString = [NSString stringWithFormat:@"%@%@",URL.host,URL.path];
    if (self.scheme.length && ![self.scheme isEqualToString:URL.scheme]) {
    return nil;
    }
    //调用self.regexMatcher将URL传入,获取WLRMatchResult结果,看是否匹配
    WLRMatchResult * result = [self.regexMatcher matchResultForString:urlString];
    if (!result.isMatch) {
    return nil;
    }
    //如果匹配,则将result.paramProperties路径参数传入,初始化一个WLRRouteRequest实例
    WLRRouteRequest * request = [[WLRRouteRequest alloc]initWithURL:URL routeExpression:self.routeExpressionPattern routeParameters:result.paramProperties primitiveParameters:primitiveParameters targetCallBack:targetCallBack];
    return request;
    }
    WLRRouter
    @class WLRRouteRequest;
    @class WLRRouteHandler;
    @interface WLRRouter : NSObject
    //注册block回调的URL匹配表达式,可用作内部调用
    -(void)registerBlock:(WLRRouteRequest (^)(WLRRouteRequest request))routeHandlerBlock forRoute:(NSString *)route;
    //注册一个WLRRouteHandler对应的URL匹配表达式route
    -(void)registerHandler:(WLRRouteHandler )handler forRoute:(NSString )route;
    //判断url是否可以被handle
    -(BOOL)canHandleWithURL:(NSURL *)url;
    -(void)setObject:(id)obj forKeyedSubscript:(NSString *)key;
    -(id)objectForKeyedSubscript:(NSString *)key;
    //调用handleURL方法,传入URL、原生参数和targetCallBack和完成匹配的completionBlock
    -(BOOL)handleURL:(NSURL )URL primitiveParameters:(NSDictionary )primitiveParameters targetCallBack:(void(^)(NSError , id responseObject))targetCallBack withCompletionBlock:(void(^)(BOOL handled, NSError error))completionBlock;
    在实现部分,有三个属性:
    //每一个URL的匹配表达式route对应一个matcher实例,放在字典中
    @property(nonatomic,strong)NSMutableDictionary * routeMatchers;
    //每一个URL匹配表达式route对应一个WLRRouteHandler实例
    @property(nonatomic,strong)NSMutableDictionary * routeHandles;
    //每一个URL匹配表达式route对应一个回调的block
    @property(nonatomic,strong)NSMutableDictionary * routeblocks;
    在Route挂在Handler和回调的block的时候:
    -(void)registerBlock:(WLRRouteRequest (^)(WLRRouteRequest ))routeHandlerBlock forRoute:(NSString *)route{
    if (routeHandlerBlock && [route length]) {
    //首先添加一个WLRRouteMatcher实例
    [self.routeMatchers setObject:[WLRRouteMatcher matcherWithRouteExpression:route] forKey:route];
    //删除route对应的handler对象
    [self.routeHandles removeObjectForKey:route];
    //将routeHandlerBlock和route存入对应关系的字典中
    self.routeblocks[route] = routeHandlerBlock;
    }
    }
    -(void)registerHandler:(WLRRouteHandler )handler forRoute:(NSString )route{
    if (handler && [route length]) {
    //首先生成route对应的WLRRouteMatcher实例
    [self.routeMatchers setObject:[WLRRouteMatcher matcherWithRouteExpression:route] forKey:route];
    //删除route对应的block回调
    [self.routeblocks removeObjectForKey:route];
    //设置route对应的handler
    self.routeHandles[route] = handler;
    }
    }
    接下来完善handle方法:

以上我们可以看到,Route将匹配的逻辑单独封装到WLRRouteMatcher对象中,将匹配后的结果生成WLRRouteRequest实例以携带足够完整的数据,同时将真正处理视图控制器的转场或者是组件的加载或者是未来可能拓展的handle业务封装到WLRRouteHandler实例中,匹配逻辑对应的处理逻辑干净分离,匹配逻辑可单独塑造业务匹配,处理逻辑可以通过继承扩展或者冲洗WLRRouteHandler的生命周期函数来更好的处理回调业务。如果WLRRouteHandler不能提供足够多的扩展性,则可以使用block回调最大限度的进行扩展。
以上,就是路由部分的整体实现。
转场的扩展
在WLRRouteHandler中,其实我们可以单独控制路由经过的页面跳转的转场。
-(UIViewController )targetViewControllerWithRequest:(WLRRouteRequest )request{
}
-(UIViewController )sourceViewControllerForTransitionWithRequest:(WLRRouteRequest )request{
}
-(BOOL)transitionWithRequest:(WLRRouteRequest )request error:(NSError __autoreleasing *)error{

}
这样的生命周期函数是不是很像UIViewControllerContextTransitioning转场上下文的协议的设定?- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;方法使上下文提供目标控制器和源控制器,其实在handler中你完全可以自定义一个子类,在transitionWithRequest方法里,设置遵守UIViewControllerTransitioningDelegate的代理,然后在此提供遵守 UIViewControllerAnimatedTransitioning的动画控制器,然后自定义转场上下文,实现自定义UI转场,而对应的匹配逻辑是与此无关的,我们就可以在路由曾控制全局的页面转场效果。对自定义转场不太熟悉的同学请移步我之前的文章:
ContainerViewController的ViewController 转场
路由的安全
有两个方面可以去做
WLRRouteHandler实例中, -(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request中可以检测request中的参数,比方说效验source或者是效验业务参数完整等
WLRRouter实例中handleURL方法,将在随后的WLRRoute的0.0.2版本中加入中间件的支持,就是在找到handler之前,将按照中间件注册的顺序回调中间件,而我们可以在中间件中实现风控业务、认证机制、加密验签等等
路由的效率
目前我们实现的路由是一个同步阻塞型的,在处理并发的时候可能会出现一些问题,或者是在注册比较多的route表达式以后,遍历和匹配的过程会损耗性能,比较好的实现方式是,将Route修改成异步非阻塞型的,但是API全部要换成异步API,起步我们先把同步型的搞定,随后慢慢提供异步版本的路由~
路由的使用
在大部分App实践MVVM架构或者更为复杂的VIPER架构的时候,除了迫切需要一个比较解耦的消息传递机制,如何更好的剥离目标实体的获取和配合UIKit这一层的转场逻辑是一项比较复杂的挑战,路由实际上是充当MVVM的ViewModel中比较解耦的目标获取逻辑和VIPER中Router层,P与V的调用全部靠Router转发。
在实施以组件化为目的的工程化改造中,如何抽离单独业务为组件,比较好的管理业务与业务之间的依赖,就必须使用一个入侵比较小的Route,WLRRoute入侵的地方在于WLRRouteHandler的transitionWithRequest逻辑中,通过一个UIViewController的扩展,给 targetViewController.wlr_request = request;设置了WLRRouteRequest对象给目标业务,但虽然如此,你依旧可以重写WLRRouteHandler的transitionWithRequest方法,来构建你自己参数传递方式,这一点完全取决于你如何更好的使得业务无感知而使用路由。

本文作者:佚名
来源:51CTO

时间: 2024-09-20 21:53:24

一步步构建iOS路由的相关文章

用Linux防火墙构建软路由

本文主要介绍利用Linux自带的Firewall软件包来构建软路由的一种方法,此方法为内部网与外部网的互连提供了一种简单.安全的实现途径.Linux自带的Firewall构建软路由,主要是通过IP地址来控制访问权限,较一般的代理服务软件有更方便之处. 一.防火墙 防火墙一词用在计算机网络中是指用于保护内部网不受外部网的非法入侵的设备,它是利用网络层的IP包过滤程序以及一些规则来保护内部网的一种策略,有硬件实现的,也有软件实现的. 运行防火墙的计算机(以下称防火墙)既连接外部网,又连接内部网.一般

用 Xamarin 和 Visual Studio 构建 iOS 应用

本文讲的是用 Xamarin 和 Visual Studio 构建 iOS 应用, 当创见一个 iOS 的应用程序的时候,开发者们一贯倾向于使用那些由 Apple 公司提供的编程语言和 IDE: Objective-C /Swift 和 Xcode.然而,这并不是唯一的选择 - 你还可以通过使用很多其他的编程语言和框架去创建一个 iOS 应用程序. Xamarin 是最热门的选择方式之一,它是一个跨平台的开发框架,允许你使用 C# 和 Visual Studio 开发 iOS, Android,

构建iOS持续集成平台(三)CI服务器与自动化部署

CI服务器 写到这儿,对于iOS开发者来说,需要准备好: 一个比较容易获取的源代码仓库(包含源代码) 一套自动化构建脚本 一系列围绕构建的可执行测试 接下来就需要一个CI服务器来根据源代码的变更触发构建,监控测试结果.目前,业界比较流行的,支持iOS构建的CI服务器有Travis CI和Jenkins Travis CI Travis CI[20]是一个免费的云服务平台,主要功能就是为开源社区提供免费的CI服务,对于商业用户可以使用Travis Pro版本,其基本上支持所有目前主流的语言,Obj

构建iOS持续集成平台(二)测试框架

测试框架 有了自动化构建和依赖管理之后,开发者可以很轻松的在命令行构建整个项目,但 是,作为持续集成平台来说,最重要的还是测试,持续集成最大的好处在于能够尽早发现问题,降低解 决问题的成本.而发现问题的手段主要就是测试.在Martin Fowler的Test Pyramid[10]一文中论述了 测试金子塔的概念,测试金字塔的概念来自Mike Cohn,在他的书Succeeding With Agile中有详细描述 :测试金字塔最底层是单元测试,然后是业务逻辑测试,如果更细化一点的话,可以分为把完

构建iOS持续集成平台(一)自动化构建和依赖管理

2000年Matin Fowler发表文章Continuous Integration[1]:2007年,Paul Duvall, Steve Matyas 和 Andrew Glover合著的<Continuous Integration:Improving Software Quality and Reducing Risk> [2]出版发行,该书获得了2008年的图灵大奖.持续集成理念经过10多年的发展,已经成为了 业界的标准.在Java, Ruby的世界已经诞生了非常成熟的持续集成工具

服务架构:一步步构建大型网站架构详细介绍

今天我们来谈谈一个网站一般是如何一步步来构建起系统架构的,虽然我们希望网站一开始就能有一个很好的架构,但马克思告诉我们事物是在发展中不断前 进的,网站架构也是随着业务的扩大.用户的需求不断完善的,下面是一个网站架构逐步发展的基本过程,读完后,请思考,你现在在哪个阶段. 架构演变第一步:物理分离WebServer和数据库 最开始,由于某些想法,于是在互联网上搭建了一个网站,这个时候甚至有可能主机都是租借的,但由于这篇文章我们只关注架构的演变历程,因此就假 设这个时候已经是托管了一台主机,并且有一定

移动端路由层设计

什么是移动端路由层: 路由层的概念在服务端是指url请求的分层解析,将一个请求分发到对应的应用处理程序.移动端的路由层指的是将诸如App内页面访问.H5与App访问的访问请求和App间的访问请求,进行分发处理的逻辑层. 移动端路由层需要解决的问题: 对外部提供远程访问的功能,实现跨应用调用响应,包括H5应用调用.其他App应用调用.系统访问调用等 原生页面.模块.组件等定义,统称为资源(Resource),在跨应用调用和路由层在不同端实现的业务表现需要一致的前提下,需要对资源进行定义,在路由提供

深度学习在 iOS 上的实践 —— 通过 YOLO 在 iOS 上实现实时物体检测

本文讲的是深度学习在 iOS 上的实践 -- 通过 YOLO 在 iOS 上实现实时物体检测, 原文地址:Real-time object detection with YOLO 原文作者:Matthijs Hollemans 译文出自:掘金翻译计划 译者:Danny Lau 校对者:Dalston Xu ,DeepMissea 深度学习在 iOS 上的实践 -- 通过 YOLO 在 iOS 上实现实时物体检测 译者注: 在阅读这篇文章之前可能会遇到的一些名词,这里是解释(我自己也查了相当多的资

从Chrome源码看浏览器如何构建DOM树

这几天下了Chrome的源码,安装了一个debug版的Chromium研究了一下,虽然很多地方都一知半解,但是还是有一点收获,将在这篇文章介绍DOM树是如何构建的,看了本文应该可以回答以下问题: IE用的是Trident内核,Safari用的是Webkit,Chrome用的是Blink,到底什么是内核,它们的区别是什么? 如果没有声明<!DOCTYPE html>会造成什么影响? 浏览器如何处理自定义的标签,如写一个<data></data>? 查DOM的过程是怎么样的