iOS App连接外设的几种方式

iOS development

一般iOS开发者做APP开发大部分时候都是通过Http(s)请求跟后台服务器打交道,做一些信息展示和用户交互。很少涉及到去跟外部硬件设备连接的开发。随着近年来车联网和物联网的兴起,智能家居和智能硬件的逐步火热,越来越多的app被开发出来,用来跟硬件设备进行来连接,获取硬件相关信息展示或者发送指令控制硬件来提供服务。故本文就针对iOS的app如何跟外部设备进行连接通信这个问题跟大家交流一下,如有不正确的地方恳请各位看官指正。本文原创,欢迎转载,转载请注明出处。

iOS App连接外设的几种方式

如上图所示,我把iOS App连接外设的常用方式总结了一下,可以分为三大类:

iOS App连接外部硬件方式*类是通过网络端口

建立Socket使用TCP/IP协议族进行通信,天然支持多通道,想要几个通道就建几个socket就行了。它主要有三种方式,*种方式是Wi-Fi连接,优点是:简单,不需要集成MFi芯片,只要对应的硬件有无线网卡,然后手机和硬件连接到同一个局域网中就可以使用socket通过网络协议通信了。缺点也很明显:(1)无线连接信号容易受到干扰,不太稳定,容易断开;(2)如果硬件使用的场合没有公共wifi,就需要手机自建热点共享,硬件进行热点接入,操作步骤较多,对用户来说学习使用成本较高,并且热点共享要求手机本身的数据移动网络是稳定的,在没有移动数据网络信号的地方,热点无法建立。

使用网络端口的第二种方式是USB热点共享,这个其实跟Wi-Fi中的热点共享非常类似,也不需要集成MFI芯片,区别就是USB线共享热点,走的是有线,不容易受到干扰,更稳定,而且iPhone可以边使用可以边充电;缺点也是操作步骤比较复杂,需要先打开个人热点共享;

使用网络端口的第三种方式是NCM,就是把USB端口虚拟成标准的网络端口,然后手机和外设就能通过有线网络直连了,可以理解成手机和外设通过一跟网线连起来了,然后就可以用socket通过TCP,UDP进行通信了。它的优点是:有线连接,非常稳定,带宽足够;也不依赖移动网络信号;但是它的缺点就是:需要集成MFI芯片并进行MFI认证,有一定门槛。更变态的是这么好的一种方式,苹果只允许它自己的CarPlay使用,如果硬件使用NCM跟其他app通信,是不能通过MFI认证的。

关于如何使用Socket进行TCP、UDP连接,推荐github上的开源项目CocoaAsyncSocket

iOS App连接外部硬件方式的第二大类是EAP

EAP全拼是External Accessory Protocol ,外部设备协议。这个是苹果推荐使用的外设连接方式。需要外设集成MFI芯片进行MFI认证。手机端开发相对简单,只要集成iOS系统提供的一个框架ExternalAccessory.framework,并且在info.plist中配置好协议字符串(Supported external accessory protocols),当iOS 设备通过USB线或者蓝牙连接到对应硬件时,iOS系统会把符合MFI认证要求的外设抽象成了一个流对象,App通过指定的协议字符串来创建一个EASession类的实例来访问到该流对象,就能通过NSInputStream和NSOutputStream跟硬件件进行通信了。它有两种模式,一种是叫EASession的模式,它带宽相对较低,但是允许同时通过多个协议字符串创建多个会话,也就是说直接支持多个通道;另外一种是Native Transport的模式,这种模式的优点是带宽足够大,理论值是100MB以上,但是不支持多通道,如果业务层需要支持多数据通道的话需要App自己进行通道的复用与拆分,并且Native Transport需要iPhone工作在USB host模式,硬件需要支持USB 模式切换。

关于如何使用EAP跟外部设备进行通信,可以参考苹果官方的demo进行入门和学习。

iOS App连接外部硬件方式的第三大类就是BLE

BLE即低功耗蓝牙,是iOS7.0以后才支持的连接方式。它的优点是不需要集成MFI芯片做认证,功耗低,手机端开发也相对简单,集成iOS系统提供的CoreBluetooth.framework就行。缺点是:带宽很低,一般适合于只需要传输少量数据的场景。比如前两年非常火爆的各种所谓智能硬件,像智能水杯,智能体重计,运动手环等,都是采用这种连接方式。

关于如何使用BLE进行硬件连接,可以参考本人在github的一个小开源项目(https://github.com/luoxubin/BlueTooth4.0)。另外本人自己业余时间也做过一个BLE连接外设的App-裤宝(名字有创意吧,裤子里的宝贝,是跟我另外两个小伙伴一起做的创业项目,目前该项目黄了, 不过app还在线上,AppStore里搜索“裤宝”可以下载到

总结一下,图中带MFI字样的表示该连接方式需要硬件集成MFi芯片,做MFi认证。关于苹果的MFI认证,对iOS开发中来说其实是一个比较陌生并且繁琐的topic,原因如下:

(1)网上鲜有资料,Google基本上查不到。 因为MFi认证是由硬件生产商主导进行的,苹果首先对硬件生产商的实力(质量,信誉,生产规模)有很苛刻的要求,满足要求的才有进行MFI认证的资格。满足MFi认证资格要求的硬件生产商,提交了MFi产品计划后才能得到苹果MFi开发的官方文档,这个文档是带水印的,不允许外泄;

(2)MFi认证周期很长,过程也很复杂;

(3)苹果官方沟通渠道很窄,电话打不通,邮件回复不及时。

iOS 性能调优, 成为一名合格 iOS 程序员必须掌握的技能

提供了基于Swift3.0模仿的新浪微博的Demo,大家可以下载看一看:基于Swift3.0高仿的微博客户端,里面针对于微博首页的复杂页面的优化做了很多的处理,页面的FPS 一直保持在59 ~ 60 。
看下demo的效果:

%title插图%num
FPS测试.gif

CPU 和GPU

关于绘图和动画有两种处理方式CPU(中央处理器)和GPU(图形处理器),CPU的工作都在软件层面,而GPU的在硬件层面。
总的来说,可以使用CPU做任何事情,但是对于图像的处理,通常GPU会更快,因为GPU使用图像对高度并行浮点运算做了优化(尽管我也不知道是什么鬼??),所以,我们想尽可能的把屏幕渲染的工作交给硬件去处理,而问题在于GPU并没有无限制处理的性能,一旦资源用尽,即使CPU并没有完全占用,GPU性能还是会下降。
所以,目前大多的性能优化都是关于智能利用GPU和CPU,平衡它们之间工作负载。

测量,而不是猜测

现在知道哪些可能会影响性能,该如何修复呢?有许多传统的诡计来优化,但如果盲目使用的话,可能会造成更多性能上的问题,而不是优化了
如何正确的测量而不是猜测这点很重要,根据性能相关知识写出的代码不同于仓促优化,前者是正确的姿势,后者则是在浪费生命
那改如何测量,*步就是确保在真实环境下测试你的程序

真机测试,而不是模拟器

当你开始做一些性能方面的工作时候,一定要在真机上测试,而不是模拟器,模拟器虽然可以加快开发效率,但却不能准确提供真机的性能参数
模拟器运行在Mac上,然而Mac上的cpu比ios设备要快很多,相反,Mac上的GPU和ios设备上的不一样,模拟器不得已需要在软件层面(CPU)模拟ios设备,所以GPU的相关操作在模拟器上面运行的会更慢
另一件重要的就是性能测试的时候一定要用发布配置,而不是调试模式,因为当用发布环境打包的时候,编译器会引入一些提高性能的优化,比如:去除调试符号或者移除并重新组织代码,因为可以自己做到这些,比如禁用NSlog、print语句,因为 ,只需要关心发布性能。

测试帧率

可以在程序中使用CADisplayLink来测量帧率,在屏幕上显示出来,我用Swift3.0模仿YY大神的代码写了一个简单的FPS指示器 YWFPSLabel,使用CADisplayLink监视FPS数值,日常开发的时候,可以有直接的体现,不用再靠猜了…
YWFPSLabel集成也很方便,在AppDelegate的application方法里加下面两句即可

  1. let FPSLab = YWFPSLabel(frame: CGRect())
  2. UIApplication.shared.keyWindow!.addSubview(FPSLab)

不知道大家有木有看到头部那个小label啊~~~
但是应用内的FPS显示并不能完全真实的测量出性能,因为它仅仅能测试出应用内的帧率,还有很多是动画都是在应用外发生(在渲染进程中处理),但应用内FPS计数可以对一些性能问题提供参考,一旦找到问题,需要更多的精确详细信息来定位问题所在,我们就要使用Instuments了,它可以看到更多准确是信息,查看到所有与显示的数据。

Instuments

Instuments是Xcode套件中没有被充分利用的工具,很多iOS开发者从来没用过Instrument,特别是通过短暂培训出来的同学们,所以,很多面试官也会问性能条调优方面的知识,来判断面试的同学是否真正应用对年开发经验。

  • Activity Monitor

    个人觉的很像Windows的任务管理器,可以查看所有的进程,以及进程的内存、cpu使用百分比等数据等,就不多介绍了,打开一看大概就知道怎么回事

  • Allocations

    管理内存是app开发中*重要的一个方面,对于开发者来说,在程序架构中减少内存的使用通常都是使用Allocations去定位和找出减少内存使用的方式
    接下来,谈一下内存泄漏的两种情况

  • *种:为对象A申请了内存空间,之后再也没用过对象A,也没释放过A导致内存泄漏,这种是Leaked Memory内存泄漏
  • 第二种:类似于递归,不断地申请内存空间导致的内存泄漏,这种情况是Abandoned Momory
    此工具可以让开发者很好的了解每个方法占用内存的情况,并定位相关的代码

    %title插图%num
    Allocations查看方法占用内存.png

    右键就可以打开Xcode自动定位到相关占用内存方法的代码上

%title插图%num
定位到相关代码.png

第二种情况可以根据下图的操作清晰的找到对用的代码问题

%title插图%num
定位Abandoned Momory.png

解释一下,第二种情况我们应该如何操作,重复的执行一系列的操作时候内存不会继续增加,比如打开和关闭一个窗口,这样的操作,每一次操作的前后,内存应该是相同的,通过多次循环操作,内存不会递增下去,通过这种分析结果,观察内存分配趋势,当发现不正确的结果或者矛盾的结果,就可以研究是不是Abandoned Momory的问题,并可以修正这个问题了

Core Animation

之前说过自己写的YWFPSLabel只能检测应用内的FPS,而此工具考虑到了程序外的动画,理想的FPS值为60左右,过低的话就用该进性优化了,根据WWDC的说法,当FPS 低于45的时候,用户就会察觉到到滑动有卡顿

%title插图%num
Core Animation.png

圈着数字红色方框中的数字,代表着FPS值,理论上60*佳,实际过程中59就可以了,说明就是很流畅的,说明一下操作方式:在手指不离开屏幕的情况下,上下滑动屏幕列表
介绍一下Deug Display中选项的作用

  • Color Blended Layers(混合过度绘制)

打开此选项屏幕的效果图如下:

%title插图%num
Color Blended Layers.jpg

这个选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮(也就是多个半透明图层的叠加),由于重绘的原因,混合对GPU性能会有影响,同时也是滑动或者动画掉帧的罪魁祸首之一
GPU每一帧的绘制的像素有*大限制,这个情况下可以轻易绘制整个屏幕的像素,但如果发生重叠像素的关系需要不停的重绘同一区域的,掉帧和卡顿就有可能发生
GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗CPU的资源,同样,合并不同图层的透明重叠元素消耗的资源也很大,所以,为了快速处理,一般不要使用透明图层,
1). 给View添加一个固定、不透明的颜色
2). 设置opaque 属性为true
但是这对性能调优的帮助并不大,因为UIView的opaque 属性默认为true,也就是说,只要不是认为设置成透明,都不会出现图层混合
而对于UIIimageView来说,不仅需要自身需要不是透明的,它的图片也不能含有alpha通道,这也上图9张图片是绿色的原因,因此图像自身的性质也可能会对结果有影响,所以你确定自己的代码没问题,还出现了混合图层可能就是图片的问题了
而针对于屏幕中的文字高亮成红色,是因为一没有给文字的label增加不透明的背景颜色,而是当UILabel内容为中文时,label的实际渲染区域要大于label的size,因为外围有了一圈的阴影,才会出现图层混合我们需要给中文的label加上如下代码:

  1. retweededTextLab?.layer.masksToBounds = true
  2. retweededTextLab?.backgroundColor = UIColor.groupTableViewBackground
  3. statusLab.layer.masksToBounds = true
  4. statusLab.backgroundColor = UIColor.white

