一、前言

一个比较成熟的App,经历了多个版本的迭代之后,为了方便调式和测试,往往会积累一些工具来应付这些场景。最近我们组就开源了一款适用于iOS App线下开发、测试、验收阶段,内置在App中的工具集合。使用DoraemonKit,你无需连接电脑,就可以对于App的信息进行快速的查看。一键接入、使用方便,提高开发、测试、视觉同学的工作效率,提高我们App上线的完整度和稳定性。

目前DoraemonKit拥有的功能大概分为以下几点:

  1. 常用工具 : App信息展示,沙盒浏览、MockGPS、H5任意门、子线程UI检查、日志显示。
  2. 性能工具 : 帧率监控、CPU监控、内存监控、流量监控、自定义监控。
  3. 视觉工具 : 颜色吸管、组件检查、对齐标尺。
  4. 业务专区 : 支持业务测试组件接入到DoraemonKit面板中。

拿我们App接入效果如下:

上面两行是业务线自定义的工具,接入方可以自定义。除此之外都是内置工具集合。

因为里面功能比较多,大概会分三篇文章介绍DoraemonKit的使用和技术实现,这是第一篇主要介绍常用工具集中的几款工具实现。

二、技术实现

2.1:App信息展示

我们要看一些手机信息或者App的一些基本信息的时候,需要到系统设置去找,比较麻烦。特别是权限信息,在我们app装的比较多的时候,我们很难快速找到我们app的权限信息。而这些信息从代码角度都是比较容易获取的。我们把我们感兴趣的信息列表出来直接查看,避免了去手机设置里查看或者查看源代码的麻烦。

获取手机型号

我们从手机设置里面是找不到我们的手机具体是哪一款的文字表述的,比如我的手机是iphone8 Pro,在手机型号里面显示的是MQ8E2CH/A。对于iPhone不熟悉的人很难从外表对iphone进行区分。而手机型号,我们从代码角度就很好获取。

+ (NSString *)iphoneType{struct utsname systemInfo;uname(&systemInfo);NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];//iPhoneif ([platform isEqualToString:@"iPhone1,1"]) return @"iPhone 1G";...//其他对应关系请看下面对应表    return platform;
}复制代码

iPhone设备类型与通用手机类型一一对应关系表

设备类型 通用类型
iPhone1,1 iPhone 1G
iPhone1,2 iPhone 3G
iPhone2,1 iPhone 3GS
iPhone3,1 iPhone 4
iPhone3,2 iPhone 4
iPhone4,1 iPhone 4S
iPhone5,1 iPhone 5
iPhone5,2 iPhone 5
iPhone5,3 iPhone 5C
iPhone5,4 iPhone 5C
iPhone6,1 iPhone 5S
iPhone6,2 iPhone 5S
iPhone7,1 iPhone 6 Plus
iPhone7,2 iPhone 6
iPhone8,1 iPhone 6S
iPhone8,2 iPhone 6S Plus
iPhone8,4 iPhone SE
iPhone9,1 iPhone 7
iPhone9,3 iPhone 7
iPhone9,2 iPhone 7 Plus
iPhone9,4 iPhone 7 Plus
iPhone10,1 iPhone 8
iPhone10.4 iPhone 8
iPhone10,2 iPhone 8 Plus
iPhone10,5 iPhone 8 Plus
iPhone10,3 iPhone X
iPhone10,6 iPhone X
iPhone11,8 iPhone XR
iPhone11,2 iPhone XS
iPhone11,4 iPhone XS Max
Phone11,6 iPhone XS Max

获取手机系统版本

//获取手机系统版本
NSString *phoneVersion = [[UIDevice currentDevice] systemVersion];
复制代码

获取App BundleId

一个app分为测试版本、企业版本、appStore发售版本,每一个app长得都一样,如何对他们进行区分呢,那就要用到BundleId这个属性了。

//获取bundle id
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];复制代码

获取App 版本号

//获取App版本号
NSString *bundleVersionCode = [[[NSBundle mainBundle]infoDictionary] objectForKey:@"CFBundleVersion"];复制代码

权限信息查看

当我们发现App运行不正常,比如无法定位,网络一直失败,无法收到推送信息等问题的时候,我们第一个反应就是去手机设置里面去看我们app相关的权限有没有打开。DoraemonKit集成了对于地理位置权限、网络权限、推送权限、相机权限、麦克风权限、相册权限、通讯录权限、日历权限、提醒事项权限的查询。

