作者: xiao, yanzi

iPhoneX && iOS11 适配

*近实在是蛮闲的,这都得益于苹果爸爸给力的审核,已经半个月了(委屈)

这个问题已经很久了,但是还是希望分享给各位,当然网上的教程的确很多;


 

1、automaticallyAdjustsScrollViewInsets

automaticallyAdjustsScrollViewInsets是在iOS11之前为ScrollView自动处理内边距的,讲实话这个属性我从来都是禁止的,不喜欢被别人自动控制,主要原因还是因为控制不住别人的自动控制。

当设置为YES时(默认YES),如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距,这样可以让scroll占据整个视图,又不会让导航栏遮盖。

当controller上的*个子视图不是scrollview以及其子类的时候,就会取消内边距。此时原本全屏的scrollview设置的frame(0,0,self.view.frame.size.width,xx)就会从状态栏开始算起,如果应用有导航栏的话,那么就会遮盖住视图的64个高度

当然这很绕,简单的方法就是不要去管他,当视图出现故障的时候一定要*时间想起它!

在iOS11,你会发现这个属性失效了,查询可以看到下面的说明:在iOS11使用UIScrollView’s contentInsetAdjustmentBehavior instead

@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED(“Use UIScrollView’s contentInsetAdjustmentBehavior instead”, ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES

你可以这样做:

if (@available(iOS 11.0, *)) {
    self.scrollview.contentInsetAdjustmentBehavior = UIApplicationBackgroundFetchIntervalNever;
} else {
    self.automaticallyAdjustsScrollViewInsets = false;
}

swift:

  if #available(iOS 11.0, *) {
            tableView.contentInsetAdjustmentBehavior = .never
        } else {
            self.automaticallyAdjustsScrollViewInsets = false
        }

有位大神写了个宏供各位参考:

#define  adjustsScrollViewInsets_NO(scrollView,vc)\
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
if ([UIScrollView instancesRespondToSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:")]) {\
[scrollView   performSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:") withObject:@(2)];\
} else {\
vc.automaticallyAdjustsScrollViewInsets = NO;\
}\
_Pragma("clang diagnostic pop") \
} while (0)

或者下面屌丝一点的宏定义:

#define AdjustsScrollViewInsetNever(controller,view) if(@available(iOS 11.0, *)) {view.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;} else if([controller isKindOfClass:[UIViewController class]]) {controller.automaticallyAdjustsScrollViewInsets = false;}

 

2 、齐刘海 

众所周知,导航的高度为64,在哪个手机上面都是不变的,iPhone X没出来之前是对的,随便写64,iPhone X出来后,大家都傻眼了,导航栏高度变了有可能为88,傻眼了吧,如果你纯码写的都是64,这时候你写出了下面的代码:

-(UITableView *)goodsTypeTableView{
    if (!_goodsTypeTableView) {
        _goodsTypeTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height - 64 - 49) style:UITableViewStylePlain];
        _goodsTypeTableView.delegate = self;
        _goodsTypeTableView.dataSource = self;
        [_goodsTypeTableView registerClass:[XJMarketGoodsTableViewCell class] forCellReuseIdentifier:NSStringFromClass([XJMarketGoodsTableViewCell class])];

    }
    return _goodsTypeTableView;
}

哇赛!很炸天嘛,你发现要么是上面的内容被遮挡了,要么是下面的内容被遮挡了,这都是因为写死了64和49造成的。所以你应该这样做:

    #define SafeAreaTopHeight (kScreen_height == 812.0 ? 88 : 64)

&&

  #define SafeAreaBottomHeight (kScreen_height == 812.0 ? 83 : 49)

 

3 、UIBarButtonItem

%title插图%num

在iOS 11 里面使用 CustomView创建的item造成frame错乱以及设置边距item.width = -20等失效问题,归根结底是因为苹果更改了iOS11上面uinavigationBar的结构

%title插图%num

iOS 11以前的navigationBar添加的right和left直接添加到navigationBar上面,并且使用的是frame布局的

 iOS 11 navigationBar结构图:
%title插图%num