看下效果图:

%title插图%num
图层混合优化.png

那些label的颜色也变成蓝色的了,这里有一点需要说明一下,
1). statusLab.layer.masksToBounds = true 单独使用不会出现离屏渲染
2). 如果对label设置了圆角的话,圆角部分会离屏渲染,离屏渲染的前提是位图发生了形变

  • Color Hits Green and Misses Red(光栅化缓存图层的命中情况)

这个选项主要是检测我们有无滥用或正确使用layer的shouldRasterize属性.成功被缓存的layer会标注为绿色,没有成功缓存的会标注为红色。
很多视图Layer由于Shadow、Mask和Gradient等原因渲染很高,因此UIKit提供了API用于缓存这些Layer,self.layer.shouldRasterize = true系统会将这些Layer缓存成Bitmap位图供渲染使用,如果失效时便丢弃这些Bitmap重新生成。图层Rasterization栅格化好处是对刷新率影响较小,坏处是删格化处理后的Bitmap缓存需要占用内存,而且当图层需要缩放时,要对删格化后的Bitmap做额外计算。 使用这个选项后时,如果Rasterized的Layer失效,便会标注为红色,如果有效标注为绿色。当测试的应用频繁闪现出红色标注图层时,表明对图层做的Rasterization作用不大。
在测试的过程中,*次加载时,开启光栅化的layer会显示为红色,这是很正常的,因为还没有缓存成功。但是如果在接下来的测试,。例如我们来回滚动TableView时,我们仍然发现有许多红色区域,那就需要谨慎对待了

  • Color Copied Image (拷贝的图片)

这个选项主要检查我们有无使用不正确图片格式,由于手机显示都是基于像素的,所以当手机要显示一张图片的时候,系统会帮我们对图片进行转化。比如一个像素占用一个字节,故而RGBA则占用了4个字节,则1920 x 1080的图片占用了7.9M左右,但是平时jpg或者png的图片并没有那么大,因为它们对图片做了压缩,但是是可逆的。所以此时,如果图片的格式不正确,则系统将图片转化为像素的时间就有可能变长。而该选项就是检测图片的格式是否是系统所支持的,若是GPU不支持的色彩格式的图片则会标记为青色,则只能由CPU来进行处理。CPU被强制生成了一些图片,然后发送到渲染服务器,而不是简单的指向原始图片的的指针。我们不希望在滚动视图的时候,CPU实时来进行处理,因为有可能会阻塞主线程。

  • Color Immediately (颜色立即更新)

通常 Core Animation 以每秒10此的频率更新图层的调试颜色,对于某些效果来说,这可能太慢了,这个选项可以用来设置每一帧都更新(可能会影响到渲染性能,所以不要一直都设置它)

  • Color Misaligned Image (图片对齐方式)

这里会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片,即图片Size和imageView中的Size不匹配,会使图过程片缩放,而缩放会占用CPU,所以在写代码的时候保证图片的大小匹配好imageView,如下图所示:
图片尺寸 170 * 220px

  1. let imageView = UIImageView(frame: CGRect(x: 50, y: 100, width: 170, height: 220))
  2. imageView.image = UIImage(named: “cat”)
  3. view.addSubview(imageView)

苹果的单位以点计算,而 imageView的尺寸是170 220 pt 而图片是 170220px,所以相当于在屏幕上对图片方法了一倍,看效果图如下:

%title插图%num
Color Misaligned Image.png

可以看到图片高亮成黄色显示,更改下imageView的大小

  1. let imageView = UIImageView(frame: CGRect(x: 50, y: 100, width: 85, height: 110))
  2. imageView.image = UIImage(named: “cat”)
  3. view.addSubview(imageView)

看下效果图

%title插图%num
Color Misaligned Image -2.png

当imageView和image的大小一致的时候,就正常显示了

  • Color Offscreen- Rendered Yellow (离屏渲染)

这里会把那些需要离屏渲染的到图层高亮成黄色,而出发离屏渲染的可能有

  1. /* 圆角处理 */
  2. view.layer.maskToBounds = truesomeView.clipsToBounds = true
  3. /* 设置阴影 */
  4. view.shadow..
  5. /* 栅格化 */
  6. view.layer.shouldRastarize = true

针对栅格化处理,我们需要指定屏幕的分辨率

  1. //离屏渲染 – 异步绘制 耗电
  2. self.layer.drawsAsynchronously = true
  3. //栅格化 – 异步绘制之后 ,会生成一张独立的图片 cell 在屏幕上滚动的时候,本质上滚动的是这张图片
  4. //cell 优化,要尽量减少图层的数量,想当于只有一层
  5. //停止滚动之后,可以接受监听
  6. self.layer.shouldRasterize = true
  7. //使用 “栅格化” 必须指定分辨率
  8. self.layer.rasterizationScale = UIScreen.main.scale

指定阴影的路径,可以防止离屏渲染

  1. // 指定阴影曲线,防止阴影效果带来的离屏渲染
  2. imageView.layer.shadowPath = UIBezierPath(rect: imageView.bounds).cgPath

这行代码制定了阴影路径,如果没有手动指定,Core Animation会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。
设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:

  1. // 设置圆角
  2. label.layer.masksToBounds = true
  3. label.layer.cornerRadius = 8
  4. label.layer.shouldRasterize = true
  5. label.layer.rasterizationScale = layer.contentsScale

如果界面中有很多控件需要设置圆角,比如tableView中,当tableView有超过25个圆角,使用如下方法

  1. view.layer.cornerRadius = 10
  2. view.maskToBounds = Yes