由于代码比较多,这里就不一一贴出来了。大家可以去DorameonKit/Core/Plugin/AppInfo中自己去查看。这里讲一下,权限查询结果几个值的意义。

  • NotDetermined => 用户还没有选择。
  • Restricted => 该权限受限,比如家长控制。
  • Denied => 用户拒绝使用该权限。
  • Authorized => 用户同意使用该权限。

2.2:沙盒浏览

以前如果我们要去查看App缓存、日志信息,都需要访问沙盒。由于iOS的封闭性,我们无法直接查看沙盒中的文件内容。如果我们要去访问沙盒,基本上有两种方式,第一种使用Xcode自带的工具,从Windows-->Devices进入设备管理界面,通过Download Container的方式导出整个app的沙盒。第二种方式,就是自己写代码,访问沙盒中指定文件,然后使用NSLog的方式打印出来。这两种方式都比较麻烦。

DoraemonKit给出的解决方案:就是自己做一个简单的文件浏览器,通过NSFileManager对象对沙盒文件进行遍历,同时支持对于文件和文件夹的删除操作。对于文件支持本地预览或者通过airdrop的方式或者其他分享方式发送到PC端进行更加细致的操作。

怎么用NSFileManager对象遍历文件和删除文件这里就不说了,大家可以参考DorameonKit/Core/Plugin/Sanbox中的代码。这里讲一下:如何将手机中的文件快速上传到Mac端?刚开始我们还绕了一点路,我们在手机端搭了一个微服务,mac通过浏览器去访问它。后来和同事聊天的时候知道了UIActivityViewController这个类,可以十分便捷地吊起系统分享组件或者是其他注册到系统分享组件中的分享方式,比如微信、钉钉。实现代码非常简单,如下所示:

- (void)shareFileWithPath:(NSString *)filePath{NSURL *url = [NSURL fileURLWithPath:filePath];NSArray *objectsToShare = @[url];UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];NSArray *excludedActivities = @[UIActivityTypePostToTwitter, UIActivityTypePostToFacebook,UIActivityTypePostToWeibo,UIActivityTypeMessage, UIActivityTypeMail,UIActivityTypePrint, UIActivityTypeCopyToPasteboard,UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll,UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr,UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo];controller.excludedActivityTypes = excludedActivities;[self presentViewController:controller animated:YES completion:nil];
}
复制代码

2.3:MockGPS

我们有些业务会根据地理位置不同,而有不同的业务处理逻辑。而我们开发或者测试,当然不可能去每一个地址都测试一遍。这种情况下,测试同学一般会找到我们让我们手动改掉系统获取经纬度的回调,或者修改GPX文件,然后再重新打一个包。这样也非常麻烦。

DoraemonKit给出的解决方案:提供一套地图界面,支持在地图中滑动选择或者手动输入经纬度,然后自动替换掉我们App中返回的当前经纬度信息。这里的难点是如何不需要重新打包自动替换掉系统返回的当前经纬度信息?

CLLocationManager的delegate中有一个方法如下:

/**  locationManager:didUpdateLocations:**  Discussion:*    Invoked when new locations are available.  Required for delivery of*    deferred locations.  If implemented, updates will*    not be delivered to locationManager:didUpdateToLocation:fromLocation:**    locations is an array of CLLocation objects in chronological order.*/
- (void)locationManager:(CLLocationManager *)managerdidUpdateLocations:(NSArray<CLLocation *> *)locations API_AVAILABLE(ios(6.0), macos(10.9));复制代码

我们通常是在这个函数中获取当前系统的经纬度信息。我们如果想要没有侵入式的修改这个函数的默认实现方式,想到的第一个方法就是Method Swizzling。但是真正在实现过程中,你会发现Method Swizzling需要当前实例和方法,方法是- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations 我们有了,但是实例,每一个app都有自己的实现,无法做到统一处理。我们就换了一个思路,如何能获取该实现了该定位方法的实例呢?就是使用Method Swizzling Hook住CLLocationManager的setDelegate方法,就能获取具体是哪一个实例实现了- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations 方法。

具体方法如下:

第一步: 生成一个CLLocationManager的分类CLLocationManager(Doraemon),在这个分类中,实现- (void)doraemon_swizzleLocationDelegate:(id)delegate这个方法,用来进行方法交换。