有图我们可以看到, 添加的right和leftUIBarButtonItem并不是直接添加到nagitionBar上面。而是在外面嵌套了一个uibuttonBarStackView,而StackView并不是使用的frame,而是用的约束来布局,所以必须得用 AutoLayout 了!

 /// 适配iOS11 UIBarButtonItem 添加自定义布局
 if (@available(iOS 9.0,*)) { /// 强迫症的朋友这里也是可以写成11.0
    [backbtn.widthAnchor constraintEqualToConstant:size.width].active = true;
    [backbtn.heightAnchor constraintEqualToConstant:size.height].active = true;
  }

iOS11里面正确效果:

%title插图%num

 4 、tableView

iOS11 tableView默认启用了Self-Sizing,这个应该是UITableView*大的改变。我们知道在iOS8引入Self-Sizing 之后,我们可以通过实现estimatedRowHeight相关的属性来展示动态的内容,实现了estimatedRowHeight属性后,得到的初始contenSize是个估算值,是通过estimatedRowHeight 乘以 cell的个数得到的,并不是*终的contenSize,只是当前屏幕能够显示的cell个数,滑动时,tableView不停地得到新的cell,更新自己的contenSize。

Self-Sizing在iOS11下是默认开启的,Headers, footers, and cells都默认开启Self-Sizing,所有estimated 高度默认值从iOS11之前的 0 改变为UITableViewAutomaticDimension:

如果目前项目中没有使用estimateRowHeight属性,在iOS11的环境下就要注意了,因为开启Self-Sizing之后,tableView是使用estimateRowHeight属性的,这样就会造成contentSize和contentOffset值的变化,如果是有动画是观察这两个属性的变化进行的,就会造成动画的异常,因为在估算行高机制下,contentSize的值是一点点地变化更新的,所有cell显示完后才是*终的contentSize值。因为不会缓存正确的行高,tableView reloadData的时候,会重新计算contentSize,就有可能会引起contentOffset的变化。

iOS11下不想使用Self-Sizing的话,可以通过以下方式关闭:

//添加以下代码

self.tableView.estimatedRowHeight =0;

self.tableView.estimatedSectionHeaderHeight =0;

self.tableView.estimatedSectionFooterHeight =0;

或者进行全局的配置:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //适配iOS11的tableView问题
    [UITableView appearance].estimatedRowHeight = 0;
    [UITableView appearance].estimatedSectionHeaderHeight = 0;
    [UITableView appearance].estimatedSectionFooterHeight = 0;

    if (@available(iOS 11, *)) {
        [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 
//iOS11 解决SafeArea的问题,同时能解决pop时上级页面scrollView抖动的问题
    }
    return YES;
}

如果你发现你的表头表尾单元格突然变得大了,问题就在这里!

5 、tableView分割线

iOS 7 引入separatorInset属性,用以设置 cell 的分割线边距,在 iOS 11 中对其进行了扩展。可以通过新增的UITableViewSeparatorInsetReference枚举类型的separatorInsetReference属性来设置separatorInset属性的参照值。

 