那么fps将会下降很多,特别是对某些控件还设置了阴影效果,更会加剧界面的卡顿、掉帧现象,对于不同的控件将采用不同的方法进行处理:
1). 对于label类,可以通过CoreGraphics来画出一个圆角的label
2). 对于imageView,通过CoreGraphics对绘画出来的image进行裁边处理,形成一个圆角的imageView,代码如下:

  1. /// 创建圆角图片
  2. ///
  3. /// – parameter radius: 圆角的半径
  4. /// – parameter size: 图片的尺寸
  5. /// – parameter backColor: 背景颜色 默认 white
  6. /// – parameter lineWith: 圆角线宽 默认 1
  7. /// – parameter lineColor: 线颜色 默认 darkGray
  8. ///
  9. /// – returns: image
  10. func yw_drawRectWithRoundCornor(radius: CGFloat, size: CGSize, backColor: UIColor = UIColor.white, lineWith: CGFloat = 1, lineColor: UIColor = UIColor.darkGray) -> UIImage? {
  11. let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
  12. UIGraphicsBeginImageContextWithOptions(rect.size, true, 0)
  13. let bezier = UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius))
  14. backColor.setFill()
  15. UIRectFill(rect)
  16. bezier.addClip()
  17. draw(in: rect)
  18. bezier.lineWidth = 1
  19. lineColor.setStroke()
  20. bezier.stroke()
  21. let result = UIGraphicsGetImageFromCurrentImageContext()
  22. UIGraphicsEndImageContext()
  23. return result
  24. }
  • Color Compositing Fast-Path Blue

这个选项会对任何直接使用OpenGL绘制的图层进行高亮,如果仅仅使用UIKit或者Core Animation的API,不会有任何效果,恕我才疏学浅,所以,我在测试的时候,确实在屏幕上没有任何反应的,openGL 绘制,我也不会,所以,就不知道到底会有什么效果了,哪位大神会的话,贴段代码,给我试试啊~~~

  • Flash Updated Regions (Core Graphics 绘制的图层)

此选项会对重绘的内容进行高亮成黄色,也就是软件层面使用Core Graphics 绘制的图层。我测试的时候,好像有点问题,这种解释,不知道是不是我写代码的问题,所以,就不多说了

上面说的这些高亮图层,几个常用的选项在模拟器里面可以直接调试,非常方便

%title插图%num
模拟器高亮图层.png

红框中的选项,上面都有解释,这里就不说了,勾选项,打开模拟器,一看就知道了~
麻蛋、Core Animation 部分终于扯完了,扯了好多啊。。。

又一个灰常重要的工具,主要检查内存泄漏,在前面Allcations里面我们提到内存泄漏分两种,现在我们研究Leaked Memory, 从用户使用角度来看,内存泄漏本身不会产生什么危害,作为用户,根本感觉不到内存泄漏的存在,真正的危害在于内存泄漏的堆积,*终会耗尽系统所有的内存。我们直接看图:

%title插图%num

界面的介绍

在 instruments 中,虽然选择了 Leaks 模板,但默认情况下也会添加 Allocations 模板.基本上凡是内存分析都会使用 Allocations 模板, 它可以监控内存分布情况。
选中 Allocations 模板3区域会显示随着时间的变化内存使用的折线图,同时在4区域会显示内存使用的详细信息,以及对象分配情况.
点击 Leaks 模板, 可以查看内存泄露情况。如果在3区域有 红X 出现, 则有内存泄露, 4区域则会显示泄露的对象.
打用leaks进行监测:点击泄露对象可以在(下图)看到它们的内存地址, 占用字节, 所属框架和响应方法等信息.打开扩展视图, 可以看到右边的跟踪堆栈信息,4 黑色代码*有可能出现内存泄漏的方法

%title插图%num

监测结果的分析,

%title插图%num

Time Profiler

在开发的过程中,我们经常能感觉到,点击某一按钮,或者做了某一操作,有卡顿,这就是延迟,那使用此工具,就可以揪出耗时的函数,先看一下,调试界面介绍:

%title插图%num
time Profiler.png

根据查看的相关耗时操作,我们就可以右键定位当耗时的方法:
写一个简单例子看一下:

%title插图%num
屏幕快照 2016-10-30 下午4.23.02.png

看上图,可以很清楚看到此方法耗时比较严重,右键打开定位到此方法的代码:

%title插图%num
屏幕快照 2016-10-30 下午4.24.57.png

代码截图如下:

%title插图%num
屏幕快照 2016-10-30 下午4.25.06.png

这时候,我们把循环放到子线程来做

  1. @IBAction func btnAction(_ sender: AnyObject) {
  2. let svc = SecondViewController()
  3. svc.title = “第二个页面”
  4. //全局队列异步执行
  5. DispatchQueue.global().async {
  6. for i in 0..<8888 {
  7. print(i)
  8. }
  9. }
  10. navigationController?.pushViewController(svc, animated: true)
  11. }

看效果图:

%title插图%num
屏幕快照 2016-10-30 下午4.33.43.png

到这里比较重要Instrument调试工具介绍的差不多了,说一个Xcode8.0新出的功能,很好用也很重要的功能:

%title插图%num
屏幕快照 2016-10-30 下午4.40.08.png

还是以例子说说吧,Viewcontroller里面一个button,点击跳到SecondViewcontroller,SecondViewcontroller里面有个View,view里面有个button,button点击回到ViewController,实现是通过view的属性拿到SecondviewConroller的引用,pop回去
子view的代码如下:

  1. class SubView: UIView {
  2. var delegate: SecondViewController?
  3. @IBAction func brnAction(_ sender: AnyObject) {
  4. delegate?.navigationController!.popViewController(animated: true)
  5. }
  6. }

当我们从第二个控制器,回到*个控制器的时候,我们点一下,刚那个按钮,看图:

%title插图%num
屏幕快照 2016-10-30 下午4.47.53.png

第二个控制器和子View都内存中,我们很容易,就可以发现问题了,这是因为,SecondViewController强引用了SubView,而我们SubView也强引用了SecondViewcontroller,就造成相互强引用,引用计数器不能为0,不能销毁了,我们只要把属性前面加个weak,变成弱引用就可以了

  1. weak var delegate: SecondViewController?
  2. @IBAction func brnAction(_ sender: AnyObject) {
  3. delegate?.navigationController!.popViewController(animated: true)
  4. }

这时候,我们从第二个控制器pop回来的时候,看下内存:

%title插图%num
屏幕快照 2016-10-30 下午4.54.06.png

现在就没问题了,怎样这个工具是不是挺好用啊

还有一些针对TableView,collectionView的优化,有空再聊吧,

  • 声明:
    1.本文有些文字描述,来自于其他博客和文章,因为觉的描述挺好(好吧我承认、自己也写不出来),所以就拿来用了,若有不妥的地方,请告知我,我会立即删除修改
    2.因为本人的水平有限,可能有写的不对的地方,欢迎各位大大多指点、不胜感激

iOS开发之Masonry框架源码解析

Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式,让我们可以以链式的方式为我们的控件指定约束。本篇博客的主题不是教你如何去使用Masonry框架的,而是对Masonry框架的源码进行解析,让你明白Masonry是如何对NSLayoutConstraint进行封装的,以及Masonry框架中的各个部分所扮演的角色是什么样的。在Masonry框架中,仔细的品味干货还是很多的。Masonry框架是Objective-C版本的,如果你的项目是Swift语言的,那么就得使用SnapKit布局框架了。SnapKit其实就是Masonry的Swift版本,两者虽然实现语言不同,但是实现思路大体一致。

今天博客对Masonry框架源码的解析思路是先对比给一个View添加同样的约束时,使用Masonry与系统原生的区别。然后就开门见山之间给出Masonry框架主要部分的类图,从类图中我们来整体的分析Masonry框架的结构。然后再由整体到部分逐渐的细化,窥探其内部的实现细节。通过上述步骤,我们将对Masonry框架的内部实现进行详细的了解。其实Masonry框架是轻量级的,总共的源码也没有多上行,但是仔细的阅读其实现细节,还是可以吸取很多实用的东西的。

首先Masonry在github上的地址是https://github.com/SnapKit/Masonry, 你可以通过上述链接Clone到Masonry框架,其中有Masonry框架介绍以及一些Masonry的使用示例。关于Masonry的使用方式在今天的博客中就不做过多的赘述了,其具体的使用方式请参考上述github上的链接。今天我们就剖析一下Masonry框架的源码。

 

一、Masonry框架与NSLayoutConstraint调用方式的对比

首先我们NSLayoutConstraint为我们的View添加一个约束,然后再给出Masonry的代码。当然在此我们就不说Masonry添加约束的简洁行了,当然好东西是不需要宣传的。进入该部分的主题,我们要对一个View添加一个top约束,这个约束关系我们用表达式来表示就是“subView.top = superView.top + 10”。也就是子视图的top与父视图的top中间隔着10个pt。

 

1. 使用NSLayoutConstraint添加约束

下方这段代码就是给subView添加了一个相对于superView的Top约束。一个View要想确定位置一个约束是不够的,所以可想而知,我们要写多个下方的这样的约束来确定一个View的相对位置。其实下方就是一个表达式,NSLayoutConstraint构造器中每个参数构成这个表达式的一个组成部分。由上到下我们队参数个个参数进行解析,参数constraintWithItem用来指定所约束的对象,在此就是subView。*个attribute参数则指定约束该对象的那个属性,在此就是subView的Top属性。参数relatedBy用来指定约束关系,比如大于等于,小于等于或者等于某个约束值。参数toItem则指定的是约束相对的对象,在此是相对superView的,所以此处的参数是superView。第二个attribute参数就是指定superView的Top属性。multiplier指定相对约束的倍数关系,constant则是约束的偏移量。

