当你进入某个界面或点击某个按钮后,发现屏幕不再响应事件,然后进入无限等待,debug下可以看到CPU满负荷,RAM也不断增加,那么有可能就进入了循环布局状态。
先说一个简单的循环布局的case,如下图所示。
最底下的subview A在layout时更新superview B的bounds
,B因为不在layout状态,所以会使superview B调用其superview C的setNeedsLayout()
方法,然后进入layout状态,此时C重置B的bounds
,B又使A再次layout。因为对B的bounds
设置不一致,导致循环下去。
可在Launch Arguments
中添加-UIViewLayoutFeedbackLoopDebuggingThreshold
,设置循环布局阀值,如100,然后当循环布局次数超过阀值时会抛出异常,此时通过po [_UIViewLayoutFeedbackLoopDebugger layoutFeedbackLoopDebugger]
可输出详细信息。
看名字就能猜到,这是针对数组的打印信息命令。在传统的C数组中,是无法通过数组本身知道其长度的,所以一直以来lldb
都不能很好的打印C数组,parray
和poarray
解决了这个痛点,可以通过指定打印个数来打印数组的多个内容。
需要注意的是这两个命令都是针对C数组的,区别是以
p
还是po
的方式打印数组内容。另外只能打印堆空间存储的数组。
1 2 3 4 5 6 |
|
register
命令并不是新增的,一直以来都有,但在WWDC2016Session 417 Debugging Tips and Tricks
中重点说了一下,实为解决一类疑难bug的利器,在这里也简单介绍下。
平时是否有过crash在没有源码的第三方库或系统库中,而检查自己代码又没有发现任何问题?那么试试register
命令吧,可能会有意向不到的效果。
通过register read
可以读取当前状态下寄存器存储的变量,在没有源码的情况下运气好就可以获取到一些非常有用的信息,毕竟很有可能是你传入的某个错误值导致的最终crash,而这个值就可能从寄存器中取到,从而快速定位原因。
register
还有一个非常有用的特性,当刚进入某一函数时,可通过register read $arg1 $arg2 ...
读取函数的各个参数值,如下:
在WWDC2016Session 406 Optimizing App Startup Time
中,详细介绍了App启动,即进入main()
函数前做了什么事儿,以及如何优化各个过程,另外提供了DYLD_PRINT_STATISTICS
环境变量方便测试各个过程耗时。
然后在启动时会打印如下所示信息:
Apple给出的建议是:
在最新的设备下,启动时间控制在
400ms
以内
如果你的App不达标,那么就开始着手优化吧!具体各个过程的优化原理及方法,限于篇幅就不再介绍了,感兴趣可以看看Session 406 Optimizing App Startup Time。
Xcode8新增了运行时问题的检查,如下:
runtime issue
主要分为3类:
Thread Sanitizer
检测线程问题继Address Sanitizer
之后,Xcode8中加入Thread Sanitizer
用于检查一些线程问题,并会显示在runtime issue
列表中,使用Thread Sanitizer
需如下图所示开启,重新编译运行后即可。
Thread Sanitizer
主要检测以下问题:
mutexes
pthread_join
malloc
Data races
问题凡是通过Thread Sanitizer
检查出来问题,都代表着有很严重的隐患,那么,赶快修复吧!
Xcode8对Static Analysis
做了近一步加强,新增以下问题检测:
首先需要如下方式开启,Static Analysis
将会检查UI元素
的文字设置是否使用了NSLocalizedString
。
主要检查的是dealloc
中内存的释放。这个功能来的有点晚,是一个给MRC
使用的功能,在ARC
基本普及的今天已经显得不是太重要了,当时没能雪中送炭,如今只能锦上添花了。
这项检查是针对设置nonnull
的property
或返回值
。当设置为nonnull
,但返回值可能为nil
时则会有相应warning。
OS X
和iOS
的底层细节讲的非常详细,各方面都有所涉及,对于深入了解OS X
和iOS
有很大帮助。对于一般App开发人员来说,我感觉本书内容并不太适合,所以完全以扩充知识面的目标读完本书,以下是以我所关心的内容整理的读书笔记,希望对大家有所帮助。另附亚马逊购买地址方便感兴趣的朋友:)
Mac OS Classic
和NeXTSTEP
的融合。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
Safari
替代IE for Mac
;从10.4.4(Tiger)开始,支持Intel x86架构
;10.5(Leopard)有了Objective-C 2.0
;10.6(Snow Leopard)开始完整支持64位,提供GCD
,完全抛弃PPC架构
。ARM
架构,而OS X基于Intel i386
和x86_64
。XNU
则是开源的。SpringBoard
,OS X为Aqua
。Aqua
、Dashboard
、Spotlight
和辅助功能(Accessibility)
,iOS中为SpringBoard
同时支持Spotlight
Cocoa
、Carbon
和Java
,而在iOS中只有Cocoa(Cocoa Touch)
核心框架
、Open GL
和QuickTime
内核
和UNIX shell环境
launchd
启动的,支持GUI工作的主进程是WindowServer
Carbon
是OS 9遗留编程接口的名称,已废弃Mach微内核
、BSD层
、linkern
、I/O Kit
kqueue
是BSD中使用的内核事件通知机制,一个kqueue
指的是一个描述符,这个描述符会阻塞等待直到一个特定类型和种类的事件发生。如监视文件、Mach port、套接字、发给进程的特定信号、纳秒级定时器、虚拟内存相关通知、vnode相关。强制访问控制(MAC)
,FreeBSD 5.x最早引入,是OS X隔离机制(Sandboxing,沙盒机制)
和iOS的entitlement机制
基础。Apple System Log(ASL)
,目标是提供比传统UNIX日志syslog更为灵活的功能。FSEvents
提供了文件系统通知的API,和Linux的inotify
类似。#!
)0xcafebabe
,大尾0xbebafeca
)0xfeedface
,64位0xfeedfacf
),系统根据魔数类型加载执行文件。dyld
作为动态链接器,这是一个用户态进程,不属于内核。__PAGEZERO段
,32位为一个页(4K),64位为4GB。为方便捕获空指针和将整数当做指针引用。1
|
|
DTrace
支持较为完整,而iOS没有,需使用CHUD
或AppleProfileFamily
sysctl
、proc_info
stack_snapshot
系统调用,可以捕获指定进程中所有的线程状态。Crash Reporter
生成崩溃日志,用以调试应用程序崩溃。heap
、leaks
、malloc_history
工具调试内存泄漏。BIOS
引导(Windows),OS X使用EFI
引导,iOS使用iBoot
引导引导服务
和运行时服务
,前者只能只能在EFI模式下使用,后者在退出EFI模式,即操作系统加载后也能使用,I/O Kit重度使用。BootStruct
,将其和控制权交给内核完成引导。launchd
对应UN*X系统中的init
,但相对init
有许多改进。包含了如atd、crond、inetd/xinetd等daemon,支持事务、autorun和文件系统观察,整合I/O Kit。lockdownd
,负责处理设备激活、备份、崩溃报告、设备同步等。Finder
,iOS为SpringBoard
。XPC
是Lion和iOS5新引入的轻量级进程间通信原语,目前闭源。巨内核
(UNIX、Linux)、微内核
(Mach)、混合内核
(XNU、Windows)。UNIX
、MACH
、MDEP
(机器相关调用)、DIAG
(诊断调用)kernel_task
(准确说0表示没有pid),launchd
是第一个用户态进程,pid为1。KDP
协议远程调试内核。互斥体
、自旋锁
、信号量
、锁集
。其中信号量和锁集在用户态下可见。主机(host)
、时钟
、处理器
和处理器集的抽象
。任务(task)
中的。kernel_task
即是Mach对于内核的表示。远程线程
,即可以在一个任务中创建另一个任务的线程,Windows也可以实现,而UNIX和Linux不支持这种功能。控制权转交(handoff)
:Mach调度器允许线程主动放弃CPU,并指定某个特定线程运行。可使用续体(continuation)
:线程可丢弃自己的栈,系统恢复时不需要恢复线程栈,可以明显加快上下文切换速度,此项特性在Mach中应用广泛。异步软件陷阱(AST)
可使内核响应外带事件,如调度事件,BSD信号基于此实现。launchd
注册异常端口,其子进程也继承同样端口,崩溃报告器(crash reporter)
会接受此端口发出的异常,会当发生crash时,崩溃报告器会自动根据需要启动。虚拟内存层
(机器无关)、物理内存层
(机器相关)。zone
的概念相当于Linux的memory cache
和Windows的Poll
。zone
是一种内存区域,用于快速分配和释放频繁的固定大小的对象。例如,可使用zprint kalloc
查看kalloc
的zone
。Default分页器
、VNode分页器
、Device分页器
、Swapfile分页器
、Apple-protected分页器
、Freezer分页器
(iOS)。pageout
守护线程执行。UNIX03
认证,达到源码级兼容,即提供与UNIX统一的API。POSIX API
。但XNU的BSD不是完整的BSD,即移植部分BSD内容,如VFS
和网络架构
。kernel_task
也没有对应的进程(因此其pid为0,表示没有进程pid)。fork()
系统调用复制出来。vfork
、fork
、posix_spawn
系统调用,底层都是由fork1()
实现,只是传入参数不同。DTrace
,XNU在BSD层还提供了其他UNIX具有的ptrace
,但功能大大缩水,如不能读写其他进程内存。异常机制
处理底层的陷阱,BSD则在异常机制
之上构建了信号处理机制
。操作系统和用户产生的信号先被Mach转换为异常,然后再由BSD产生信号。Jetsam
,或Memorystatus
。用于杀掉消耗过多太多内存的进程并抛弃占用内存。Jetsam/Memorystatus
和默认的freezer
结合使用,实现内存冷冻而不是杀死。内核地址空间布局随机化(KASLR)
,以提高系统安全性。工作队列(work queue)
,作用是为应用程序提供多线程支持并扩展到多处理器支持,为GCD
提供了基础。沙盒机制
,在iOS中主要体现为entitlement机制
。sandbox
将所有第三方应用限制为只能访问自己的目录;AppleMobileFileIntegrity.kext
(用户态守护进程amfid)负责杀掉任何代码签名不正确的进程。文件系统
是在BSD层实现的,使用了来自Solaris的VFS框架
(已成为UNIX内核与文件系统实现之间的标准接口)。主引导记录(MBR)
、Apple Partition Map(APM)
、GUID分区表(GPT)
。MBR
是除OS X和64位Windows之外其他操作系统的默认分区方案,以磁盘第一扇区为引导扇区。APM
是苹果设计用来取代MBR
的,现只存于PPC的Mac和iPad Classic和Nano中。GPT
(包括iOS),属于EFI规范的一部分。LwVM
是苹果的私有分区方案,继承自GPT,用于iOS5默认分区方案,它允许分区加密。CoreStorage
是Lion新引入的分区类型,给OS X带来了逻辑卷管理的支持,支持全盘加密,只能创建在GPT
驱动器上。虚拟文件系统交换(VFS)
。Mac原生的文件系统为HFS
(已废弃)、HFS+
。diskarbitrationd
实现,由launchd启动。DMG
格式,即磁盘镜像文件,包含了整个文件系统,属于苹果私有格式。从Lion开始,允许指定DMG
文件用作根文件系统,如安装系统时。FAT
,Windows是NTFS
,Linux是Ext2/3/4
,OS X为HFS+
,iOS为HFSX
。扩展属性
来支持访问控制表(ACL)
(精确设置任何用户任何组的具体权限)。扩展属性
可以添加很多额外信息,如文件件的颜色标签、文件下载来源等,可通过ls -l@
或xattr
查看,但像文件压缩、ACL则在这些命令中被屏蔽了,需要更底层的方法查看。HFS+
的压缩属性进行压缩的,如常用的ls命令。可通过ls的-O参数查看。HFS+
使用的UTF-16编码,文件名最长255个字符。HFS+
是大小写不敏感的,但保留大小写,而现版本的HFSX
只是在HFS+
的基础上变为大小写敏感。HFS+
使用6个特殊文件维护数据:编录(catalog)B树
、属性B树
、extent溢出B树
、热文件B树
、分配文件
、启动文件
。AppleTalk
网络协议栈,后放弃才采用TCP/IP
。至今仍使用的Bonjour协议
和AFP协议
都是AppleTalk
的遗产。网络驱动程序套接字(PF_NDRV)
(苹果特有),支持用户态下深入数据链路层直接修改原始数据包。系统套接字(PF_SYSTEM)
(苹果特有),提供了一种内核空间和用户空间通信的方法。IPv4
、IPv6
、AppleTalk
。lo接口
是唯一必须存在的,且属于原生支持接口;en接口
(以太网或802.11接口)、fw接口
(IP over FireWire)、pdp_ip接口
(蜂窝数据连接)、ppp接口
(Point-to-Point协议)都不是XNU原生支持的,而是通过内核扩展来创建的。utun
是特殊的原生支持接口,使用的是系统套接字(PF_SYSTEM)
,VPN
和其他进程通过这个接口提供一个伪接口,这个伪接口的流量都会重新引导到用户态进程。utun
发送数据包:
XNU支持一下数据包过滤机制,著名的TCPDump
就是基于BPF过滤机制
实现的。
不同数据包过滤机制的比较:
kernelcache
预链接kext,kernelcache
可以签名、加密(iOS就加密了)。kernelcache
在OS X下是动态创建的,以加快引导进程;iOS中则是由苹果提供的一个固定的文件,不同iDevice是不一样的。kextd
完成的,以Mach消息通信,而iOS不存在。伪kext
的形式出现在kext列表中。I/O Kit
,是一套几乎自包含的编程环境,可方便的通过面向对象的特性开发驱动程序。libkern C++
运行时是I/O Kit
的基础,定义了所有I/O Kit
驱动程序都可使用的基础类,如OSObject、OSMetaClass、OSArray、OSDictionary、OSString等。I/O Kit
维护了一个保存所有对象及对象间关系最新信息的数据库,称之为I/O Registy
。I/O Kit
所有驱动程序都是从公共祖先IOService
继承而来的对象。I/O Kit
的驱动程序分为两种:驱动程序(driver)
和节点(nub)
,节点就是指两个驱动程序之间的适配器,表示被控制的设备。I/O Kit
驱动程序状态机:OVER!
]]>Web Search
和Workflows
配置。
尽可能把会使用到的搜索地址添加进来,这样就可以尽可能使用Alfred
作为入口,提升效率,以下是我自行添加的搜索地址URL
https://github.com/search?utf8=%E2%9C%93&q={query}
需勾选Encode spaces as +
http://stackoverflow.com/search?q={query}
需勾选Encode spaces as +
https://www.baidu.com/s?wd={query}
http://baike.baidu.com/search/word?word={query}
http://image.baidu.com/i?z=&s=1&ct=201326592&cl=2&lm=-1&tn=baiduimage&ie=utf-8&word={query}
http://www.zhihu.com/search?q={query}&type1=all
http://list.tmall.com/search_product.htm?q={query}
http://s.taobao.com/search?q={query}
http://search.jd.com/Search?keyword={query}&enc=utf-8
直接使用的Examples里面的,在此基础上稍微做了下改进,修复了一些bug,还支持tab
自动补全,可以在搜索建议结果未返回时直接搜索内容。
如果使用代理服务器,可以通过修改源码实现,点击如下所示Open workflow folder
然后编辑workflows.php,找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
这个是照虎画猫自己写的,使用和Google Suggest
类似。
以上两个Workflow
是我最频繁使用的功能,相当于直接把Google和Baidu网站上的搜索框放进了Alfred
,利用搜索建议大大提升搜索效率,强烈推荐。
获取本地IP和公网IP。
快速打开/关闭VPN。
很方便的转换Color值,如从Hex转到UIColor的rgb格式,可以识别NSColor和UIColor的方法,非常方便。
方便的进行URL、HTML、Base64的encode和decode。
SSH
时需要通过Wi-Fi
来连接,输入命令时反应比较慢,还容易掉线,尤其是在越狱开发时,有时会有砸设备、砸Mac的冲动,当然我砸不起,只是想想。
如果能通过USB连接就好了,既不需要依赖Wi-Fi
,而且速度非常快,感谢开源社区的大牛们,usbmuxd开源库就顺带实现了这个功能。
通过brew来安装(当然也可以自己去下源码手动安装,由于依赖项比较多,所以很繁琐)
1
|
|
安装usbmuxd
库之后,就顺带安装了一个小工具iproxy
,该工具会将设备上的端口号映射到电脑上的某一个端口,例如:
1
|
|
以上命令就是把当前连接设备的22端口(SSH端口)映射到电脑的2222端口,那么想和设备22端口通信,直接和本地的2222端口通信就可以了。 因此,SSH连接设备就可以这样连接了:
1
|
|
这样就再也不用依赖Wi-Fi
了,而且反应很流畅,当然此工具不仅可以用于SSH,也可以映射其他端口,这个就看个人需求了。
在使用block过程中,经常会遇到retain cycle
的问题,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
在block中用到了self,self会被block retain,而_observer会copy一份该block,就是说_observer间接持有self,同时当前的self也会retain _observer,最终导致self持有_observer,_observer持有self,形成retain cycle
。
对于在block中的retain cycle
,在2011 WWDC Session #322 (Objective-C Advancements in Depth)有一个解决方案weak-strong dance
,很漂亮的名字。其实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
在block中使用self之前先用一个__weak
变量引用self,导致block不会retain self,打破retain cycle,然后在block中使用wself之前先用__strong
类型变量引用wself,以确保使用过程中不会dealloc。简而言之就是推迟对self的retain,在使用时才进行retain。这有点像lazy loading的意思。
注:iOS5以下没有__weak
,则需使用__unsafe_unretained
。
在非ARC环境中,显然之前的使用的__weak
或__unsafe_unretained
将会是无效的,那么我们需使用另外一种方法来代替,这里就需要用到__block
。
__block
在ARC和非ARC中有点细微的差别(Automatic Reference Counting : Blocks):
__block
会自动进行retain__block
不会自动进行retain因此首先要注意的一点就是用__block
打破retain cycle
的方法仅在非ARC下有效,下面是非ARC的weak-strong dance
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
将self赋值为__block
类型变量,在非ARC中__block
类型变量不会进行retain,从而打破retain cycle,然后在使用bself前进行retain,以确保在使用过程中不会dealloc。
如果使用Ad-hoc
证书是有限制的,100个设备,但用In-House Distribution
证书就不存限制了,可以说它是iOS中权限最大的证书(不仅没有分发数目限制,而且可以通过多种途径分发app,更重要的是不需要进行应用程序审核)。
Ad-hoc
需要,毕竟只是用于测试目的的。In-House Distribution
不需要。
不能,需要另外购买99刀的开发者证书。
iTunes
进行安装。iPhone 配置实用工具
或Apple Configurator
将应用程序安装在设备上。如果泄露,可能导致外部人员使用企业app,此时可以撤销证书,然后生成新证书重新分发app。这样旧证书分发的app就无法使用了。原理是这样的:用户首次打开应用程序时,会通过联系 Apple 的 OCSP 服务器来验证分发证书,OCSP 响应会在设备上缓存一段时间(OCSP 服务器所指定的时间段),一般为 3 天到 7 天之间。在此期间将不会再次检查证书的有效性,直至设备重新启动且缓存的响应过期为止。如果那时收到撤销命令,则应用程序将被阻止运行。
理论上可以,但是这是违反Apple协议的,这是要负法律责任的。一般能申请得到企业开发者证书的都是500人以上具有一定规模的企业,这样的企业不会明目张胆去违反Apple的协议。再说,在上一个问题中说明的证书验证原理表明,Apple有能力监控到一个In-House Distribution
分发的app的用户使用情况,当发现有明显异常(随意分发的用户人数肯定远大于公司员工人数),相信Apple不会坐视不管。话又说回来,只是少量的分发给一些客户或个人的话,这也没有什么大碍。因此这完全取决于随意分发的量。
xcrun
工具写成shell,然后再添加到xCode的工程下,这样就很方便的在每次build之后就能生成相应的ipa文件。shell如下:
1 2 3 4 5 6 7 |
|
会在工程根目录下生成一个build文件夹,然后会把生成的日志和ipa文件放到文件夹下。
然后在project下的Build Phase
下Add Run Script
将shell路径添加进去。
Documents
目录下,这样用户能在一开始就能有一些文件可用。像类似于一些阅读类app就经常将几本默认书籍放入Documents
目录下。
首先我们来看一下一个应用程序的文件结构:
如图,可以看出.app
文件和Documents
文件夹处于同一级,而.app
文件正是我们build时打包生成的应用程序文件,我们向project中添加的resource文件也当然只处于.app
中。这样,就想到在Info.plist
中会不会有这样的设置,可以在应用程序安装时将指定文件添加到Documents
目录,很可惜,没有这样的设置。
那么,现在看来,要实现这样的功能就只能曲线救国了,思路如下:
将文件随app一起打包,然后在程序第一次运行时将这些文件通过代码copy到Documents
目录下,当然要记录一个状态位,每次去读取这个状态位来进行判断。