typedef NS_ENUM(NSInteger, UITableViewSeparatorInsetReference) {

    // The value set to the separatorInset property is interpreted as an offset from the edges of the cell.

    UITableViewSeparatorInsetFromCellEdges,

    

    // The value set to the separatorInset property is interpreted as an offset from the automatic separator insets.

    UITableViewSeparatorInsetFromAutomaticInsets

API_AVAILABLE(ios(11.0), tvos(11.0));

他们的效果是这样的 :
%title插图%num%title插图%num
一般我们都会设置分割线左边顶个:使用下面的全局配置:
  1. – (void)_setTableViewAppearace {
  2. [[UITableView appearance]setTableFooterView:[UIView new]];
  3. [[UITableView appearance]setSeparatorInset:UIEdgeInsetsZero];
  4. }

 


基本就是这样了,如果有其它的问题会继续补充!

《深入解析Mac OS X & iOS操作系统》读书笔记

本书对OS XiOS的底层细节讲的非常详细,各方面都有所涉及,对于深入了解OS XiOS有很大帮助。对于一般App开发人员来说,我感觉本书内容并不太适合,所以完全以扩充知识面的目标读完本书,以下是以我所关心的内容整理的读书笔记,希望对大家有所帮助。

*章 达尔文主义:OS X的进化史

  1. OS X是Mac OS ClassicNeXTSTEP的融合。
  2. Darwin是操作系统的类UNIX核心,由kernel、XNU和运行时组成,是OS X和iOS的重要组成部分,OS X的Darwin是开源的,除OS X10.0对应Darwin 1.3.x之外,其他版本都符合:if (OSX.version == 10.x.y) Darwin.version = (4+x).y
  3. 从10.3(Panther)开始,苹果开发了Safari替代IE for Mac;从10.4.4(Tiger)开始,支持Intel x86架构;10.5(Leopard)有了Objective-C 2.0;10.6(Snow Leopard)开始完整支持64位,提供GCD,完全抛弃PPC架构
  4. iOS和OS X对比:
    • iOS基于ARM架构,而OS X基于Intel i386x86_64
    • iOS内核代码依然闭源,OS X内核XNU则是开源的。
    • iOS内核的编译稍有不同,关注的是嵌入式特性和一些新的API。
    • iOS的系统GUI是SpringBoard,OS X为Aqua
    • iOS的内存管理要紧凑得多,因为移动设备没有几乎无穷的交换空间可以使用。
    • iOS应用程序不允许访问底层UNIX API(即Darwin),也没有root访问权限,而且只能访问自己的目录内数据。

第二章 合众为一:OS X和iOS的架构

  1. OS X和iOS层次结构:
    • 用户体验层:包括AquaDashboardSpotlight辅助功能(Accessibility),iOS中为SpringBoard同时支持Spotlight
    • 应用框架层:包括CocoaCarbonJava,而在iOS中只有Cocoa(Cocoa Touch)
    • 核心框架:也称为图形和媒体层。包括核心框架Open GLQuickTime
    • Darwin:操作系统核心,包括内核UNIX shell环境
      %title插图%num
  2. Darwin架构:
    %title插图%num
  3. GUI是由*个用户态进程launchd启动的,支持GUI工作的主进程是WindowServer
  4. Carbon是OS 9遗留编程接口的名称,已废弃
  5. XNU包含组件:Mach微内核BSD层linkernI/O Kit

第三章 站在巨人的肩膀上:OS X和iOS使用的技术

  1. kqueue是BSD中使用的内核事件通知机制,一个kqueue指的是一个描述符,这个描述符会阻塞等待直到一个特定类型和种类的事件发生。如监视文件、Mach port、套接字、发给进程的特定信号、纳秒级定时器、虚拟内存相关通知、vnode相关。
  2. 强制访问控制(MAC),FreeBSD 5.x*早引入,是OS X隔离机制(Sandboxing,沙盒机制)和iOS的entitlement机制基础。
  3. OS X 10.4(Tiger)引入新的日志模型:Apple System Log(ASL),目标是提供比传统UNIX日志syslog更为灵活的功能。
  4. FSEvents提供了文件系统通知的API,和Linux的inotify类似。
  5. 沙盒架构:
    %title插图%num

第四章 庖丁解进程:Mach-O格式、进程以及线程内幕

  1. UNIX进程生命周期:
    %title插图%num
  2. OS X目前支持三种可执行格式:
    • 解释器脚本格式(以#!后的命令运行脚本,魔数:#!
    • 通用二进制格式(胖二进制格式,魔数:小尾0xcafebabe,大尾0xbebafeca
    • Mach-O格式(OS X原生二进制格式,魔数:32位0xfeedface,64位0xfeedfacf),系统根据魔数类型加载执行文件。
  3. 通用二进制格式本质就是各种架构的二进制文件的打包文件,通过文件头的信息以加载匹配当前架构的二进制文件。
  4. OS X上几乎所有的程序都是动态链接的,默认使用dyld作为动态链接器,这是一个用户态进程,不属于内核。
  5. 32位OS X系统中,用户态和内核态都有完整的4GB地址空间,代价是地址空间切换需要刷新CR3和TLB
  6. __PAGEZERO段,32位为一个页(4K),64位为4GB。为方便捕获空指针和将整数当做指针引用。
  7. OS X中main函数有额外参数apple:
void main (int argc, char **argv, char **envp, char **apple)

第五章 进程跟踪和调试

  1. OS X对DTrace支持较为完整,而iOS没有,需使用CHUDAppleProfileFamily
  2. 获取进程信息系统调用:sysctlproc_info
  3. 使用未文档化的stack_snapshot系统调用,可以捕获指定进程中所有的线程状态。
  4. OS X和iOS都没有使用核心转储文件,而是使用Crash Reporter生成崩溃日志,用以调试应用程序崩溃。
  5. OS X和iOS可将异常端口绑定至BSD进程底层的Mach任务,可很容易实现当应用程序崩溃时自动运行另一个程序。而在UNIX中,很难简单实现,因为只有父进程能收到子进程的死亡通知。
  6. 可使用heapleaksmalloc_history工具调试内存泄漏。

第六章 引导过程:EFI和iBoot

  1. 大部分PC使用BIOS引导(Windows),OS X使用EFI引导,iOS使用iBoot引导
  2. EFI服务提供引导服务运行时服务,前者只能只能在EFI模式下使用,后者在退出EFI模式,即操作系统加载后也能使用,I/O Kit重度使用。
  3. EFI引导完成后得到*终的BootStruct,将其和控制权交给内核完成引导。
  4. iOS引导过程:(除引导ROM外,其他步骤都是加密且数字签名的。)
    %title插图%num

第七章 launchd

  1. OS X和iOS中的launchd对应UN*X系统中的init,但相对init有许多改进。包含了如atd、crond、inetd/xinetd等daemon,支持事务、autorun和文件系统观察,整合I/O Kit。
  2. launchd区分两种后台作业:
    • 守护程序(daemon):和用户没有交互,不考虑是否有用户登录系统。
    • 代理程序(agent):可以和用户有交互,只有在用户登录时启动。
  3. iOS中的lockdownd,负责处理设备激活、备份、崩溃报告、设备同步等。
  4. OS X中的GUI shell是Finder,iOS为SpringBoard
  5. XPC是Lion和iOS5新引入的轻量级进程间通信原语,目前闭源。

第八章 内核架构

  1. 内核架构设计类型有巨内核(UNIX、Linux)、微内核(Mach)、混合内核(XNU、Windows)。
  2. 内核态、用户态转换分为自愿转换、非自愿转换。
    • 自愿转换:使用内核服务,即系统调用。
    • 非自愿转换:发生异常、中断或处理器陷阱时。
  3. XNU中系统调用有4种:UNIXMACHMDEP(机器相关调用)、DIAG(诊断调用)
  4. 32位系统下,UNIX系统调用编号为正数,MACH为负数,64位则都为正数,*高位字节包含调用类型。

第九章 由生到死——内核引导和内核崩溃

  1. XNU源码中,所有函数的实现都将函数名放在行头,即返回值在上一行,这是为了方便搜索。
  2. pid 0是内核进程kernel_task(准确说0表示没有pid),launchd是*个用户态进程,pid为1。
  3. 可通过KDP协议远程调试内核。

第十章 Mach原语:一切以消息为媒介

  1. Mach的*主要目标就是将所有功能移出内核,放在用户态中,将内核保持在*简状态。
  2. Mach同步原语:互斥体自旋锁信号量锁集。其中信号量和锁集在用户态下可见。
  3. Mach的机器层原语:主机(host)时钟处理器处理器集的抽象

第十一章 Mach调度

  1. 线程是Mach中*小执行单元,线程是包含在任务(task)中的。
  2. Mach内核中没有BSD进程概念,而是以任务表示,一个BSD进程对应一个底层Mach任务对象,kernel_task即是Mach对于内核的表示。
  3. Mach允许创建远程线程,即可以在一个任务中创建另一个任务的线程,Windows也可以实现,而UNIX和Linux不支持这种功能。
  4. Mach调度优先级有128个,Windows为32个,Linux为140个。其中数字越大,优先级越高。
  5. 控制权转交(handoff):Mach调度器允许线程主动放弃CPU,并指定某个特定线程运行。
  6. 可使用续体(continuation):线程可丢弃自己的栈,系统恢复时不需要恢复线程栈,可以明显加快上下文切换速度,此项特性在Mach中应用广泛。
  7. 通过异步软件陷阱(AST)可使内核响应外带事件,如调度事件,BSD信号基于此实现。
  8. launchd注册异常端口,其子进程也继承同样端口,崩溃报告器(crash reporter)会接受此端口发出的异常,会当发生crash时,崩溃报告器会自动根据需要启动。

第十二章 Mach虚拟内存

  1. Mach的虚拟内存子系统主要分两层:虚拟内存层(机器无关)、物理内存层(机器相关)。
  2. Mach中zone的概念相当于Linux的memory cache和Windows的Poll
  3. zone是一种内存区域,用于快速分配和释放频繁的固定大小的对象。例如,可使用zprint kalloc查看kalloczone
  4. Mach的分页器主要有:Default分页器VNode分页器Device分页器Swapfile分页器Apple-protected分页器Freezer分页器(iOS)。
  5. 分页器只是提供分页操作,不决定具体调度,调度由pageout守护线程执行。

第十三章 BSD层

  1. OS X有UNIX03认证,达到源码级兼容,即提供与UNIX统一的API。
  2. BSD层在Mach层之上,提供了POSIX API。但XNU的BSD不是完整的BSD,即移植部分BSD内容,如VFS网络架构
  3. BSD的进程和线程都是在Mach提供原语的基础上进行了封装,BSD进程和线程对应有Mach的任务和线程。
  4. XNU中内核线程都是Mach线程,没有对应的BSD线程,同样内核任务kernel_task也没有对应的进程(因此其pid为0,表示没有进程pid)。
  5. UNIX模型中,进程不能被“创建”出来,只能通过fork()系统调用复制出来。vforkforkposix_spawn系统调用,底层都是由fork1()实现,只是传入参数不同。
  6. 除了DTrace,XNU在BSD层还提供了其他UNIX具有的ptrace,但功能大大缩水,如不能读写其他进程内存。
  7. Mach通过异常机制处理底层的陷阱,BSD则在异常机制之上构建了信号处理机制。操作系统和用户产生的信号先被Mach转换为异常,然后再由BSD产生信号。

第十四章 有新有旧:BSD高级功能

  1. OS X和iOS低内存处理机制称为Jetsam,或Memorystatus。用于杀掉消耗过多太多内存的进程并抛弃占用内存。
  2. iOS中,Jetsam/Memorystatus和默认的freezer结合使用,实现内存冷冻而不是杀死。
  3. 从Mountain Lion和iOS6开始,实行内核地址空间布局随机化(KASLR),以提高系统安全性。
  4. 工作队列(work queue),作用是为应用程序提供多线程支持并扩展到多处理器支持,为GCD提供了基础。
  5. MAC是从TrustdBSD引入的强大安全特性,在OS X主要体现在沙盒机制,在iOS中主要体现为entitlement机制
  6. sandbox将所有第三方应用限制为只能访问自己的目录;AppleMobileFileIntegrity.kext(用户态守护进程amfid)负责杀掉任何代码签名不正确的进程。

第十五章 文件系统和虚拟文件系统交换

  1. XUN的文件系统是在BSD层实现的,使用了来自Solaris的VFS框架(已成为UNIX内核与文件系统实现之间的标准接口)。
  2. OS X传统上支持3种分区方案:主引导记录(MBR)Apple Partition Map(APM)GUID分区表(GPT)
  3. MBR是除OS X和64位Windows之外其他操作系统的默认分区方案,以磁盘*扇区为引导扇区。
  4. APM是苹果设计用来取代MBR的,现只存于PPC的Mac和iPad Classic和Nano中。
  5. 在苹果普遍使用的是GPT(包括iOS),属于EFI规范的一部分。
  6. LwVM是苹果的私有分区方案,继承自GPT,用于iOS5默认分区方案,它允许分区加密。
  7. CoreStorage是Lion新引入的分区类型,给OS X带来了逻辑卷管理的支持,支持全盘加密,只能创建在GPT驱动器上。
  8. 所有文件系统都提供了同样的原语,内核对文件的接口称为虚拟文件系统交换(VFS)。Mac原生的文件系统为HFS(已废弃)、HFS+
  9. 即插即用是由守护进程diskarbitrationd实现,由launchd启动。
  10. DMG格式,即磁盘镜像文件,包含了整个文件系统,属于苹果私有格式。从Lion开始,允许指定DMG文件用作根文件系统,如安装系统时。

第十六章 基于B树的HFS+文件系统

  1. DOS原生文件系统为FAT,Windows是NTFS,Linux是Ext2/3/4,OS X为HFS+,iOS为HFSX
  2. HFS+通过支持扩展属性来支持访问控制表(ACL)(精确设置任何用户任何组的具体权限)。
  3. 扩展属性可以添加很多额外信息,如文件件的颜色标签、文件下载来源等,可通过ls -l@xattr查看,但像文件压缩、ACL则在这些命令中被屏蔽了,需要更底层的方法查看。
  4. 系统中有很多文件是通过HFS+的压缩属性进行压缩的,如常用的ls命令。可通过ls的-O参数查看。
  5. HFS+使用的UTF-16编码,文件名*长255个字符。
  6. HFS+是大小写不敏感的,但保留大小写,而现版本的HFSX只是在HFS+的基础上变为大小写敏感。
  7. HFS+使用6个特殊文件维护数据:编录(catalog)B树属性B树extent溢出B树热文件B树分配文件启动文件

第十七章 遵守协议:网络协议栈

  1. 苹果原本使用自己的AppleTalk网络协议栈,后放弃才采用TCP/IP。至今仍使用的Bonjour协议AFP协议都是AppleTalk的遗产。
  2. 网络驱动程序套接字(PF_NDRV)(苹果特有),支持用户态下深入数据链路层直接修改原始数据包。
  3. 系统套接字(PF_SYSTEM)(苹果特有),提供了一种内核空间和用户空间通信的方法。
  4. 套接字在内核中是个巨大的数据结构,内核需要维护套接字和文件描述符的映射关系。
  5. XNU支持的网络层协议有:IPv4IPv6AppleTalk
  6. 在网络接口层,lo接口是唯一必须存在的,且属于原生支持接口;en接口(以太网或802.11接口)、fw接口(IP over FireWire)、pdp_ip接口(蜂窝数据连接)、ppp接口(Point-to-Point协议)都不是XNU原生支持的,而是通过内核扩展来创建的。
  7. utun是特殊的原生支持接口,使用的是系统套接字(PF_SYSTEM)*和其他进程通过这个接口提供一个伪接口,这个伪接口的流量都会重新引导到用户态进程。utun发送数据包:
    %title插图%num
  8. XNU支持一下数据包过滤机制,著名的TCPDump就是基于BPF过滤机制实现的。
    不同数据包过滤机制的比较:
    %title插图%num

第十六章 内核扩展模块

  1. OS X和iOS使用kernelcache预链接kext,kernelcache可以签名、加密(iOS就加密了)。
  2. kernelcache在OS X下是动态创建的,以加快引导进程;iOS中则是由苹果提供的一个固定的文件,不同iDevice是不一样的。
  3. OS X中大部分和kext的接口工作是由守护进程kextd完成的,以Mach消息通信,而iOS不存在。
  4. 内核内置组件会以伪kext的形式出现在kext列表中。

第十七章 驱动力——I/O Kit驱动程序框架

  1. XNU使用C++开发驱动程序,其运行时环境称为I/O Kit,是一套几乎自包含的编程环境,可方便的通过面向对象的特性开发驱动程序。
  2. libkern C++运行时是I/O Kit的基础,定义了所有I/O Kit驱动程序都可使用的基础类,如OSObject、OSMetaClass、OSArray、OSDictionary、OSString等。
  3. I/O Kit维护了一个保存所有对象及对象间关系*新信息的数据库,称之为I/O Registy
  4. I/O Kit所有驱动程序都是从公共祖先IOService继承而来的对象。
  5. I/O Kit的驱动程序分为两种:驱动程序(driver)节点(nub),节点就是指两个驱动程序之间的适配器,表示被控制的设备。
  6. I/O Kit驱动程序状态机:
    %title插图%num

OVER!

苹果新专利:AppleCar或可识别交警手势

品玩2月3日讯,据AppleInsider消息,苹果*新曝光了一份识别交通手势的新专利。专利显示,Apple Car可通过传感器识别交警的手势。此外,车辆可配备命令确认装置,用于向交警确认车辆对交通分流条件或机动命令的理解。一旦检测到并确定,从指挥交警那里收集的信息“可以与其他车辆和设备共享,或者存储在数据库中”

%title插图%num

 

IOS13系统升级带来的H5兼容性问题

20号新推送的IOS13给很多app厂商和RD带来了便秘的感觉,目前复现的问题如下,后续还会持续更新:

1.H5 hybrid输入框导致的页面上移,卡住不动。收起减半后,页面出现半截白屏。(IOS12 + IOS13)

IOS12会在键盘弹出时将页面上推,并压缩body的高度。

IOS13会在键盘弹出时将页面上推,但html,body的高度全部不变。

目前移动端的输入框不外乎一下三种:

(1) 原声input

(2)可编辑DIV

(3)自家或他家的富文本编辑器

我司目前使用第二种方案,即可编辑DIV+本地原声虚拟键盘(后续会改为原声H5键盘),可从两个层面解决上述问题。

首先需要在根节点*底部添加一个占位的DIV,并且设置不可见。

<div id=”app”>

<div  v-if=”loadingFlag && !timeoutFlag” class=”hw-box”>

<hw-entrance class=”do-entrance”>www.pingguoyul.cn</hw-entrance>

</div>

<!– IOS13用于兼容处理键盘弹出后页面上拉的问题 –>

<div id=”bottomBackToView”></div>

</div>

接下来要添加样式:

#app #bottomBackToView{

display: block;

width: 100%;

height: 0;

opacity: 0;

}

H5层面:通过监听焦点移除设

oDom.addEventListener(‘blur’, function (www.xinchenkg.com) {

document.body.scrollTop www.xinchenptgw.cn= 0; // IOS12

document.body.style.height www.shengsyLpt.cn= document.body.clientHeight; // IOS12

backView.scrollIntoView(www.jintianxuesha.com); // IOS13用于兼容处理键盘弹出后页面上拉的问题

})

上述解决方法在非可编辑DIV的方案中也许时能够解决问题的,但是在Hybrid H5中可编辑DIV还有其他各种兼容性问题,在此,我们的*终解决方案时需要IOS配合解决的,利用IOS监听键盘收起然后做对应的处理:

bridgeClass.jsEventHook.keyboardWillHide = function() {

if (!switchFlag) {

document.body.scrollTop = 0;  // IOS12

document.body.style.height = document.body.clientHeight;  // IOS12

backView.scrollIntoView(); // IOS13用于兼容处理键盘弹出后页面上拉的问题

APP兼容性专项测试

APP兼容性测试维度包含:新旧版本兼容测试、不同机型测试(系统兼容性、屏幕兼容性、分辨率兼容、尺寸兼容)、不同网络兼容,具体如下:

一、新旧版本兼容性测试

新旧版本覆盖安装升级正常
新增功能,新旧版本覆盖安装后使用正常
二、不同机型测试

1.系统兼容性

iOS系统:iOS11.x、iOS12.x、iOS13.x、iOS14.x
Android系统:Android5.x、Android6.x、Android7.x、Android8.x、Android9.x、Android10.x、Android11.x
2.屏幕兼容性

iOS:
(1)刘海屏:例如:iPhone x、iPhone xs 、iPhone XR、iPhone 11、iPhone 11 Pro、iPhone 11 pro max、iPhone 12、iPhone 12 pro、iPhone 12 pro max、iPhone 12 mini

(2)非刘海屏:例如:iPhone 8、iPhone 8 plus、iPhone 7、iPhone 7 plus、iPhone 6、iPhone 6s、iPhone 6s plus、iPhone 5s

Android:
(1)全面屏:例如:华为P30、红米K30至尊纪念版、荣耀X10、vivo APEX 2020等

(2)非全面屏:例如:华为P10、华为P10 plus、荣耀8等

(3)曲面屏:例如:三星Galaxy S10+、三星Galaxy Note 10+ 5G、华为Mate30 Pro、华为P30 Pro、vivo NEX3等

(4)折叠屏:例如:华为Mate XS 5G、华为mate X2、三星Galaxy Z Fold2 5G、三星 Galaxy W21 5G

3.分辨率兼容性

iOS
(1)1080*2340 :iPhone 12 mini

(2)1284*2778:iPhone 12 pro max

(3)1170*2532:iPhone 12 、iPhone 12 pro

(4)750*1334:iPhone SE 2、iPhone 7、iPhone 8、iPhone 6、iPhone 6s

(5)1242*2688:iPhone 11 pro max、iPhone XS Max

(6)1125*2438:iPhone 11 pro

(7)828*1792:iPhone 11、iPhone XR

(8)1125*2436:iPhone XS、iPhone X

(9)1242*2208:iPhone 8 plus、iPhone 7 plus、iPhone 6s plus

(10)640*1136:iPhone 5s

(11)iOS系统自带的显示模式:标准模式、放大模式

Android
(1)1440*3200:小米11

(2)1344*2772:华为mate 40 Pro

(3)1080*2400:一加8T、vivo S7、OPPO Reno5、荣耀30、小米10青春版、荣耀X10、荣耀Play4T Pro、OPPO A92s、Redmi K30 Pro、华为nova7、三星Galaxy S20 Ultra、荣耀30 Pro 5G、荣耀V30、荣耀V30 Pro、vivo S5、OPPO R17

(4)1080*2460:中兴AXON 20

(5)1080*2376:IQOO 5、vivo X50、vivo X50 Pro、vivo X60 Pro、一加8Pro

(6)1080*2340:锤子坚果R2、荣耀30Pro、魅族17、魅族17Pro、iQOO U1、华为畅享20Pro、华为nova7 Pro、红米9、realme X2

(7)1600*720:红米9A

(8)1080*2408:vivo Y31s、IQOO Neo3、IQOO z1

(9)720*1560:荣耀Play4T

(10)1080*2256:vivo NEX 3 5G

(11)720*1600:OPPO A32、OPPO A8

(12)1080*1920:Mi 10 Pro

(13)2340*1080:小米10

(14)3220*1400:三星Galaxy S20

(15)1080*2280:三星Galaxy Note10

说明:因为Android不同厂家机型多,不同多屏幕分辨率也多,以上主要是列举常见的

4.尺寸兼容性

iOS主要机型尺寸:4寸-6.7寸
Android主要机型尺寸:5寸-6.7寸
5.不同网络兼容性

Wi-Fi切换4G/5G网络情况下功能是否正常
4G/5G网络切换Wi-Fi情况下功能是否正常
有网切换无网情况下功能是否正常
无网切换有网情况下功能是否正常
%title插图%num

IOS控件-UIDatePicker日期选择器

%title插图%num

创建一个DatePicker控件

1 let datePicker = UIDatePicker()
2         //设置日期拾取器对象的中心点位置
3         datePicker.center = CGPoint(x: 160, y: 200)
4         datePicker.tag=1
5         //设置日期*小值
6         datePicker.minimumDate=Date()
7         //*大值为之后的三天
8         datePicker.minimumDate = Date(timeInterval: 3*24*60*60, since: Date())
9         self.view.addSubview(datePicker)

添加一个button

1 let button=UIButton(frame: CGRect(x: 20, y: 360, width: 280, height: 44))
2         button.setTitle(“get date”, for: UIControlState())
3         button.backgroundColor=UIColor.brown
4         button.addTarget(self, action: #selector(ViewController.showDatePicker(_ :)), for: UIControlEvents.touchUpInside)
5         self.view.addSubview(button)

绑定一个点击事件

1 @objc func showDatePicker(_ button:UIButton){
2         //通过tag找到datePicker
3         let datePicker=self.view.viewWithTag(1) as! UIDatePicker
4         //获得datePicker的日期值
5         let date=datePicker.date
6         //新建一个日期格式化对象 用来格式化日期
7         let dateFormatter = DateFormatter()
8         //设置日期的格式
9         dateFormatter.dateFormat=”yyyy–MM–dd HH:mm”
10         //将日期转换为指定字符串
11         let dateAndTime = dateFormatter.string(from: date)
12
13         let dialog = UIAlertController(title: “title”, message: dateAndTime, preferredStyle: UIAlertControllerStyle.alert)
14         let ok = UIAlertAction(title: “ok”, style: UIAlertActionStyle.default, handler: nil)
15         dialog.addAction(ok)
16         self.present(dialog,animated: true,completion: nil)
17     }

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

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速