由上到下,NSLayoutConstraint的构造器中的参数会构成一个数学表达式,那就是subView.top = superView.top * 1 + 10,该表达式就直观的给出了subView.top与superView.top的关系。经下方的代码我们就为subView添加了一个相对于superView的Top约束,约束的偏移量是10。

%title插图%num

 

 2.使用Masonry添加上述约束

接下来就是Masonry出场的时刻了,我们将使用Masonry添加上述约束,其代码如下。下方给出了三种设置方式,下方三种方式是等价的,当然在Masonry中不知下方三种实现方式。下方Block中的每句话都代表着subView.top = superView.top * 1 + 10的意思,也就是说我们只需要写这三行代码中的其中一种即可。使用Masonry的好处一目了然,让你的代码更为简洁。

Masonry框架中支持约束的添加,约束的更新,约束的重建以及基本动画的实现等等。功能还是蛮强大的。在Masonry框架主要中采用了链式调用和匿名闭包的方式来简化约束的添加。有关Masonry更为详细的使用方式请参见上述Masonry框架的Github链接,具体使用方式在此就不做过多的赘述了。

%title插图%num

 

二、Masonry框架的类结构

通过上述的Masonry的使用方式我们可以看出,UIView的对象可以直接调用mas_makeConstraints方法来为相应的View对象添加约束。因为mas_makeConstraints方法位于UIView的View+MASAdditions类目中,所以UIView的对象可以直接调用。同样在View+MASAdditions类目还有其他方法供UIView的对象使用,稍后会进行详细的介绍。

下方就是Masonry框架核心类以及类目之间的关系,下方的类图是在阅读Masonry源码时画的,仅此一份,如有雷同纯属巧合。如果下图中的文字比较小的话,你可以图片另存到本地,然后放大后进行查看,废话少说,进入我们类图的主题。下方的类图中没有包括Masonry框架中的所有的类,不过所有核心的类都在下方了。我们从左往右依次对下方的类图进行解说。

1.View+MASAdditions类目介绍(左边红框中的部分)

*左边那一坨大类,也就是绿框中的部分,就是Masonry框架对UIView的公有类目,也就是源文件中的View+MASAdditions的部分,在该类目中为添加了类型为MASViewAttribute的成员属性(稍后会介绍MASViewAttribute是个神马东西)。除了添加一系列的成员属性外,还添加了四个公有的方法:mas_closestCommonSuperview方法负责寻找两个视图的*近的公共父视图(类比两个数字的*小公倍数)、mas_makeConstraints方法负责创建安装约束、mas_updateConstraints负责更新已经存在的约束(若约束不存在就Install)、mas_remakeConstraints方法则负责移除原来已经创建的约束并添加上新的约束。上述方式是UIView对象设置约束主要调用的方法,稍后会详细介绍其实现方式。

 

2.MASViewAttribute类的介绍(右边黄框中的部分)

介绍完用户直接使用的UIView的公共类目,接下来我们来看一下用户看不到的部分,那就是下方类图中右边的那一撮类。右边的四个小类的耦合性比较高,我们先看一下MASViewAttribute类。MASViewAttribute类的结构比较简单,主要包括三个属性,三个方法。从MASViewAttribute这个类名中我们就能看出,这个类是对UIView和NSLayoutAttribute的封装。使用等式来表示就是MASViewAttribute = UIView + NSLayoutAttribute + item。在MASViewAttribute类中的view属性表示所约束的对象,而item就是该对象上可以被约束的部分。

此处的item成员属性我们稍后要作为NSLayoutConstriant构造器中的constraintWithItem与toItem的参数。当然对于UIView来说该item就是UIView本身。而对于UIViewController,该出Item就topLayoutGuide,bottomLayoutGuide稍后会给出详细的介绍。该类中除了两个构造器外还有一个isSizeAttribute方法,该方法用来判断MASViewAttribute类中的layoutAttribute属性是否是NSLayoutAttributeWidth或者NSLayoutAttributeHeight,如果是Width或者Height的话,那么约束就添加到当前View上,而不是添加在父视图上。

 

3.MASViewConstraint的介绍(右边黄框中的部分)

接着我们看一下MASViewConstraint类,该类是对NSLayoutConstriant类的进一步封装。MASViewConstraint做的*核心的一件事情就是初始化NSLayoutConstriant对象,并将该对象添加在相应的视图上。因为NSLayoutConstriant在初始化时需要NSLayoutAttribute和所约束的View,而MASViewAttribute正是对View与NSLayoutAttribute进行的封装,所以MASViewConstraint类要依赖于MASViewAttribute类,两者的关系如下所示。

由下方的类图我们可以看出MASConstraint是MASViewConstraint的父类,MASConstraint是一个抽象类,不可被实例化。我们可以将MASConstraint看做是一个接口或者协议。MASConstraint抽象类还有一个子类,也就是MASViewConstraint的兄弟类MASCompositeConstraint,从MASCompositeConstraint的命名中我们就可以看出来MASCompositeConstraint是约束的一个组合,也就是其中存储的是一系列的约束。MASCompositeConstraint类的结构比较简单,其核心就是一个存储MASViewConstraint对象的数组,MASCompositeConstraint就是对该数组的一个封装而已。

 

4.工厂类MASConstraintMaker(中间绿框中的部分)

两边的看完了,接下来我们来看一下中间的部分,也就是MASConstraintMaker类。该类就是一个工厂类,负责创建MASConstraint类型的对象(依赖于MASConstraint接口,而不依赖于具体实现)。在UIView的View+MASAdditions类目中就是调用的MASConstraintMaker类中的一些方法。上述我们在使用Masonry给subView添加约束时,mas_makeConstraints方法中的Block的参数就是MASConstraintMaker的对象。用户可以通过该Block回调过来的MASConstraintMaker对象给View指定要添加的约束以及该约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象。

Masonry框架中的核心类以及类目间的关系就介绍完了,下方就是核心类和类目的类图。下方将会逐步的窥探其代码实现。 

%title插图%num

 

三、View+MASAdditions源码解析

我们先对UIView的公共类目View+MASAdditions中的源码进行解析,也就是对应着上方红框中的部分。用户是通过 View+MASAdditions中的东西来为View添加约束的,View+MASAdditions也就是Masonry框架与外界交互的通道。该部分主要对View+MASAdditions源码进行解析,先介绍其成员属性,然后介绍主要的方法。进入该部分的主题。

1.View+MASAdditions主要成员属性及getter方法

下方截图中是View+MASAdditions类目中的部分成员属性,其他的也与下方类似,这些属性都是MASViewAttribute类型的。以下方的mas_left成员属性为例,因为MASViewAttribute是View与NSLayoutAttribute的合体,所以mas_left就代表着当前View的NSLayoutAttributeLeft属性,也就是mas_left存储的是当前View的NSLayoutAttributeLeft属性。同理,mas_top就代表着当前View的NSLayoutAttributeTop属性,其他成员属性也是一样。

%title插图%num

通过上述成员属性所对应的getter方法,我们可以对其中所存储的内容一目了然。下方是mas_left、mas_top和mas_right成员属性所对应的getter方法,其中所做的事情就是对MASViewAttibute进行实例化,在实例化时指定当前视图所对应的LayoutAttribute。也就是mas_left = self + NSLayoutAttributeLeft, mas_top = self +NSLayoutAttributeTop, 当然此处的self就代表当前视图。

%title插图%num

 

2.mas_makeConstraints方法解析

上面在介绍类图的时候也提到了,用户是通过调用mas_makeConstraints方法来为当前视图添加约束的。下方代码就是mas_makeConstraints函数的代码实现,根据个人理解,对每行代码进行了中文注释,接下来我们来好好的看一下该函数的结构.mas_makeConstraints方法的返回值是一个数组(NSArray),数组中所存放的就是当前视图中所添加的所有约束。因为Masonry框架对NSLayoutConstraint封装成了MASViewConstraint,所有此处数组中存储的是MASViewConstraint对象。

接下来来看mas_makeConstraints的参数,mas_makeConstraints测参数是一个类型为void(^)(MASConstraintMaker *)的匿名Block(也就是匿名闭包),该闭包的返回值为Void, 并且需要一个MASConstraintMaker工厂类的一个对象。该闭包的作用就是可以让mas_makeConstraints方法通过该block给MASConstraintMaker工厂类对象中的MAConstraint属性进行初始化。请参加下方block的使用。

在mas_makeConstraints方法体中,首先将当前View的translatesAutoresizingMaskIntoConstraints属性设置成No, 然后创建了一个MASConstraintMaker工厂类对象constraintMaker,然后通过block将constraintMaker对象回调给用户让用户对constraintMaker中的MAConstraint类型的属性进行初始化。换句话说block中所做的事情就是之前用户设置约束是所添加的代码,比如make.top(@10) == ( constraintMaker.top = 10 )。*后调用constraintMaker的install方法对用户指定的约束进行安装。

%title插图%num

 

 

3.mas_updateConstraints与mas_remakeConstraints函数的解析

这两个函数内部的实现与mas_makeConstraints类似,就是多了一个属性的设置。mas_updateConstraints中将constraintMaker中的updateExisting设置为YES, 也就是说当添加约束时要先检查约束是否已经被安装了,如果被添加了就更新,如果没有被添加就添加。而mas_remakeConstraints中所做的事情是将removeExisting属性设置成YES, 表示将当前视图上的旧约束进行移除,然后添加上新的约束。

%title插图%num

%title插图%num