- (void)doraemon_swizzleLocationDelegate:(id)delegate {if (delegate) {//1、让所有的CLLocationManager的代理都设置为[DoraemonGPSMocker shareInstance],让他做中间转发[self doraemon_swizzleLocationDelegate:[DoraemonGPSMocker shareInstance]];//2、绑定所有CLLocationManager实例与delegate的关系,用于[DoraemonGPSMocker shareInstance]做目标转发用。[[DoraemonGPSMocker shareInstance] addLocationBinder:self delegate:delegate];//3、处理[DoraemonGPSMocker shareInstance]没有实现的selector,并且给用户提示。Protocol *proto = objc_getProtocol("CLLocationManagerDelegate");unsigned int count;struct objc_method_description *methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count);NSMutableArray *array = [NSMutableArray array];for(unsigned i = 0; i < count; i++){SEL sel = methods[i].name;if ([delegate respondsToSelector:sel]) {if (![[DoraemonGPSMocker shareInstance] respondsToSelector:sel]) {NSAssert(NO, @"你在Delegate %@ 中所使用的SEL %@,暂不支持,请联系DoraemonKit开发者",delegate,sel);}}}free(methods);}else{[self doraemon_swizzleLocationDelegate:delegate];}
}
复制代码

在这个函数中主要做了三件事情,1、将所有的定位回调统一交给[DoraemonGPSMocker shareInstance]处理 2、[DoraemonGPSMocker shareInstance]绑定了所有CLLocationManager与它的delegate的一一对应关系。3、处理[DoraemonGPSMocker shareInstance]没有实现的selector,并且给用户提示。

第二步:当有一个定位回调过来的时候,我们先传给[DoraemonGPSMocker shareInstance],然后[DoraemonGPSMocker shareInstance]再转发给它绑定过的所有的delegate。那我们App为例,绑定关系如下:

{"0x2800a07a0_binder" = "<CLLocationManager: 0x2800a07a0>";"0x2800a07a0_delegate" = "<MAMapLocationManager: 0x2800a04d0>";"0x2800b59a0_binder" = "<CLLocationManager: 0x2800b59a0>";"0x2800b59a0_delegate" = "<KDDriverLocationManager: 0x2829d3bf0>";
}
复制代码

由此可见,我们App的统一定位KDDriverLocationManager和苹果地图的定位MAMapLocationManager都是使用都是CLLocationManager提供的。

具体 DoraemonGPSMocker这个类如何实现,请参考DorameonKit/Core/Plugin/GPS中的代码。

2.4:H5任意门

有的时候Native和H5开发同时开发一个功能,H5依赖native提供入口,而这个时候Native还没有开发好,这个时候H5开发就没法在App上看到效果。再比如,有些H5页面处于的位置比较深入,就像我们代驾司机端,做单流程比较多,有的H5界面需要很繁琐的操作才能展示到App上,不方便我们查看和定位问题。 这个时候我们可以为app做一个简单的浏览器,输入url,使用自带的容器进行跳转。因为每一个app的H5容器基本上都是自定义过得,都会有自己的bridge定制化,所以这个H5容器没有办法使用系统原生的UIWebView或者WKWebView,就只能交给业务方自己去完成。我们在DorameonKit初始化的时候,提供了一个回调让业务方用自己的H5容器去打开这个Url:

[[DoraemonManager shareInstance] addH5DoorBlock:^(NSString *h5Url) {//使用自己的H5容器打开这个链接}];复制代码

这个工具实现比较简单,就不多说了,代码路径在DorameonKit/Core/Plugin/H5.

2.5:子线程UI检查

在iOS中是不允许在子线程中对UI进行操作和渲染的,不然会造成未知的错误和问题,甚至会导致crash。我们在最近几个版本中发现新增了一些crash,调查原因就是在子线程中操作UI导致的。为了对于这种情况可以提早被我们发现,我在在DorameonKit中增加了子线程UI渲染检查查询。

具体事项思路,我们hook住UIView的三个必须在主线程中操作的绘制方法。1、setNeedsLayout 2、setNeedsDisplay 3、setNeedsDisplayInRect:。然后判断他们是不是在子线程中进行操作,如果是在子线程进行操作的话,打印出当前代码调用堆栈,提供给开发进行解决。具体代码如下:

@implementation UIView (Doraemon)+ (void)load{[[self  class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsLayout) swizzledSel:@selector(doraemon_setNeedsLayout)];[[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsDisplay) swizzledSel:@selector(doraemon_setNeedsDisplay)];[[self class] doraemon_swizzleInstanceMethodWithOriginSel:@selector(setNeedsDisplayInRect:) swizzledSel:@selector(doraemon_setNeedsDisplayInRect:)];
}- (void)doraemon_setNeedsLayout{[self doraemon_setNeedsLayout];[self uiCheck];
}- (void)doraemon_setNeedsDisplay{[self doraemon_setNeedsDisplay];[self uiCheck];
}- (void)doraemon_setNeedsDisplayInRect:(CGRect)rect{[self doraemon_setNeedsDisplayInRect:rect];[self uiCheck];
}- (void)uiCheck{if([[DoraemonCacheManager sharedInstance] subThreadUICheckSwitch]){if(![NSThread isMainThread]){NSString *report = [BSBacktraceLogger bs_backtraceOfCurrentThread];NSDictionary *dic = @{@"title":[DoraemonUtil dateFormatNow],@"content":report};[[DoraemonSubThreadUICheckManager sharedInstance].checkArray addObject:dic];}}
}@end
复制代码

完整代码实现请参考DorameonKit/Core/Plugin/SubThreadUICheck

2.6:日志显示

这个主要是方便我们查看本地日志,以前我们如果要查看日志,需要自己写代码,访问沙盒导出日志文件,然后再查看。也是比较麻烦的。

DoraemonKit的解决方案是:我们每一次触发日志的时候,都把日志内容显示到界面上,方便我们查看。 如何实现的呢?因为我们这个工具并不是一个通用性的工具,只针对于底层日志库是CocoaLumberjack的情况。稍微讲一下的CocoaLumberjack原理,所有的log都会发给DDLog对象,其运行在自己的一个GCD队列中,之后,DDLog会将log分发给其下注册的一个或者多个Logger中,这一步在多核下面是并发的,效率很高。每一个Logger处理收到的log也是在它们自己的GCD队列下做的,它们询问其下的Formatter,获取Log消息格式,然后根据Logger的逻辑,将log消息分发到不同的地方。系统自带三个Logger处理器,DDTTYLogger,主要将日志发送到Xcode控制台;DDASLLogger,主要讲日志发送到苹果的日志系统Console.app; DDFileLogger,主要将日志发送到文件中保存起来,也是我们开发用到最多的。但是自带的Logger并不满足我们的需求,我们的需求是将日志显示到UI界面中,所以我们需要新建一个类DoraemonLogger,继承于DDAbstractLogger,然后重写logMessage方法,将每一条传过来的日志打印到UI界面中。

这个工具参考LumberjackConsole这个开源项目完成,因为刚出iOS11的时候,作者没有适配,所以我们自己拷贝一份代码出来,自己维护了。 完整代码实现请参考DorameonKit/WithLogger中.

三、总结

写这篇文章主要是为了能够让大家对于DorameonKit进行快速的了解,大家如果有什么好的想法,或者发现我们的这个项目有bug,欢迎大家去github上提Issues或者直接Pull requests,我们会第一时间处理,也希望我们这个工具集合能在大家的一起努力下,做得更加完善。

如果大家觉得我们这个项目还可以的话,点上一颗star吧。

DoraemonKit项目地址:github.com/didi/Doraem…

四、交流群

转载于:https://juejin.im/post/5be9959f5188251e1f50bd30

iOS研发助手DoraemonKit技术实现(一)相关推荐

  1. 小程序影藏溢出的gif_分享几个优质开源项目 | 电商类app,趣享 gif,研发助手DoraemonKit,github小程序...

    本来想一个个分享,这样标题比较好起,不过感觉有点少,那就不定期攒一些分享吧.其中部分来源投稿,有些是我自己无意发现的. 1非常方便的 github 小程序员 经常想在手机上看一些最新的github项目 ...

  2. ios研发Mini LED技术 iPad/MacBook尺寸将更大

    著名苹果分析师郭明錤(Ming-Chi Kuo)的研究称,苹果在未来几年可能推出几款新产品,而这几款新产品使用的是一种新显示技术,能够更好地显示颜色和对比度. 下一代"mini LED&qu ...

  3. DoraemonKit,一款功能齐全的客户端 (iOS、Android) 研发助手,你值得拥有。

    介绍 DoraemonKit(哆啦A梦)是一款提供给开发.测试.设计同学的提高工作效率的研发助手类开源产品.每一项功能都解决了我们在研发测试过程中的一个痛点.使用DoraemonKit,您无需使用电脑 ...

  4. android滴滴开源,DoKit - 滴滴开源的一款功能齐全的客户端( iOS 、Android )研发助手...

    技术编辑:鸣飞 发自北京 SegmentFault 思否报道 | 公众号:SegmentFault SegmentFault 思否消息:滴滴技术团队今天宣布,Dokit 3.0 版本正式发布,Dora ...

  5. 外卖也智能!美团骑手智能助手的技术与实践

    点击关注 InfoQ,置顶公众号 接收程序员的 8 点技术早餐 作者 | 何仁清 编辑 | 臧秀涛 随着数字化时代的到来,外卖市场近年来发展非常迅猛.对外卖物流系统而言,配送效率和用户体验至关重要.而 ...

  6. 【北京站】技术沙龙第19期:千万日活量级 iOS 应用背后的技术

    美团点评技术沙龙由美团·点评技术团队主办,每期沙龙邀请美团 · 点评及其他互联网公司的技术专家分享来自一线的实践经验,覆盖各主要技术领域. 美团·大众点评作为国内最大的 O2O 平台,旗下有多个iOS ...

  7. iOS视频硬编码技术

    iOS视频硬编码技术 一.iOS视频采集硬编码 基本原理 硬编码 & 软编码 硬编码:通过系统自带的Camera录制视频,实际上调用的是底层的高清编码硬件模块,即显卡,不使用CPU,速度快 软 ...

  8. linux 车载视频监控,基于Linux平台车载视频监控系统研发-计算机科学与技术专业论文.docx...

    基于Linux平台车载视频监控系统研发-计算机科学与技术专业论文 目录 HYPERLINK \l "_bookmark0" 第一章 绪论1 HYPERLINK \l "_ ...

  9. 滴滴顺风车女乘客遇害;华为法律应对澳 5G 禁令;苹果研发新背板技术|极客头条...

    「CSDN 极客头条」,是从 CSDN 网站延伸至官方微信公众号的特别栏目,专注于一天业界事报道.风里雨里,我们将每天为朋友们,播报最新鲜有料的新闻资讯,让所有技术人,时刻紧跟业界潮流. 快讯速知 滴 ...

  10. iOS即时语音聊天技术实践

    CMDN Club第十五期活动已于3月15日顺利举行,本次活动以"移动平台语音技术的应用与实践"主题,以语音技术开发为焦点,从语音基础服务.语音产品开发.语音技术实现等多个维度,探 ...

最新文章

  1. 风格化手绘纹理包 CGTrader – Stylized Mix Vol. 41 – Hand Painted Texture Pack
  2. Python pickle使用
  3. 手机KG音乐怎么下载竖屏MV
  4. Python语言学习:利用python获取当前/上级/上上级目录路径(获取路径下的最后叶目录的文件名、合并两个不同路径下图片文件名等目录/路径案例、正确加载图片路径)之详细攻略
  5. angr学习笔记(6)(内存地址单元符号化)
  6. boost::fusion::make_unfused用法的测试程序
  7. 基于阈值的损失函数_推荐 :常见损失函数和评价指标总结(附公式amp;代码)...
  8. bzoj4598 [Sdoi2016]模式字符串 hash+点分
  9. python3.5安装教程及环境配置_Python安装和环境配置教程
  10. java 获取xml 版本号_java解析xml获取对应值
  11. Oracle PL\SQL 基础学习一
  12. 在LoadRunner中设置HTTP请求time-out的时间
  13. libevent 编程疑难解答
  14. 【论文笔记】Joint Cascade Face Detection and Alignment
  15. 抖音记事本代码html,抖音使用教程 抖音表白代码使用方法介绍
  16. Android APP压力测试 之Monkey日志自动分析脚本
  17. 使用bat一键修改ip地址(包括静态、动态ip)
  18. MATLAB鲁棒控制器实现
  19. pioneer软件VoLTE测试步骤,世纪鼎利Pioneer连接移动平台进行VoLTE测试操作说明综述...
  20. 起底身份倒卖产业:那些被公开叫卖的人生

热门文章

  1. Kafka学习之(四)PHP操作Kafka
  2. (转载)python日期函数
  3. 多个相同name的文本输入框,输入其中一个后,使剩下的不能输入值
  4. 反射认识_03_改变成员变量Fields
  5. BZOJ 1632: [Usaco2007 Feb]Lilypad Pond
  6. vc6.0垃圾文件清理工具_C盘空间逐渐被垃圾文件填满,详细清理方法介绍
  7. Linux 更新 CPU microcode
  8. Cross-compiling Apache httpd 2.2.19 for uclibc
  9. qt信号槽踩坑日记(信号执行一次,槽函数执行多次解决方案)
  10. php不包含_php 正则 不包含某字符串的正则表达式