最简单的方式是使用 pkg 安装。下载安装包,然后双击即可。
安装完后要重新开一个终端才能运行,否则会提示 port command not found
.
1
|
|
1 2 3 4 5 |
|
参考 [Mac] Python Numpy Scipy Matplotlib 快速安装。
如前所述,CSV 文件其实就是文本文件,常规格式如下:
1 2 3 |
|
通过 Numbers 打开,看起来是这样的:
Header1 | Header2 | Header3 |
---|---|---|
Data1 | Data2 | Data3 |
Data1 | Data2 | Data3 |
知道了规则,不难用 Python 写出大概这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
所有的数据都读到了 datas 数组中,下面是对这组数据进行绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
效果如图:
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2017/05/11/use-python-to-handle-csv-file/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
但是,一款出色的产品,往往不只有出色的核心功能,它的产品细节、用户体验也是趋于完善近乎完美的。大牛的你,虽然不想干这些脏活累活,然而,牛在江湖身不由己,你有三个选择:
现在要说的 App 本地化,就是一个典型的又粗又脏又累的活:
身为一名程序员,看到上述三个烦死人的特征,除了累觉不爱之外,还有没有别的什么想法呢?
利用脚本自动完成啊!脚本天生就是干这些事的。
脚本已经写好了:AppStringsTranslator
xx.strings
文件,遍历每一行。"xx" = "xxx";
,则把等号左右的key
和 value
提取出来,保存到 keys
和 values
两个数组中。keys
,而往 values
里加一个空字符串。values
里的每个元素。keys
和翻译结果按照同样的格式写入到新的 xx_toLang.strings
文件。第 3 步的目的是,保留原来的语言文件里的注释和空行。
AppStringsTranslator.py
,填上你自己的百度 AppKey/SecretKey
,见这里。python AppStringsTranslator.py
。Localizable.strings
。zh
。en cht jp kor
。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
有时候,我们会在语言文件里添加一些特殊的字符,比如 %@ %d %lld
之类的格式化占位符,这些字符是不需要被翻译的。但是利用脚本,是没有办法智能识别哪些字符需要保留,以及保留到哪里的,这种情况还是需要开发者自己去处理。
原文作者: lslin
原文链接: http://blog.lessfun.com/blog/2016/12/26/ios-app-strings-translator/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
有过看 Log 经历的人都知道,Log 到看时方恨少,对着十多兆的日志文件,满屏幕的文本信息,却找不到自己想要的那个关键点的记录,这是极其让人崩溃的。于是,看代码和写代码的时候都会下意识地在关键路径都打上 Log,宁可错打一千,不可放过一条。几次下来, Log 文件越来越大,看 Log 越来越累……如果逼不得已非得要大海捞针,那就让捞针这个过程更方便一些吧,这时候,关键字过滤以及高亮的需求随之而来。
软件界的神器之所以能成为神器,除了其本身功能突出之外,很大部分原因是它支持各种定制,能方便地添加插件或扩展,比如 Vim,Alfred,Chrome,等等。Sublime Text 也毫不例外地支持插件和自定义。本文要说的就是如何为 Log 文本文件添加自定义的语法高亮。
Package Control 是 Sublime Text 的插件管理器,其本身也是一个插件,通过 Package Control 能很方便地查找、安装、卸载插件。
PackageDev 是一款快速创建 Sublime Text 的语法定义、片段等扩展文件的插件。
安装 PackageDev 后,通过Tools | Packages | Package Development | New Syntax Definition
菜单,创建一个新的语法文件。
这时候,会自动新建一个空模板:
1 2 3 4 5 6 7 8 9 10 |
|
包括设置语法名、后缀名、需要匹配的关键字、使用什么语法高亮关键字,等等。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
点击保存,在 Packages/User/
目录下创建一个 MyLog
文件夹,文件名改为 MyLog.YAML-tmLanguage
,保存到 MyLog
文件中。
语法的定义可以参考官方文档。
匹配的关键字的 name
的定义,见这里。
上面的 YAML 文件提高了可读性,但为了让 Sublime Text 能识别,还需要转换为 tmLanguage 文件。
编辑完并保存 YAML 后,点击 Tools | Build System | Convert to
,或者按 Ctrl(CMD) + B
把 YAML 文件转为 tmLanguage
,这时应该会自动保存到 Sublime Text 的配置文件夹中 Packages/User/MyLog/MyLog.tmLanguage
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
此后,使用 Sublime Text 打开一个后缀名 .mylog
的文件,或者打开 Plain Text
类型的文件然后设置 Syntax
为 MyLog
类型,就可以看到高亮的效果了。如图:
Filter Lines 插件能方便地过滤特定字符串到一个新的 Tab 中,支持全字匹配、正则匹配。相对于全文搜索而已,把匹配结果输出到一个全新的文件中,能更有助于查看关键信息。
Log Highlight 是专门处理 Log 高亮的插件,原理与自定义语法高亮不同。这个插件也可以自定义语法高亮,同时可定制性与功能也更强大,比如支持跳转、为警告或错误添加书签,等等,有兴趣可以使用一下。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2016/10/28/make-a-custom-syntax-highlighting-for-sublime-text/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
升级到 iOS 9 后,官方新增了 ReplayKit,并且禁用了录屏的私有 API。ReplayKit 并不算是完美的录屏方案,如果想要把梦幻西游的游戏过程录制下来,需要梦幻西游这个应用本身添加 ReplayKit 的支持,然后再把录制的视频分享出去。对于不支持 ReplayKit 的游戏,怎么录制?答案是,没有办法。试想,又有多少个游戏会内置 ReplayKit 呢?
iOS 10 在 iOS 9 的 ReplayKit 保存录屏视频的基础上,增加了视频流实时直播功能(streaming live),官方介绍见 Go Live with ReplayKit。下面详细说说这个流程。
从录制到直播,整体流程如下:
其中,被录制端
可以是任意一个 App,如梦幻西游,广播端
是支持 ReplayKit Live 的直播平台,如虎牙直播。
被录制端需要在原有功能的基础上,增加一个唤起广播的入口。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
如下图的右上角就是开始广播的入口按钮。
如果手机内没有支持广播的 App,会弹框提示到 App Store 查找一个。
如果已经安装支持广播的 App,则会列出,点击后会打开广播端的 Broadcast UI。
下面要说的就是怎么实现一个支持广播的 App。
很多直播 App 本身已经支持通过摄像头进行视频流上传、直播,新增对 ReplayKit Live 的支持,只需要创建两个扩展的 target,分别是 Broadcast UI Extension 和 Broadcast Upload Extension,在XCode 8 中内置了这两个模板。
Broadcast UI 负责广播前的一些初始化工作,比如,让用户填写直播平台的账号、密码、直播标题。从被录制端唤起广播请求并选定广播平台后,会显示 Broadcast UI 界面。
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
效果:
如果用户点击 OK
,则会回调到第二部分中的RPBroadcastActivityViewControllerDelegate
,开始直播会调用 Broadcast Upload 扩展。
第二部分调用startBroadcastWithHandler
,会跑到Broadcast Upload
,本扩展的作用是接收并处理 Broadcast UI 传过来的用户信息,以及处理 RPBroadcastController 传过来的音视频流数据,如编码、上传。
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
ReplayKitUploader
是自定义的一个类,使用了单例模式,负责广播服务的登录、编码、上传功能。使用单例,而不是直接在 SampleHandler 里面处理,是因为 SampleHandler 并不是 Broadcast Upload Extension 里的唯一一个实例,实际上,Upload Extension 会不断地创建很多个 SampleHandler 来处理 CMSampleBufferRef,而我们为了保存一些内部状态,必须使用一个固定的类实例来实现。
iOS 10 新增了很多种 Extension,包括本文提到的两种 Broadcast Extension。主 App 与 Extension 属于不同的两个进程,代码逻辑也是分离的,而实际情况中,主 App 与 Extension 往往会包含相同的逻辑,需要共用代码。
主 App 与 Extension 属于两个不同的 target,共用代码,有以下几种方式:
在 iOS 环境中,想要共享设备屏幕,无论是录播还是直播,都注定了没有直接方便的方案。ReplayKit Live 是目前最标准的做法,只是,使用 ReplayKit 有两个前提,应用本身支持 ReplayKit,直播平台支持 Broadcast Extension。这两个前提,后者比较容易实现,而前者,就需要靠各个应用开发商的自觉了。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2016/09/21/ios-10-replaykit-live-and-broadcast-extension/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
(void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
dispatch_after
dispatch_source_t
每种方法创建的定时器,其可靠性与最小精度都有不同。可靠性
指是否严格按照设定的时间间隔按时执行,最小精度
指支持的最小时间间隔是多少。
谈到定时器,首先需要了解的一个概念是 NSRunLoop。NSRunLoop 是消息处理的一种机制,类似于 Windows 中的消息循环,有个更通用的叫法是 Event Loop。
其原理很简单,启动一个循环,无限地重复接受消息->等待消息->处理消息
这个过程,直到退出。伪代码如下:
1 2 3 4 5 6 |
|
每个线程内部都会有一个 RunLoop,启动 RunLoop 之后,就能够让线程在没有消息时休眠,在有消息时被唤醒并处理消息,避免资源长期被占用。
在 iOS 中,NSThead 和 NSRunLoop 是一一对应的,但创建线程的时候不会默认创建 NSRunLoop,实际上也不允许自己创建 NSRunLoop,在线程内第一次调用[NSRunLoop currentRunLoop]
的时候才会自动创建。
NSRunLoop 也处理 NSTimer 事件,但 NSTimer 并不属于输入源的一种。
深入了解 RunLoop有更深入完整的介绍。
最常用,能满足对间隔要求不严格、对精确度不敏感的需求。
1 2 3 4 5 6 7 |
|
不可靠,其所在的 RunLoop 会定时检测是否可以触发 NSTimer 的事件,但由于 iOS 有多个 RunLoop 的运行模式,如果被切到另一个 run loop,NSTimer 就不会被触发。每个 RunLoop 的循环间隔也无法保证,当某个任务耗时比较久,RunLoop 的下一个消息处理就只能顺延,导致 NSTimer 的时间已经到达,但 Runloop 却无法及时触发 NSTimer,导致该时间点的回调被错过。
苹果官方文档:
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer.
理论上最小精度为 0.1 毫秒。不过由于受 Runloop 的影响,会有 50 ~ 100 毫秒的误差,所以,实际精度可以认为是 0.1 秒。
苹果官方文档:
Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds.
间隔 0.1 秒,调用12次。其中倒数第二次调用前会执行一个比较耗时的运算任务。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
可以看到偏差在 1 ~ 2 毫秒左右。在第 10 次之后执行了一个较耗时的任务,导致第 11 次比预期延迟了 0.5 秒执行。后面的回调仍然按照预设的延时执行。
这是 NSObject 对 NSTimer 封装后提供的一个比较简单的延时方法,内部用的也是 NSTimer,所以,同上。
CADisplayLink 也可以用作定时器,其调用间隔与屏幕刷新频率一致,也就是每秒 60 帧,间隔 16.67 ms。与 NSTimer 类似,如果在两次屏幕刷新之间执行了一个比较耗时的任务,其中的某一帧就会被跳过,造成 UI 卡顿。
1 2 3 4 5 6 7 8 9 10 |
|
如果执行的任务很耗时,也会导致回调被错过,所以并不十分可靠。但是,假如调用者能够确保任务能够在最小时间间隔内执行完成,CADisplayLink 就比较可靠,因为屏幕的刷新频率是固定的。
受限于每秒 60 帧的屏幕刷新频率,注定 CADisplayLink 的最小精度为 16.67 毫秒。误差在 1 毫秒左右。
另外需要注意的是,虽然 CADisplayLink 有一个属性 frameInterval
是用于设置定时器的调用间隔,但是这个属性会在第一次回调之后才生效,对于第一次回调,总是会以 1/60 的间隔来执行的。这样会导致的结果是,比如你设置了每 1 秒执行一次某个方法,但是第一次执行的时候,却是在 16.7 毫秒之后,远远小于预设值。
间隔 0.1 秒,调用12次。其中倒数第二次调用前会执行一个比较耗时的运算任务。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
除了第一次回调,间隔误差比较大之外,别的回调误差在 0.1 ~ 0.5 毫秒之间,精度比 NSTimer 要高。第 11 次回调,受耗时任务影响,延时了 0.5 秒。值得注意的是,第 12 次,延时再次与第一次回调一样,变成了 1/60 秒左右。
换言之,CADisplayLink 在第一次回调以及在耗时任务之后的回调,精度不可控。
dispatch_after
dispatch_after 用起来十分简单,代码紧凑易读,而且可以很轻松地在别的线程分配延时任务,所以使用范围很广泛。
1 2 3 |
|
Any fire of the timer may be delayed by the system in order to improve power consumption and system performance. The upper limit to the allowable delay may be configured with the ‘leeway’ argument, the lower limit is under the control of the system.
延时参数的单位是纳秒。如果有延时,则无法保证。
间隔 0.1 秒,调用12次。其中倒数第二次调用前会执行一个比较耗时的运算任务。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
平均误差 9 毫秒。
dispatch_source_t
经测试,dispatch_source_t 的最小精度和可靠性都与 diapatch_after 差不多。
间隔 0.1 秒,调用12次。其中倒数第二次调用前会执行一个比较耗时的运算任务。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
从结果看,与 diapatch_after
区别不大。
上述的各种定时器,都受限于苹果为了保护电池和提高性能采用的策略,导致无法实时地执行回调。如果你的确需要使用更高精度的定时器,官方也提供了方法,见 High Precision Timers in iOS / OS X
前面所述的定时器,使用方法各有不同,但其核心代码实际上是一样的。
There are many API’s in iOS and OS X that allow waiting for a specified period of time. They may be Objective C or C, and they take different kinds of arguments, but they all end up using the same code inside the kernel.
而有别于普通定时器的高精度定时器,则是基于高优先级的线程调度类创建的定时器,在没有多线程冲突的情况下,这类定时器的请求会被优先处理。
提高调度优先级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
精确延时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
小于 0.5 毫秒。这里有一份实现的代码以及与普通定时器的对比。
详见 官方说明。概述如下:
encodedPutPolicy
。SecretKey
对上一步生成的 encodedPutPolicy
计算 HMAC-SHA1 签名,进行URL安全的Base64编码,得到 encodedSign
。AccessKey
、encodedSign
和 encodedPutPolicy
用:
连接起来,得到Token1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 9 10 |
|
其中,CCHmac
使用的是 Objective C 的 #import <CommonCrypto/CommonCrypto.h>
,在 Swift 中用需要自己添加桥接头文件。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/12/29/swift-create-qiniu-upload-token/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
先简单介绍一下标题的含义。也许你看到iOS
、截屏
,觉得这有什么好长篇大论的,小菜一碟而已。可能你忽略了后台
这个关键词。
这里的关键就在于怎么在 App 切换到后台之后,仍然能够持续截取用户屏幕内容。解决了这点,剩下的就是把图片合成视频,有必要的话再加入声音。
另外,需要说明的是,实现后台截屏只能使用私有 API,而苹果是不允许这类 App 上架的,就算你用了一些技巧(比如动态加载私有 API 以绕过 App Store 的审核)而上架,假以时日苹果也会发现并且下架处理。Display Recorder 就是这么做并且被下架的,所以现在它发到越狱市场了。
在 iOS 上录制全局屏幕,保存成一个视频,这么一个小众需求到底有什么作用?虽然一般用户都不会用到,不过它还是有点用的:
iOS 的系统封闭,API 变化无常,所以并没有一个可以全版本 iOS 系统通用的后台截屏方法。下面所列的方法都因系统版本而异,前提都是非越狱。
_UICreateScreenUIImage 是 UIImage 的一个私有方法,在 iOS 6以前可以用于后台调用截屏,方法如下:
1 2 3 4 5 6 |
|
但是,在 iOS 6 以后,这个方法不允许在 App 切到后台的时候调用了,会在调试控制台输出不能调用的错误提示。
RecordMyScreen 用的就是这个方法,不过实测在 iOS 7以上,只能截到黑色的空白图片。网上说 RecordMyScreen 就是 Display Recorder 的开源版本。
其中涉及到后台截屏的代码如下:
1 2 3 4 5 6 7 8 9 10 11 |
|
但是,经过测试,在iOS 7和iOS 8上,这个方法没办法正确截屏,只能得到黑色的图片。而且,虽说 RecordMyScreen 是一个开源项目,但实际上它并不是一份完整可用的代码,开源到中途,作者发现有人在窃取他的项目源码,于是停止了开源。虽然如此,该项目中对于音视频的编码、合成部分的处理都是很值得参考的。
ScreenRecorder 用了这个方法,但是实际上这个项目没办法在后台运行。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
好吧,前面提到的几种方法在 iOS 7 以上版本都没啥卵用。现在说的这个方法是可以在 iOS 7 和 iOS 8 上使用的,只是 iOS 9 禁用了该方法。
与 RecordMyScreen 类似,还是基于 IOSurface 私有库,只是调用的方法不太一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
见:
在 App 后台得到截图之后怎么处理成视频呢?其实在 RecordMyScreen 中有完整的代码可以参考,只是它里面截屏的方法需要替换为本文提到的第四种
实现,不赘述。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/12/25/ios-record-screen-in-background/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
LSLog-XCode 是一款 XCode 插件,用于过滤 XCode 调试控制台输出的 Log,支持正则表达式,可自定义前缀字符串以区分不同等级的Log,并修改对应的文字颜色。
LSLog-XCode 内置支持 XCodeColors 插件。如果你没有安装 XCodeColors,LSLog-XCode 会采用类似 XCodeColors 的语法,高亮显示调试器打印的 Log。
不同 Log 等级对应的前缀字符串及默认文字颜色:
<ERROR>
, RGB(214, 57, 30)<WARNING>
, RGB(204, 121, 32)<INFO>
, RGB(32, 32, 32)<VERBOSE>
, RGB(0, 0, 255)LSLog-XCode.xcplugin 被保存在 ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins
,如果要卸载,只需删除这个文件:~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/LSLog-XCode.xcplugin
。
原文作者: lslin
原文链接: http://blog.lessfun.com/blog/2015/12/18/lslog-xcode-plugin-to-filter-and-colorize-debugging-console/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
大部分视图控制器切换导致的问题,根本原因都是使用了动画,因为执行动画需要时间,在动画未完成的时候又进行另一个切换动画,容易产生异常,假如在 Push 和 Pop 的过程不使用动画,世界会清静很多。所以本文只讨论使用了动画的视图切换。也就是使用以下方式的 Push 和 Pop:
1 2 |
|
连续两次 Push 不同的 ViewController 是没问题的,比如这样:
1 2 3 4 |
|
但是,如果不小心连续 Push 了同一个 ViewController,并且 animated 为 YES,则会 Crash:Pushing the same view controller instance more than once is not supported
。
这种情况很有可能发生,特别是界面上触发切换的入口不止一处,并且各个入口的点击没有互斥的话,用两根手指同时点击屏幕就会同时触发两个入口的切换了。多点触碰导致的同时 Push,基本上是防不胜防,当界面元素很复杂的时候,特别容易出现这个问题,而指望从用户交互的角度上避免这个问题是不可能的,测试美眉以暴力测试、胡乱点击而著称,防得了用户防不住测试。
所以我们需要从根本上解决这个问题:当一个 Push 动画还没完成的时候,不允许再 Push 别的 ViewController。这样处理是没有问题的,因为连续带动画地 Push 多个 ViewController 肯定不是开发和产品的意愿,就算有这种需求,也可以通过禁用动画的方式来解决。
继承 UINavigationController 并重载 pushViewController 方法。
isSwitching == YES
,则忽略这次 Push。isSwitching = YES
再继续切换。isSwitching
改为 NO。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
连续 Pop ,可能会导致两种情况。
例如,下面的代码,执行到第二句的时候,self 已经被释放了。
1 2 |
|
假如你避开了上面那种调用,换成了这样:
1 2 |
|
由于访问的是全局的 AppDelegate,自然避免了调用者被释放的问题,但是,连续两次动画 Pop,在iOS 7.X 系统会导致界面混乱、卡死、莫名其妙的崩溃(iOS 8 貌似不存在类似的问题)。比如,下面这个崩溃的堆栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
从崩溃记录完全看不出原因,十分坑爹。
Push 的过程中调用 Pop,会导致界面卡死,表现为:不响应任何点击、手势操作,但是不会崩溃。这也是在 iOS7 中出现的问题,iOS 8 之后不存在。
同 1.1,重载 Pop 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
手势滑动返回本质上调用的还是 Pop,所以,同上。
不过,还可以更根本地禁止用户进行这样的操作,也就是在切换过程中禁止滑动返回手势。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/09/09/uiviewcontroller-push-pop-trap/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
一个项目开发得越久,添加的功能模块也就越多,相应地,也会慢慢引入大量图片等资源。但是,在移除一些不再使用的模块的时候,开发者往往会把该模块所对应的图片资源一起删除,因为源码和资源是分离的。长久以来,项目中就会存在大量没被使用的资源文件。
在某个时机,也就是需求完成得差不多,Bug 增加得不够多,Crash 上涨得不够快的时候,码农们终于有了一点闲暇时间,打算清理一下资源文件,减少 App 安装包的大小。这是一件体力活,方法无非是,一个一个地复制资源文件名,然后在 XCode 中全局查找该字符串,如果结果为 0,那么这个资源很可能就没有被使用。为什么说很可能?因为在代码中,有可能通过字符串拼接的方式使用了这个资源,而这种情况是没办法通过字符串匹配查找出来的。
道理我们都懂,但是,操作起来也实在是太繁琐了,码农们不可能会乐意这样干的。于是,我们需要这么一款工具:能够迅速找出工程中所有没被使用的资源文件。
果不其然,在我打算写这么一个工具的时候,在网上已经有了两种方案。
1 2 3 4 5 6 7 8 9 10 |
|
缺点:不够智能,不够通用,速度太慢,结果不正确。
Unused 对脚本的调用做了封装,通过界面可以配置一定的信息,然后比较清晰的输入结果。
缺点:实际上,Unused 的内部还是调用了方案 1 的脚本,所以方案 1 的缺点也就是方案 2 的缺点。
LSUnusedResources 很大程度上受 Unused 的影响,比如界面、交互,以及部分代码。但是,本工具在核心代码上做了优化,使其在搜索的速度、结果的正确上都有了很大的提高。
核心步骤,简述如下:
find
命令查找指定后缀名的文件。Unused 会把大量实际上有使用的资源,当做未使用的资源输出。LSUnusedResources 则不会出现这样的问题,并且使得结果更加优化。
举例说明:
你在工程中添加了下面资源:
1 2 3 4 |
|
然后用字符串拼接的方式在代码中引用:
1 2 |
|
icon_tag_x.png
是不应该被当做未使用的资源的,只是以一种比较隐晦的方式间接引用了,所以不应该出现在结果列表中。
Browse..
选择一个文件夹;Search
开始搜索;Delete
可以直接删除资源。原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/09/02/find-unused-resources-in-xcode-project/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
密码计算器是iPhone上的一款私密文件管理器,通过在计算器上输入密码来访问您的私人文件,可以完美地保护您的照片、视频、日记、联系人等私人隐私文件。
当前版本:1.0.1
更新时间:2015.05.06
密码计算器是iPhone上的一款私密文件管理器。那么多年以来,你一定保存了一些私密的照片、视频、笔记或联系人在手机里,并且不想被你身边的人看到。密码计算器可以完美地实现你的需求,保护你的隐私。表面上这是一款计算器,但是可以输入密码来访问你的私人文件。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/04/07/passcalc-protect-your-privacy-in-iphone/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
当TableView需要显示一个Cell时,会先从已创建的Cell中找一个可以重用的,然后展现到屏幕。一般情况下,可以被重用的Cell都滚到了屏幕区域外。如果慢慢地拖动TableView,就可以看到Cell不断地被重用(通过断点可以看到Cell的init或awakeFromNib没有被调用)。但是如果快速滚动,还是可能会看到Cell被创建。
则指定其ReuseIdentify,在delegate返回Cell的时候,调用:
1
|
|
则需要先注册:
1
|
|
缓存基本上可以解决大部分性能问题。TableView需要知道Cell的高度,才能对Cell进行布局;需要知道所有的Cell的高度,才能知道TableView本身的高度,所以,每次调用reloadData,都需要计算所有Cell的高度。我们要尽量减小高度计算的复杂度。
在创建TableView的时候,直接设置其rowHeight属性。
实现代理方法,根据Cell的类型返回不同的高度:
1
|
|
由于需要动态计算高度,所以运算量必然会增大,但是还是存在优化的空间。常见的优化方式是,把cellHeight作为data的一个属性缓存起来,对于每一个data对应的每一个cell,就只需要计算一次高度。示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
当然,这样的方式,还是把运算量放到了TableView的代理方法内,其实也可以在创建ContentInfo本身的时候,就调用它的calcHeight方法,在代理方法里就可以可以直接返回info.cellHeight了。但也要结合实际情况进行取舍,因为有时候,有了数据源,但不一定需要展示TableView,提前计算高度反而会浪费时间。
比如每一个Cell都需要用到的UIImage,UIFont,NSDateFormatter或者任何在绘制时需要的对象,推荐使用类层级的初始化方法中执行分配,并将其存储为静态变量。
如果发现通过StoryBoard+xib+AutoLayout创建Cell时性能满足不了需求,可以考虑去掉AutoLayout。
如果不用AutoLayout还是有问题,可以考虑通过代码创建Cell的Views。
如果使用代码创建还是解决不了问题,那就只能靠自绘了,重载Cell的drawRect方法即可。
子View的层级越深,渲染到屏幕上所需要的计算量就越大。
对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容。
给Cell中View加阴影会引起性能问题,如下面代码会导致滚动时有明显的卡顿:
1 2 3 4 |
|
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2015/04/01/uitableview-performence-improve/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
MarkText是iPhone上的Markdown文本编辑器。支持iCloud同步,实时语法高亮、带CSS的HTML预览、文件导出为HTML/PNG/PDF, 分享到Evernote,Dropbox或Email。
当前版本:1.0.3
更新时间:2015.05.06
MarkText是iPhone与iPad上的一款Markdown文本编辑器,支持iCloud同步,实时语法高亮,能转换为HTML、PNG或PDF文件。通过Email发送格式化的markdown内容,或者调用第三方App打开转换后的文件。
通过iTunes导入markdown文本文件,或通过MarkText创建,编辑时支持实时语法高亮显示。编辑完成之后,可以通过iTunes导出到电脑,或者转换为HTML、PNG、PDF文件并发送到Evernote、Dropbox、Email等第三方App。
辅助工具条可以让你很方便地点击按钮插入Tabs、标题、列表、代码块,或者超链接。左右滑动工具条可以迅速移动光标;双指选择可以快速选择文本。
HTML预览支持CSS样式,语法高亮支持的特性:常规Markdown、MultiMarkdown、脚注、图片、表格和代码块。
通过iCloud,在所有设备自动同步存储所有文件。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/12/10/marktext-a-markdown-text-editor-for-ios/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
想要在AppStore上发布个人App从来都不是一件简单的事,光是申请开发者账号就可以折腾好久了。这是加入个人开发者计划,申请iOS开发者账号的最新教程,2014年版,供参考。什么时候,苹果才能为开发者考虑一下,减少各种繁琐的流程呢?
访问https://developer.apple.com/cn/programs/ios/ ,点击立即注册
。
按照提示,选择注册类型:公司或个人。这里以「个人」为例,点击继续。
注册或创建Apple ID。如果已有,可以继续使用。我选了新建。
按照要求填写接口。需要注意的地方:FirstName 和 LastName 要是自己的名字拼音,由于涉及到审核、支付、收款等操作,不要乱填。另外,中英文的姓名顺序一直都可以把人绕晕,总之,我在 FirstName 填了名字拼音,在 LastName 填姓氏拼音。
设置安全问题答案,找回密码需要。
在协议这里打钩,点击Agree
。
完善个人的一些信息。
至此,Apple ID创建完成。
这时候已有新的Apple ID,再次打开https://developer.apple.com/programs/ios ,正式加入$99/年的个人开发者计划。
选择刚刚创建的Apple ID:
提示选择注册类型,个人或公司。这里选择个人。
输入账单地址等信息,这里需要与信用卡账单显示的地址完全一致,中英文各输入一遍,注意输入手机号时,区号填86
。
选择开发者计划类型。
Review,检查没问题后,提交。
终于到了付款这一步骤了,点击Buy now
。
再次要求登录Apple ID。
输入信用卡信息进行付款。
提交订单。
付款完成提示,等待苹果发送激活的邮件吧。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/12/03/ios-developer-program-tutorial-2014/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
Block
是一个很实用的语法,特别是与GCD结合使用,可以很方便地实现并发、异步任务。但是,如果使用不当,Block 也会引起一些循环引用问题(retain cycle
)—— Block 会 retain ‘self’,而 ‘self‘ 又 retain 了 Block。因为在 ObjC 中,直接调用一个实例变量,会被编译器处理成 ‘self->theVar’,’self’ 是一个 strong 类型的变量,引用计数会加 1,于是,self retains queue, queue retains block,block retains self。
Apple 官方的建议是,传进 Block 之前,把 ‘self’ 转换成 weak automatic 的变量,这样在 Block 中就不会出现对 self 的强引用。如果在 Block 执行完成之前,self 被释放了,weakSelf 也会变为 nil。
示例代码:
1 2 3 4 |
|
clang 的文档表示,在 doSomething 内,weakSelf 不会被释放。但,下面的情况除外:
1 2 3 4 5 |
|
在 doSomething 中,weakSelf 不会变成 nil,不过在 doSomething 执行完成,调用第二个方法 doOtherThing 的时候,weakSelf 有可能被释放,于是,strongSelf 就派上用场了:
1 2 3 4 5 6 |
|
__strong
确保在 Block 内,strongSelf 不会被释放。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/11/22/when-should-use-weakself-and-strongself-in-objc-block/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
当然,不能怪你,你的项目在iOS7.1上是运行地好好的。只能怪苹果没有推出一个完美无Bug的开发工具。
解决方法很简单,在Simulator的系统菜单中,取消勾选:
Hardware
–> Keyboard
–> Connect Hardware Keyboard
。
取消选中之后,键盘可以正常弹出,但是,无法使用硬件的键盘输入了,自己慢慢用鼠标点击模拟器里的键盘吧……
也许你键盘能正常显示了,但是一显示就崩溃,在堆栈中有这样的字样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
以上崩溃也只在XCode 6,iOS 8出现,在iOS 7一切正常。
原因可能是你设置了UITextField
或UITextView
的inputView
或inputAccessoryView
为customView,比如这样:
1 2 3 4 5 6 7 8 9 10 11 |
|
上述代码在iOS 8中是必定Crash的,因为UITextView.inputAccessoryView
不能是其他View的子View。而上面的switchKeyboardBar
先被添加到了当前UIViewController
的View
中,再被设置到inputAccessoryView
,就会导致崩溃。
不从Xib创建自定义View,而是在代码中手动创建,并且不添加到别的View中。
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/10/24/xcode-6-dot-1-iphone-simulator-8-dot-1-keyboard-issue/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
虽然自iOS6之后,苹果推荐我们使用Autolayout布局,并且在Xib和Storyboard中默认帮我们打开了这个选项,但是在开发过程中,我们偏向于使用Autosizing
,并且手动取消掉Autolayout
,原因在于,Autolayout太繁琐复杂,而Autosizing简单并且能满足大部分的需求。
当父视图被拉伸的时候,子视图能够适配父视图的新大小。其原理是,子视图有一个masks,用于指定与父视图上下左右边缘的距离,以及自身宽高的关系。
比如,指定子视图的右边缘紧跟着父视图的右边缘,那么父视图变大之后,子视图还是贴在父视图的右边。
这在大部分简单布局情况下非常有效。
使用Autosizing,有一个前提,就是子视图的Frame是固定的,至少宽高是固定的,或者跟随着父视图的Frame变化。但是,如果希望多个子视图与父视图的边距固定,大小自动调整,Autosizing就爱莫能助了。
原因在于:Autosizing无法智能计算多个子View各自的Frame。
比如,我们希望在竖屏下布局是这样:
并且在横屏下布局是这样:
除了手写代码调整Frame,单独用Autosizing是无法做到的。这时候就需要借助强大的Autolayout了。
Autolayout使用约束来决定每个View的坐标、大小,约束可以针对SuperView,也可以针对其他任意一个SubView。
使用自动布局,你可以表达出视图与视图之间的关系,而不是明确地指定每个视图的Frame。通过约束,视图会自动计算它们应该呆在哪个位置,只要约束足够多,它们也能自动计算自己的大小。
只要指定了约束,无论屏幕大小怎么变化,它们都能自适应,这就是Autolayout的优点:妈妈再也不用担心你手写布局代码啦!也不用担心你为了适配各种屏幕大小而加班了。
Autolayout唯一的缺点就在于:过于复杂,较难上手。
取决于项目需求。如果Autosizing完全能满足开发需求,那么就没必要使用复杂的Autolayout。但是,如果你已经被适配各种屏幕大小折腾得不成人形了,那么就要早日投入到Autolayout的怀抱了。
首先要改变自己对布局的思考方式。你应该忘掉Frame,需要考虑的是subView A与subView B的上下左右的关系,以及与superView的关系。
在Xcode5之后,苹果已经尽力让开发者能更方便地使用Autolayout了。
通过Xcode
–>Editor
–>Pin/Align
菜单为视图添加约束即可。
在XCode中除了通过菜单,还可以通过可视化的方式添加约束:
如果你添加的约束不足以表达某个View的位置大小,XCode还会以黄色的辅助线发出警告,十分好用。
XCode虽然强大,但是有时候我们也许希望借助代码来写Constraint。
加入你希望一个子view跟随父view的大小,但是与边距有10个点的距离:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
每个约束都是这样的长长一串代码,设想一下需要添加6个约束的话……
幸好有了这个开源库:Masonry。
使用这个库,代码添加约束就可以简介如下:
1 2 3 |
|
比如,我们自己实现了一个图文混排的TextView,添加到Xib时我们还不知道其高度,需要在代码中计算,那么就需要在代码里更新约束,如:
1 2 3 4 5 6 7 8 9 10 |
|
多说无益,贵在实践。只要有意识地去使用了一次,自然就会了。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/10/17/ios-autolayout-vs-autosizing/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
在Windows下,最好用的SVN客户端是TortoiseSVN,但是没有跨平台版本。在Mac OS X,也有好几个SVN客户端,比如:Versions,CornerStone,SmartSVN,其中,最好用的是SmartSVN。
SmartSVN有两个版本,专业版和基础版。专业版可以免费试用30天,如果不注册,自动切换为基础版。最重要的是,基础版已经涵盖了大部分的功能特性,实乃业界良心。
话说我一直以来在Mac系统都用着SmartSVN 7.5.5版,顺心顺手,直到昨晚,update的时候一直是Local Refreshing
状态,数据也是有出没进,如下图:
Out
的数据一直在增加,如果放任不管,甚至可以达到几百M。于是我意识到出问题了,但是用命令行的svn来操作却一切正常。怀疑是SmartSVN的Log Cache
太大,于是删除了Log Cache
文件,位置在~/Library/Preferences/SmartSVN
下,问题依旧。
无奈,只好使出重装大法。下载、安装了SmartSVN 8.6版本后,直接弹出了下面的提示:
实在是后知后觉啊,既然你那么Smart,就不能在出问题的时候直接弹提示吗?非得等到人家摸索并安装了最新版后才弹这个。
好消息是,安装8.6版本后,问题解决。
SmartSVN更新不了,问题不在于客户端本身,而是svn server端禁用了SSLv3引起的,为了避免这个问题: https://poodle.io/
至于为什么更新为SmartSVN 8.6之后,问题解决,是因为8.6版本内置的是svn1.8,在SSLv3无法连接的时候,可能自动切换为tlsv模式。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/10/17/smartsvn-keep-local-refreshing-issue/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
1 2 3 4 |
|
逻辑很简单,就是代码繁琐了点,设想一下,假如有5个以上的子View需要调整位置与大小……
也很简单,就是封装为通用的宏,或者方法(有些人比较反感C语言样式的宏定义)。
1 2 3 4 5 6 7 |
|
然后,就可以这样调整UIView的Frame了:
1 2 |
|
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/10/15/ios-adjust-view-frame-quickly-with-macro/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
下面罗列一下我在适配iOS8过程中遇到的兼容问题——而同样的代码在iOS6/7是完全没问题的。
在App中,第一个ViewController是只支持竖屏方向(Portrait)的,切换到第二个页面,默认也是Portrait,但用户可以点击按钮切换为横屏(Landscape)。
所以,我把App-Info.plist只选中了Portrait
一项,并且在Root ViewController
重载了以下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
然后在需要切换到横屏的时候,调用以下代码:
1 2 3 4 5 |
|
由于在iOS6、7中,键盘方向是跟随状态栏方向的,所以一切表现正常,横屏下,无论设备方向怎样,键盘都是横着弹出。
但是,在iOS8中,键盘却随着设备方向弹出了。换言之,即使Interface Orientation为Landscape,但Device Orientation为Portrait,键盘就会以Portrait的方向弹出。
如下图:
我想要的是键盘不管设备方向,只关注状态栏方向,也就是StatusBarOrientation。由于iOS8新出不久,在网上没找到解决方案。后来我一同事发现手动更改设备方向可以解决这一问题:
1 2 3 4 5 6 7 8 9 |
|
本来,防止屏幕锁定只需一句代码:
1
|
|
但是,在iOS8中,偶尔会失效。测试人员发现了这个问题是在弹出键盘点击发送后必现,而原因不明。因为键盘出现与隐藏,理论上不应该影响这个idleTimerDisabled
的属性。暂且认为是iOS8的Bug吧。
在键盘收起的时候,重设IdleTimerDisabled
。
1 2 3 4 5 6 |
|
在iOS中使用OpenGL ES渲染,切换到后台时需要停止渲染,否则会引起崩溃。见这里:How to fix OpenGL ES application crashes when moving to the background
但是在iOS8中,即使不是切换到后台,而是通过NavigationController
切换到另一个ViewController再切回来,也会引起崩溃,崩溃点在:
1
|
|
猜测可能是,在iOS 8中,如果OpenGL的视图如果切换到不可见的ViewController,也需要停止绘制,否则也会引起在后台渲染OpenGL类似的崩溃。
在ViewWillDisapper的时候停止渲染。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
iOS的兼容真是个蛋疼的问题。
原文作者: lslin]]>
原文链接: http://blog.lessfun.com/blog/2014/09/24/ios8-issue-keyboard-orientation-and-idletimerdisabled-not-working/
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0