%title插图%num

 

4、mas_closestCommonSuperview方法解析

mas_closestCommonSuperview方法负责计算出两个视图的公共父视图,这个类似求两个数字的*小公倍数。下方的代码就是寻找两个视图的公共父视图,当然是*近的那个公共父视图。如果找到了就返回,如果找不到就返回nil。寻找两个视图的公共父视图对于约束的添加来说是非常重要的,因为相对的约束是添加到其公共父视图上的。比如举个列子 viewA.left = viewB.right + 10, 因为是viewA与viewB的相对约束,那么约束是添加在viewA与viewB的公共父视图上的,如果viewB是viewA的父视图,那么约束就添加在viewB上从而对viewA起到约束作用。

  %title插图%num

四、顺藤摸瓜,解析约束工厂类MASConstraintMaker

上一个部分我们分析了View+MASAdditions类目,在该类目中主要使用到了约束的工厂类MASConstraintMaker,接下我们就来窥探一下MASConstraintMaker中的内容。MASConstraintMaker之所以成为约束工厂类,因为MASConstraintMaker赋值创建NSLayoutConstraint对象,因为Masonry将NSLayoutConstraint类进一步封装成了MASViewConstraint,所以MASConstraintMaker是负责创建MASViewConstraint的对象,并调用MASViewConstraint对象的Install方法将该约束添加到相应的视图中。

1.MASConstraintMaker中的核心公有属性。

下方截图是MASConstraintMaker中的部分属性,可以看出下方的属性都是MSAConstriant类型,MSAConstriant是抽象类,所以下方成员变量存储的实质上是MSAConstriant子类MASViewConstraint的对象。MASConstraintMaker就负责对MASViewConstraint进行实例化。一句话解释MASViewConstraint,MASViewConstraint = View + NSLayoutConstraint + Install。稍后会给出MASViewConstraint具体技术细节的实现。在MASConstraintMaker还有一个私有数组constraints,该数组就用来记录以及创建的Constraint对象。

  %title插图%num

2.MASConstraintMake中的工厂方法解析

工厂类肯定有工厂方法,接下来我们来介绍MASConstraintMaker中的工厂方法方法,上面每个MASConstraint类型的属性都对应一个getter方法,在getter方法中都会调用addConstraintWithLayoutAttribute方法,而addConstraintWithLayoutAttribute会调用第二个截个图中的方法,而截图中的这个方法就是MASConstraintMaker工厂类的工厂方法,根据提供的参数创建MSAViewConstraint对象,如果该函数的*个参数不为空的话就会将新创建的MSAViewConstraint对象与参数进行合并组合成MASCompositeConstraint类(MASCompositeConstraint本质上是MSAViewConstraint对象的数组)的对象。

  %title插图%num

下方就是MASConstraintMaker工厂类的工厂方法,负责创建MASConstraint类的对象。下方的方法可以创建MASCompositeConstraint和MASViewConstraint对象,上面也说了,MASCompositeConstraint对象就是MASViewConstraint对象的数组。下方创建完MASConstraint类的相应的对象后,会把该创建的对象添加进MASConstraintMaker工厂类的私有constraints数组,来记录该工厂对象创建的所有约束。newConstraint.delegate = self; 这句话是非常重要的,由于为MASConstraint对象设置了代理,所以才支持链式调用(例如:maker.top.left.right.equalTo(@10))。

关于链式调用咱就以maker.top.left.right为例。此处的maker, 就是我们的MASConstraintMaker工厂对象,maker.top会返回带有NSLayoutAttributeTop属性的MASViewConstraint类的对象,我们先做一个转换:newConstraint = maker.top。那么maker.top.left 等价于newConstraint.left,需要注意的是此刻调用的left方法就不在是我们工厂MASConstraintMaker中的left的getter方法了,而是被换到MASViewConstraint类中的left属性的getter方法了。给newConstraint设置代理就是为了可以在MASViewConstraint类中通过代理来调用MASConstraintMaker工厂类的工厂方法来完成创建。下方代码如果没有newConstraint.delegate = self;代理的设置的话,那就不支持链式调用。

说了这么多,总结一下,如果你调用maker.top, maker.left等等这些方法都会调用下方的工厂方法来创建相应的MASViewConstraint对象,并记录在工厂对象的约束数组中。之所以能链式调用,就是讲当前的工厂对象指定为MASViewConstraint对象的代理,所以一个MASViewConstraint对象就可以通过代理来调用工厂方法来创建另一个新的MASViewConstraint对象了,此处用到了代理模式。

    %title插图%num

3. 工厂类中的install方法

虽然我们将MASConstraintMake视为工厂类,不过该工厂类的功能不仅仅创建MASConstraint的对象,还负责调用MASConstraint对象的install方法来将相应的约束安装到想要的视图上。在MASConstraintMake类中的install方法就是遍历工厂对象所创建所有约束对象并调用每个约束对象的install方法来进行约束的安装。下方就是该工厂类中的install方法。

在安装约束时,如果self.removeExisting == Yes, 那么用户就通过mas_remakeConstraints方法调用的install方法,就先将原来的约束进行移除掉,然后添加上新的约束。在安装约束时,将updateExisting赋值给每个约束,每个约束在调用本身的install方法时会判断是否更新。下方就是MASConstraintMake的install方法的实现和注释。

  %title插图%num

五、继续顺藤摸瓜,解析MASViewConstraint

MASConstraintMaker工厂类所创建的对象实质上是MASViewConstraint类的对象。而MASViewConstraint类实质上是对MASLayoutConstraint的封装,进一步说MASViewConstraint负责为MASLayoutConstraint构造器组织参数并创建MASLayoutConstraint的对象,并将该对象添加到相应的视图中。接下来我们将对MASViewConstraint类中的内容进行解析。

1.MASViewConstraint的对象链式调用探索

MASViewConstraint的对象是支持链式调用的,比如constraint.top.left.equalTo(superView).offset(10); 上面的这种方式就是链式调用,而且像equalTo(superView)这种形式也不是Objective-C中函数调用的方式,在Objective-C中是通过[]来调用函数的,而此处使用了()。接下来讲分析这种链式的调用是如何实现的。

在MASViewConstraint类中的left, top等约束的getter方法都会调用下方的这个方法,而这个方法中所做的事情就是通过代理来调用工厂中的工厂方法来根据LayoutAttribute创建相应的MASConstraint对象。

    %title插图%num

而像offset(10)这种调用方式是如何实现的呢?我们知道在OC中是不能通过小括号来调用方法的,那边闭包是可以的,不过offset()不是一个简单的闭包。在offset()的代码分析后我们不难发现offset() = offset + (); offset的代码实现方式如下。offset是一个getter方法的名,offset函数的返回值是一个匿名Block, 也就是offset后边的()。这个匿名闭包有一个CGFloat的参数,为了支持链式调用该匿名闭包返回一个MASConstraint的对象。

  %title插图%num%title插图%num

2.install方法解析

MASViewConstraint中install方法负责创建MASLayoutConstraint对象,并且将该对象添加到相应的View上。下方代码就是install中根据MASViewConstraint所收集的参数来创建NSLayoutConstraint对象,下方的MASLayoutConstraint其实就是NSLayoutConstraint的别名。下方就是调用系统的NSLayoutConstraint为创建相应的约束对象,下方的构造器与*部分中的NSLayoutConstraint一致。

  %title插图%num

创建完约束对象后,我们要寻找该约束添加到那个View上。下方的代码段就是获取接收该约束对象的视图。如果是两个视图相对约束,就获取两种的公共父视图。如果添加的是Width或者Height,那么久添加到当前视图上。如果既没有指定相对视图,也不是Size类型的约束,那么就将该约束对象添加到当前视图的父视图上。代码实现如下:

  %title插图%num

创建完约束对象,并且找到承载约束的视图后,接下来就是将该约束添加到该视图上。子啊添加约束是我们要判断是不是对约束的更新,如果是对约束的更新的话就先获取已经存在的约束并对该约束进行更新,如果被更新的约束不存在就进行添加。添加成功后我们将通过mas_installedConstraints属性记录一下本安装的约束。mas_installedConstraints是通过运行时为UIView关联的一个NSMutable类型的属性,用来记录约束该视图的所有约束。

  %title插图%num

3.UIView的私有类目UIView+MASConstraints

在MASViewConstraint中定义了一个UIView的私有类目UIView+MASConstraints,该类目的功能为UIView通过运行时来关联一个NSMutableSet类型的mas_installedConstraints属性。该属性中记录了约束该View的所有约束。代码实现如下。

%title插图%num

因为篇幅有限,今天的博客就先到这儿

iOS开发针对对Masonry下的FPS优化讨论

今天博客的内容就系统的讨论一下Masonry对FSP的影响,以及如何更好的使用Masonry。如果你对iOS开发足够熟悉的话,那么对Masonry框架应该不陌生。简单的说,Masonry的诞生让AutoLayout的使用更为优雅,让控件的布局更为方便。使用辩证的观点来看一个事物的话,凡事都有两面性,Masonry的使用也不例外。Masonry框架的使用不当会直接影响当UI的FPS。今天我们就来讨论一下在使用Masonry时的一些误区,看一下那些影响性能的使用方式。本篇博客我们依然会依托于Demo来叙述的一些东西。

之前写过一篇文章是专门来介绍Masonry框架的,并且对该框架的源码进行了相关解析,详细内容请移步于《iOS开发之Masonry框架源码解析》。

 

一、Demo综述

1.运行效果

先入为主,本篇博客的内容依然是依托于我们特意为本篇博客所打造的Demo的,首先我们先来看一下Demo运行起来是怎样的效果,通过Demo我们可以看到那些问题,以及这些问题是如何被解决的。下方就是我们本篇博客所涉及Demo的运行效果。

从下方的运行效果不难看出,我们是分了6种情况来观察和判断Masonry的各种使用方式对FPS的影响如何。上方通过六个SegmentControl可以去切换Cell的布局方式。当然每种布局方式所呈现出来的Cell是相同的。这样也是做实验时保持实验项改变其他项保持一致的原则。我们可以通过右下方FPS指示器来直观的感受一下FPS的变化趋势。下方这个FPS显示控件是从我们之前的Demo中拿过来的。之前的Demo也是关于FPS优化的,只不过是关于Cell高度动态计算的FPS优化,详情请移步于《iOS开发之多种Cell高度自适应实现方案的UI流畅度分析》。

下方Cell中所显示的数据时随机生成的,左边的Image也是随机取的。右边的Title和Detail都是NSAttributedString并且下方的有些Detail有可能为空。如果某一条的Detail为空,那么该条Detail下方的所有内容的布局上移。稍后会详细的介绍该Demo以及其中的技术点。

%title插图%num  %title插图%num

2、模拟网络请求

上面Cell中显示的数据是通过模拟网络数据来获取的,下方就是我们的模拟网络层的相关代码。毕竟是Demo,并且Demo的重点不在网络层上,下方就简单的写了一下,代码比较简单。就是一个单例+一个模拟数据随机生成的方法,然后把这个随机生成的数据通过Block回调到网络层的使用者上。具体代码如下所示:

%title插图%num

 

3、上述Cell的基类XBaseTableViewCell

上面每种Segment中的Cell都是一种独立的Cell类型,不过这些Cell有一个共同的父类。而这个父类就负责来处理这些Cell所共用的逻辑。下方的这个XBaseTableViewCell就是上述显示的所有Cell的基类。其中声明并初始化了Cell上的所有控件。并且提供了相关的设置值的方法。

从下方的代码中不难看出,有两个方法是需要子类进行重写的,一个是给控件添加布局的方法addLayoutSubviews, 另一个就是更新布局的方法updateLayoutSubviews方法。每个子类中都会对这两个方法进行重写并给出不同的布局方式。

%title插图%num

 

4、Segment中切换Cell的代码

下方是在相应的VC中的点击SegmentControl的逻辑代码, 点击不同的Segment会选择不同的Cell然后刷新TableView,代码比较简单就不做过多的赘述了。

%title插图%num

 

 

二、对上述各种的布局方式进行分析

接下来要做的事情就是分析一下每种布局方式对FSP的影响,下方会对不同的布局情况使用Instrument进行分析并看一下具体的数据。下方分别讨论了只使用Masonry的Update操作、Remake操作、先Make后Update、Frame操作以及先Make后Frame操作。

1、update

首先我们来看一下update操作。也就是使用update直接给控件赋值,这是比较偷懒的一种操作。因为在我们的Demo中在设置cell的值时会更新一些控件的UI布局,所有我们索性就直接使用Masonry的update,直接给控件添加约束。在Masonry中的update操作有个特点,就是update一个约束会先在已添加的约束数组中找到该约束,然后更新该约束,如果找不到就install添加相应的约束。从这个update的功能来看其效率是比较低的。

我可以先看一下代码实现,在子类XUpdateLayoutTableViewCell中,重写了addLayoutSubviewsupdateLayoutSubviews两个方法。在updateLayoutSubviews方法中,为所有的控件使用update的方式添加约束。下方这样写会在每次设置值的时候都会调用下方的updateLayoutSubviews方法,这样就会更新cell上的控件的所有布局,当然,不建议这样去做,因为这样会更新那些不需要更新的约束。之所以今天罗列出来,是因为在开发中下方的问题确实存在,也许是因为时间紧张,也许是因为其他原因导致的下方这种代码实现。

%title插图%num

 

我们先来使用Instruments跑一下上述的Demo,然后直观的感受一下该Demo的Core Animation的直观表现。下方就是我们将SegmentControl切换到Update时所对应的FPS数据。从下方的数据我们不难看出,直接用Update添加更新约束这种做法是比较影响FPS的。当然,Cell中还会使用到属性字符串,这个我们稍后会讨论一下的。

%title插图%num

 

我们可以来跑一下Update状态下的Time Profile。如下所示,从下方的结果中不难看出,在Cell更新数据时,有两块的操作比较耗时。一个是Masonry的update操作,另一个则是Label设置NSAttributedString的操作。因为我们使用的每个Label都会赋值一个属性字符串,这个是比较耗时的操作。还有一个要明确一点的是,属性字符串的创建和生成并不会占用多少时间,而属性字符串的赋值和渲染所占用的时间是比较多的,这一点从下方的Time Profile中也是不难看出的。

%title插图%num

 

2、remake

接下来我们在来看一下Remake操作,从下方的Core Animation的结果中不难看出,其所表现出来的效果还不如使用Update操作呢。下方的FPS比Update要低一些,这也与remake自身的操作有关系,remake从字面意思来看就是重新制作,如果之前已经添加过约束的话就先移除掉,然后再添加新的约束。

%title插图%num

 

下方是Remake所对应的Time Profile,从结果中我们可以看出布局更新占用了66.6%的耗时,而且33%的install耗时中uninstall占用了10%左右的开销。在Masonry中remake效率是*低的。稍后我们会继续进行讨论。

%title插图%num

 

3、make + update

讨论完update和remake, 我们来讨论一下使用Masonry的常规做法。就是使用make来初始化控件的布局,使用update来更新所需要更新的约束。因为代码比较简单,就不一一往上贴了,但是跑一下使用Instrument跑一下还是很有必要的。下方是make + update 的方式的Core Animation所跑出来的结果。但从下方的FSP结果来看,还是要比之前只使用update或者remake的效果要好一些的,不过下方的FPS还是不高,稍后我们会将下方的数据进行细化。

该部分的Time Profile就不跑了,因为设置值的时候我们依然采用的Update来更新的约束,只不过不是更新所有的约束,而是更新那些只需要更新的约束。因为更新的约束的量会少一些,所有FPS的表现效果会比之前更新所有的约束会更好一些。make + update的方式会是FPS稍微改善一些,但是从下方的图中我们可以看出,改善的并不是特别好。

%title插图%num

 

4、frame + frame

接下来,我们就不用Masonry来布局了,我们直接使用Frame布局。因为Autolayout*终仍然会转换为Frame布局的,很显然Frame布局在性能方面是优于Autolayout布局的。接下来我们就来使用Frame布局然后使用Frame更新。下方的FPS还算说得过去,不过还不是满格,其大部分原因就是因为NSAttrubitedString的原因了。

%title插图%num

 

我们可以看一下更新Frame的Time Profile,如下所示。从下方的截图中,我们不难看出update frame的时间占比只占到了2.5%,之前更新约束能占到60%左右,可以看到使用Frame布局的好处。从下方的分析结果中不难看出,现在影响FPS主要因素已经从更新布局转化到了AttributeString的设置。这也是上述FPS没有满格的原因。

%title插图%num

 

5、make + frame

Masonry的诞生就是为了方便控件布局的,Frame布局不够灵活,适配起来比较繁琐,所以才有了AutoLayout。不过虽然AutoLayout可以很方便的适配屏幕,可是其性能方面表现的不是特别好。那么我们可不可以将两者进行结合呢。也就是说使用make来初始化控件的布局,使用Frame来更新布局。当然这一过程不是简单的在设置值的时候更新一下Frame就可以的,因为在Cell设置值的时候去更新Frame是没用的,因为更新完Frame后,在渲染显示的时候,还是会按照AutoLayout的布局来显示的。我们需要做的是将Frame布局放到Autolayout布局之后,此处我们要做的就是将更新Frame的相关代码放到下一个Runloop中来执行。更新Frame代码如下:

%title插图%num

 

在cell中是make初始化控件布局,使用Frame更新布局,和Frame+Frame的方式差不多,只不过是使用Masonry布局时,在首屏加载的时候不如Frame布局,以后更新是一样的。下方是使用Masonry+Frame的形式的Core Animation的结果。效果虽然比上一部分会稍微差一些,但是*终的效果还是满Ok的。

%title插图%num

 

 

 三、总结

本篇博客只讨论Masonry的布局方式对FPS的影响,至于上述的NSAttributeString的问题不做过多赘述了。如果根据业务需要,有许多富文本的展示影响了FPS的话,那么可以采用其他的方式来进行优化,比如使用AsynDisplayKit所提供的相关Node进行显示等等。在博客的结尾,还是有必要做一个总结的。

下方是我们在代码中更为细化的数据,从数据中不难看出Remake的性能是*差的,所以我们在使用Masonry时尽量要少使用Remake。对控件的更新只一味的选择使用Update也不是一个好的选择,如果要使用Masonry框架还要对控件进行布局更新的话,*好是把那些不变的约束和需要更新的约束分开。使用make来添加相关约束,使用update来修改需要更新的约束。当然使用Frame布局的性能会好一些,不过布局过程过于繁琐不便于进行屏幕的适配。当然也可以使用Masonry进行布局使用Frame进行布局的更新,当然需要注意的是Frame布局更新的时机,需在Autolayout加载的时机后进行。

下方是进行了统一的数据统计,当然是针对本篇博客所对应的Demo的。下方表格中统计了一次更新cell布局所采用的不同方式的平均时间,从下方的数据中我们不难看粗Remake的更新布局用时*多,消耗了12+ms, 而Update所有的约束用时也是不少,一次更新布局使用了9+ms。而只更新需要更新的布局用时7+ms, 稍微要比更新所有的布局要好一些。当然直接修改Frame的用时*少,只用了0.06+ms的时间,从该数据可以直观的感受到Frame布局的效率性。

而右边还给出了一个属性字符串的创建和赋值的用时,其中我们可以看到,属性字符串的创建耗时并不是太多,而比较耗时的是属性字符串的赋值,每次赋值占用了0.7ms, 如果是10个的话,那么赋值时间就是7ms, 如果属性字符串的内容再复杂一些,那么用时肯定会比这个高。当然我们可以使用第三方提供的一些控件和方法将这部分时间给优化掉,这个可以放到以后再讨论。

今天的博客就到这儿吧,目的是在使用Masonry时要合理的进行使用,有必要时,可以使用Frame进行布局。

%title插图%num

 

Ubuntu下修改DNS服务器地址

今天刚装了Ubuntu系统,想修改dns地址,结果网上找了很多方法都解决不了。后来通过直接修改系统文件解决。步骤如下:
1、打开终端,输入以下命令行后打开文件并进行编辑

sudo gedit /etc/resolv.conf

2、将文件中原dns地址注释掉或删掉,改为自己想要设置的dns地址。
即修改nameserver后的地址。
如:

nameserver 10.8.8.8
nameserver 10.8.4.4

点击保存,完成

3、检查是否更改成功
通过输入以下命令行可查看dns地址

nslookup
server

ubuntu16.04 搭建 dns 服务器

具体需求:部门管理中,有git服务器,部门管理系统的服务器,* 服务器等等,每一个访问都是用ip,这样很烦,不高大上,我想整成这样:git服务器:git.com 管理系统的服务器:mag.com… 的形式。

解决方法:新建一个虚拟机(这里是ubuntu 16.04 64位),搭建成一个dns服务器,然后让部门的每一台电脑的dns指向dns服务器,就可以了。

环境(都是ubuntu16.04 64位):我的git服务器: 10.10.3.155
管理系统所在的服务器:10.10.3.154
新建的dns服务器:10.10.3.153

现在开始搭建dns服务器(10.10.3.153)

1.更新环境,切换成root用户

su root
apt-get update

2.安装bind9

apt-get install bind9

3.配置/etc/bind/named.conf.local 文件(服务器域解析文件)

zone “mag.com”{
type master;
file “db.mag.com”;
};
zone “git.com”{
type master;
file “db.git.com”;
};

4.步骤3中的db.mag.com和db.git.com是域解析文件,默认在/etc/var/cache/bind,它们的格式与bind9自带的模板db.local格式一致,我们把/etc/bind/db.local文件copy到/etc/var/cache/bind下,并命名为db.mag.com 和 db.git.com

cp /etc/bind/db.local /var/cache/bind/db.mag.com
cp /etc/bind/db.local /var/cache/bind/db.git.com

5.编辑域解析文件

vim /var/cache/bind/db.mag.com

在末尾加:@ IN A 10.10.3.154

即:

;
; BIND data file for local loopback interface
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS localhost.
@ IN A 127.0.0.1
@ IN AAAA ::1
@ IN A 10.10.3.154

db.git.com 的配置同理,在/var/cache/bind/db.git.com *后面加一句@ IN A 10.10.3.155 即可。

6.配置/etc/bind/named.conf.options 文件

在中间加入下面的代码,dns服务器就可以访问外网了

forwarders {
8.8.8.8;
};

完整的文件如下:

options {
directory “/var/cache/bind”;

// If there is a firewall between you and nameservers you want
// to talk to, you may need to fix the firewall to allow multiple
// ports to talk. See http://www.kb.cert.org/vuls/id/800113

// If your ISP provided one or more IP addresses for stable
// nameservers, you probably want to use them as forwarders.
// Uncomment the following block, and insert the addresses replacing
// the all-0’s placeholder.

// forwarders {
// 0.0.0.0;
// };

forwarders {
8.8.8.8;
};

//========================================================================
// If BIND logs error messages about the root key being expired,
// you will need to update your keys. See https://www.isc.org/bind-keys
//========================================================================
dnssec-validation auto;

auth-nxdomain no; # conform to RFC1035
listen-on-v6 { any; };
};

7.重启bind9服务

/etc/init.d/bind9 restart

8.测试
%title插图%num

在浏览器输入:git.com 回车

%title插图%num

9.就这样成功了。

iOS开发 – 在状态栏显示FPS,CPU和内存信息

前言
今天在用Instruments分析App的时候,总感觉看起来不太直观。到Github上找了找,发现几乎都是只显示FPS的,而且效果也不是我想要的。于是就自己写了个

源码地址
LHPerformanceStatusBar
效果

%title插图%num

字体颜色会根据阈值进行颜色变化,性能差的时候字体会变成红色,性能一般的时候会变成橘黄色,阈值可配。

集成
CocoaPod集成

pod LHPerformanceStatusBar

使用
– (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[LHPerformanceMonitorService run];
}

原理
FPS的计算
CoreAnimation有一个很好用的类CADisplayLink,这个类会在每一帧绘制之前调用,并且可以获取时间戳。于是,我们只要统计出,在1s内的帧数即可。

 

– (void)envokeDisplayLink:(CADisplayLink *)displayLink{
if (_lastTimestamp == -1) {
_lastTimestamp = displayLink.timestamp;
return;
}
_countPerFrame ++;
NSTimeInterval interval = displayLink.timestamp – _lastTimestamp;
if (interval < 1) {
return;
}
_lastTimestamp = displayLink.timestamp;
CGFloat fps = _countPerFrame / interval;
//…
}

内存和CPU信息的获取
CPU和内存的获取采用了mach头文件中的方法,调用了底层API,采用C方式来获取。

选择Ubuntu服务器版操作系统的六大理由

今天分别从成本、系统集成、虚拟化、云计算、安全性、系统管理上来阐述,为什么要选择Ubuntu服务器版操作系统。

减少成本

(1) 减少数据中心成本

Ubuntu服务器版是真正能为企业减少IT基础设施成本的机会。Ubuntu服务器提供了企业功能定制化服务。精简的结构让*少的能耗和*省资源提供更多的服务。这种为特定功能定制的缩减版Ubuntu服务器也意味着更小的出错率。

(2)服务器维护简单

Ubuntu服务器只有部分组件需要维护,对于技术娴熟的系统管理员来说,维护Ubuntu服务器是一项清闲的工作。一般的服务只需要15-30分钟就可以配置完成。

(3)自动更新

经过一些初始配置工作后,剩下的系统可以自动进行安全配置。这样就不需要管理员再进行配置,服务器就可以提供一些重要服务。Ubuntu有两个版本更新周期,长短周期的无缝配合,让系统在5年内实现新技术的更新换代,版本更新过程中用户不需要担心系统安全和稳定问题。

(4)应用包

应用程序在Ubuntu中通常被称为包,因为在Ubuntu系统中,应用程序和其所依赖的库都必须打包在一起。这点与其他Linux系统不太一样。这就意味着系统管理员可以使用启动、停止、关机等简单的命令来控制Ubuntu系统中应用程序。这样简单的操作方式更加容易扩展服务器的功能,使用包方式不仅可以节省系统管理员的时间,还可以*大限度的提高数据中心的正常运行时间。

(5)减少能量消耗

通过Ubuntu企业云、Power Capping技术和PowerNap技术可以减少Ubuntu服务器系统的能耗。当数据中心的能耗减少了,系统可提供的服务也更好。Ubuntu具有*佳的服务环境,其低耗稳定的特性,可以*大限度的提高上网本和笔记本电池的寿命,同时让Ubuntu内核发挥*高效率。

(6)免费许可证

Ubuntu服务器提供免费的许可证和订阅。Ubuntu技术团队免费提供重要的维护和安全升级。所有订阅和许可证费用是通过提供有重要价值的服务获得,比如,给企业搭建环境、商业咨询和技术支持等。

系统集成

(1)集成现有的系统

Ubuntu服务器版本用常用的身份认证方式和服务入口工具简单地集成企业现有的客户/服务器结构。我们都知道系统集成技术的重要性,这也是Ubuntu团队花费大量时间研究如何实现服务器与基础设施简单融合的原因。

(2)简单的验证方式

验证功能对于网络信息识别与分享是非常重要的。所有Ubuntu服务器版都用Open LDAP来确保在必要时建立一个共享服务目录。通过简单的配置后,新版Ubuntu服务器就可以成为LDAP上网本中集成的一部分

(3)结合微软活跃目录

所有融合微软活动目录(ActiveDirectory)的Ubuntu服务器版本都有一个Likewise-Open工具。Likewise-Open可以帮助Ubuntu机器在不同机器中通过活跃目录实现资源的辨别、分享认证和访问。所以Ubuntu服务器可以通过简单的指令在无安全风险下为客户机提供资源服务。

(4)共享打印服务

共享打印服务是通过SAMBA协议(一种开源的SMB/CIFS的实现)或者CUPS协议(苹果常用的Unix打印系统,也用于苹果Mac OSX系统中)实现的。所有基于CUPS协议下的大部分平台都支持自动发现打印资源功能,在苹果电脑上可瞬间配置成功。在Windows机上安装打印机需要增加一些额外配置工作,但是在Ubuntu服务器版本上就可以提供石头般稳定的服务。

(5)使用SAMBA协议共享文件

文件共享和打印共享一样使用SANBA协议,可以合并微软的活动目录(Active Directory)。兼容Ubuntu客户端复杂的运行环境。通过NFS、Kerberos、SHH等协议实现UNIX和Linux系统的集成。

 三虚拟技术

(1)更容易实现虚拟化

Ubuntu服务器版是非常流行的虚拟化数据中心平台。Ubuntu服务器为主机和客户机提供KVM虚拟化技术。同时Ubuntu服务器还结合了大量的开源和专有技术。

(2)开源虚拟化

每一款发行版Ubuntu服务器都提供了很多方式来创建和管理虚拟化环境。开源技术是虚拟化环境搭建技术的前端,而且Ubuntu免费许可证的运行模式,非常适合动态的扩大和减少虚拟化环境中实际和虚拟的机器。

(3)低空间占用的操作系统

Ubuntu服务器可以通过虚拟机配置出空间占用低的理想环境。Ubuntu有一个虚拟机生成器,允许多个欲安装的机器通过简单的程序复制实现立刻安装。通过常用的环境配置工具,用户可以在简单的环境中管理虚拟机。而且虚拟机和物理机的管理方式没有不同,这两种机器用相同的界面和方式进行管理。

(4)Ubuntu服务器:已经准备好虚拟化

用Ubuntu系统内置的KVM,libvirt,和虚拟主机简表可以在X86中建立虚拟环境。为了简化硬件维修和维持效率平衡,在用户和服务器之间的迁移时要求它们共用一个存储系统。当相同服务器上的所有主机都使用相同的操作系统和应用程序时,内存集成可以*大程度的增加虚拟机的数量。

(5)通过VirtlO设备增加性能

VirtlO设备提供虚拟机访问硬件设备的直接通道,加快了运行速度和简化维护。你可以给虚拟机扩展特定硬件实现更高的吞吐量。Libvirt接口将要成为一项开源标准,通过第三方通用接口成为Linux内核的一部分。

(6)*好的客户端操作系统

通过现在主流的虚拟技术,比如,亚马逊EC2,VMware,Xen,Parallels,LXC,VirtualBox,以及KVM ,Ubuntu服务器可当做客户端来用。你可以基于虚拟机上在Ubuntu服务器上勾选你需要的功能,配置一个空间占用率*低的精小系统。我们还为你提供一个安装工具,只要几分钟就可以在你的系统上安装、卸载虚拟机。

云计算

Ubuntu服务器版可以为你提供一切资源,将你的基础设施建立在公共云前端(亚马逊 CE2)或者是你私有云的建设。你可以用相同的镜像和工具来控制这两种云。Ubuntu企业云可以通过防火墙的安全检查提供实时灵活的云计算,并且实现私有云与共有云之间简单迁移。

(1)私有云:Ubuntu企业云

如果你想在你的IT基础设施上创建私有云,Ubuntu企业云(UEC)可以为你提供所需要的工具。这样你就可以在安全环境下享受云计算带来的好处。

部署工作负载随时运行。提高或者降低应用程序的计算能力。作为Ubuntu服务器的重要组成部分,Ubuntu企业云很容易安装。UEC整合了一系列的开源项目,包括KVM、Libvirt和Eucalyptus。

(2)公共云:基于亚马逊 EC2

亚马逊灵活的EC2(ElasticCompute Cloud)允许你在*少的硬件条件下创建所需的虚拟系统。亚马逊EC2与Ubuntu服务器版本中的模块性、虚拟技术、一系列的应用软件和高效的执行度完美的配合。两者结合起来可以让企业在几分钟内建立灵活、符合企业需求的虚拟系统。

安全性

(1)建立安全性

Ubuntu服务器版本内核很安全,因为它是基于安全性很好的Debian操作系统。Ubuntu安全设计团队、Debian和一些Linux同行一起合作,来确保设计的系统能够及时发现并修复漏洞。Ubuntu免费公平的使用方式也意味着补丁包对于用户都是公开的,而不仅仅只是企业客户和订阅者。

(2)防火墙不复杂

Ubuntu服务器也引入简单易用的安全功能——这是一项*有用的安全技术,因为它可以减少安全管理中的“用户错误”因素。比如,防火墙会提示你为网络的数据通道指定你想要的(SMTP,HTTP,etc)协议。Ubuntu服务器没有默认的网络协议,所以即使首次安装管理员不熟悉的服务,也不会有安全隐患。

(3)通过AppArmor实现访问控制

AppArmor迅速的成为开源服务默认的强制访问控制工具。AppArmor允许系统管理员为每一个程序加入一个安全描述,限制非“安全”程序的访问和控制权力。AppArmor在传统的UNIX的任意访问控制的基础上额外增加了一些规则来控制程序的访问权限。这完全是 “学习”传统的设立规则方式,使其成为一种强制的标准而被广泛应用。

另一项功能是帮助你在服务器主目录下建立一个加密的私有目录,存储那些重要的秘密数据,用户名和登录信息。这是系统管理员为系统管理员设计的,那些有数据访问控制需求的管理员可以考虑花点时间来创建它。这种方式使用起来很方便。

管理员

(1)方便的管理方式

Ubuntu服务器让系统管理员工作起来更简单高效。Ubuntu的核心是Debian,而Debian是一款由系统管理员专门为系统管理员设计的Linux发行版,以高的安全性和易管理性闻名。所以有很多耗时的管理任务都被设计成简单、自动的。

(2)自动化部署更省时间

自动化部署是Ubuntu结构中的一项关键技术。原来为服务器增加一些相同或者简单的任务时,共同的一点就是配置过程需要消耗好几个小时。但是现在通过Ubuntu服务器,你可以建立可复制且独立于硬件的部署方案,加入你需要的应用程序中。只需几分钟就可以部署完成。Ubuntu服务器支持为主机提供部署方案。

(3)轻松获取应用包

Ubuntu用户通过Debian的包体系可以节省时间。每次的版本更新,Ubuntu服务器都会自动加入更多的服务部署标准,从原来的LAMP(Linux,Apache,MySQL,PHP/Python)栈到后来的java到现在的云计算。增加应用包可以从开源“体系”仓库中获得,随着不断扩展的Ubuntu体系,可以从Launchpad(PPA)中获得个人增加的应用包,或者一个公司也可以用自己打包应用程序来部署。

(4)通过启动板轻松管理

管理、监管、维护你的IT环境,启动板简洁的管理让用户管理多台机器就像管理一台般轻松。用户可以通过一个简单的Web终端来管理网络上的虚拟机和物理机,比如订阅服务或者是防火墙部署。

Centos与Ubuntu中安装docker

ubuntu系统
1.更新包索引

sudo apt-get update

2.安装以下包使apt可以通过HTTPS使用存储库(repository)

sudo apt-get install apt-transport-https ca-certificates curl software-properties-common

3.添加Docker的官方GPG密钥

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –

4.安装stable存储库

sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable”

5.更新apt包索引

sudo apt-get update

6.安装Docker CE

sudo apt-get install docker-ce

7.测试hello world

sudo docker run hello-world

docker服务一般安装完成后自启,不放心的话
查看docker服务是否启动

systemctl status docker

若未启动,则启动docker服务

sudo systemctl start docker

如果想要安装特定版本 可以查看系统支持版本

apt-cache madison docker-ce

选择要安装的特定版本,第二列是版本字符串,第三列是存储库名称,它指示包来自哪个存储库,以及扩展它的稳定性级别。
要安装一个特定的版本,将版本字符串附加到包名中,并通过等号(=)分隔它们

sudo apt-get install docker-ce=<VERSION>

卸载之前版本(之前如果安装过)

sudo apt-get remove docker docker-engine docker-ce docker.io

Centos系统
1.*新yum 包

sudo yum update

2.安装需要的软件包

sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2

3.设置yum源

sudo yum-config-manager \
–add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

4.安装docker

sudo yum install docker-ce docker-ce-cli containerd.io

5.启动并测试hello world

sudo systemctl start docker

sudo docker run hello-world

6.设置docker自启

sudo systemctl enable docker

sudo chkconfig docker on

如果要安装一个特定的版本
查看仓库中所有docker版本

yum list docker-ce –showduplicates | sort -r

安装docker,其中<VERSION_STRING>替换成相应的版本号,版本号为第二列,替换成(docker-ce-18.09.1)

sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io

卸载之前版本(之前如果安装过)

sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

如果上述方法有错误请指正谢谢
如果使用上述方法安装不成功请检查 系统的内核是否支持docker 及时更换Linux系统版本

判断linux是ubuntu还是centos

方式一:
radhat或centos存在: /etc/redhat-release 这个文件【 命令 cat /etc/redhat-release 】
ubuntu存在 : /etc/lsb-release 这个文件 【命令 cat etc/lsb-release 】
方式二:
看看安装指令,
有yum的就是Centos【yum -help】,
有apt-get的就是Ubuntu 【apt-get -help】。