7 月活动,关注公众号抽奖
又到了我们每月一次的活动日了。今天我们还是抽奖,送出 10 枚 MWeb for Mac 的兑换码。MWeb 是一款专业的 Markdown 写作、记笔记、静态博客生成软件,功能相当强大,相信用过的童鞋都知道。作者 oulvhai
,目前是独立开发者。这里感谢 oulvhai 为我们提供的兑换码。
MWeb 相关的介绍大家可以查看官网,我们这次的兑换码是非 Mac App Store 版,还请中奖的小伙伴在官网下载试用版,然后激活即可。
上周内容回顾
上周公众号发布的以下文章:
本期知识小集的主要内容包括:
- 一个关于 GCD 分组任务的小 tip
- 一次内存泄漏后的思考
- UIAlertView 与输入框结合使用时的一个坑
- 提高 Shortcuts 调试效率的小技巧
- Xcode 断点调试时打印变量值报错的问题(编译优化相关)
- Watch OS 4 新 API 解决手表自动休眠问题
一个关于 GCD 分组任务的小 tip
作者: halohily
在项目中看到一段使用 GCD group 处理的代码,简化下来大概如下图
dispatch_group_notify
的调用放在了 dispatch_group_async
的 block 中,乍一看会有是否产生永久阻塞的疑问,因为子任务完成后的派发任务被放在了一个子任务中。然而其实这是不会阻塞的,代码会按编写人的预期进行执行,即 log1 输出之后,输出 log2。这是因为 dispatch_group_notify 的 block 是异步执行的。
再举个例子,如下图,执行结果依次会是:log 1,log 2 ,log 4 ,log 5 ,log 3。
虽然此处结果正确,但这种将 dispatch_group_notify 的调用放在某一个子任务的执行块中的写法是不被推荐的,它不但反逻辑,而且并不总能保证结果正确。比如如果在此例中,在调用了 dispatch_group_notify 的子任务之后,又为该任务组使用 dispatch_group_async 语句添加后续子任务,这时代码的执行结果是不确定的。
既然最开始的例子中执行结果是正确的,有的同学会问,如果把 dispatch_group_notify 的调用放在所有子任务的最前面,如下图,是否也能获得预期的结果呢?答案是否定的,因为在最开始调用 dispatch_group_notify 时,子任务数量为0,它的代码块会立即执行。而后为该组派发了多个子任务,当这些子任务都执行完毕后,也并不会再次触发 dispatch_group_notify 的代码块。
一次内存泄漏后的思考
作者: Lefe_x
最近项目中遇到一个内存泄漏的问题,SecondViewController 这个类在 pop 后并没有执行 dealloc 方法,也就没有被正常被释放。使用内存泄漏工具排查,并没有发现有循环引用的地方,手动查了一下也没发现异常。正在迷茫的时候,突然看到了一个注册监听的地方。实现方式类似下面这样:
- (void)dealloc {
[[Manager sharedInstance] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[[Manager sharedInstance] addObserver:self];
}
复制代码
看到这里你应该已经猜到 SecondViewController 为什么没被释放,它被 Manager 持有了,而 Manager 是一个单例,自然 SecondViewController 也不会被释放,dealloc 方法也不会执行。
这种设计很常见,往往给某个服务注册监听,达到类似通知的效果。如果使用数组保存监听者,监听者将会被数组持有。有同学可能说,可以在 viewDidAppear 注册,在 viewWillDisappear 移除,这样 SecondViewController 就会被释放。但是,这样设计很糟糕,我们尽量不去约束调用者如何调用某个 API。
其实正确的做法是使用一个弱引用容器,我们可以使用 NSHashTable 来保存监听者,这样当监听者释放后,将自动从 NSHashTable 中移除,也不需要主动调用移除监听者的方法(也可以调用,视情况而定)。下面是一个简单的实现,你也可以参考 YYTextKeyboardManager 的实现:
_listenerTable = [NSHashTable weakObjectsHashTable];
- (void)addObserver:(NSObject *)obj {
[self.listenerTable addObject:obj];
}
- (void)removeObserver:(NSObject *)obj {
[self.listenerTable removeObject:obj];
}
复制代码
UIAlertView 与输入框结合使用时的一个坑
作者: Vong_HUST
相信 UIAlertView
大家应该都很熟悉,但是最近遇到一个坑。
由于历史原因,项目中还在大量使用 UIAlertView。某天测试过来反馈说,评论框字符长度超过最大长度时,点击发送,弹出一个 alert
提示,点击确定后,评论框无法在被激活,也就是没法弹出键盘了。很是怪异,debug
无果,搜了一下 stackoverflow
,发现有人遇到过类似的问题,可以点击末尾的参考链接来查看具体详情。
他给出的解决方案就是把这种情况下的 UIAlertView 换成 UIAlertController。试了下这种方式,果然是可行的,由于之前 UIAlertView 是不依赖其它视图层级的,创建后直接 show
就可以了,所以很多地方直接写在了非视图控制器类中。在换成 UIAlertController 之后,由于它是继承自 UIViewController 的,所以必须要有 VC 把它 present
起来。解决方案也很简单,写一个 UIViewController 的分类获取当前顶部可见的 ViewController,然后在上面 present
出 UIAlertController 即可,获取顶部可见 ViewController 的代码随便一搜就可以找到,这边就不贴了。
PS:UIAlertController 是 iOS8 以后才提供的,不过相信大家也不用适配 iOS8 之前的系统了吧?。如果还要适配,那就只能做版本区分了。。。
参考
iOS 9 - Keyboard pops up after UIAlertView dismissed
提高Shortcuts调试效率的小技巧
作者: 高老师很忙
iOS12提供了 Shortcuts
的功能,今天给大家介绍 2 个苹果提供的提高 Shortcuts 调试效率的小技巧。
- Shortcuts 是可以通过 Siri 唤起的,如果每次调试的时候都要和 Siri 说一次短语,既浪费时间又打扰旁边正在工作的同事,Xcode 提供了 Siri Intent Query 功能,在调试 Intents Extension 或者 Intens UI Extension 的 target 时,直接在里面输入你要说的短语,就可去省去调戏 Siri 的时间啦。
- 在
iPhone设置
->开发者
里面提供了 Display Recent Shorts 和 Display Donations on Lock Screen 的开关,可以忽略系统当前的建议和预测,显示我们需要调试的 Shortcuts;同时还支持 Force Sync Shortcuts to Watch 的功能,手动去强制同步到 Watch,会节省很多时间哦!
我觉得这2个小技巧还是很实用的,如果有其它小技巧欢迎一起交流!
Xcode 断点调试时打印变量值报错的问题(编译优化相关)
作者: KANGZUBIN
在日常开发中,我们经常会在 Debug 模式下打断点进行调试,并通过 LLDB 的 po
命令在控制台打印一些变量的值,以方便排查问题。
今天在 Release 模式下编译运行项目,发现要打印某一变量的值时(po xxx
),报如下错误:
error: Couldn't materialize: couldn't get the value of variable xxx: no location, value may have been optimized out
error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression
复制代码
大致意思是说,xxx
的值不存在,可能已经被编译优化了。而且在断点模式下当我们把鼠标的箭头移到某一变量上要进行快速浏览时,发现它们的值都是 nil
。
查了一下才发现,原来这与 Xcode 工程的编译选项 Optimization Level
设置有关,它是指编译器的优化级别,优化后的代码效率比较高,但是可读性比较差,且编译时间更长,它有 6 个选项值如下图:
上述每选项值的详细说明可以参考《Xcode 中 Optimization Level 的设置》和《如何加快编译速度》两篇文章,我们这里不再赘述。
Xcode 工程的 Optimization Level 值在 Debug 模式下默认为 None [-O0]
,表示编译器不会尝试优化代码,保证调试时输出期望的结果;而在 Release 模式下默认为 Fastest, Smallest[-Os]
,表示编译器将执行所有优化,且不会增加代码的长度,它是可执行文件占用更少内存的首选方案。
这也是为什么我们在 Release 模式下断点打印变量会报错,因为编译器已经给代码做了优化,它将不在调试时记录变量的值了。
此外,有时候遇到一些线上 Bug 但是在 Debug 调试时却无法复现,我猜有可能会跟编译优化有关,你觉得呢?欢迎留言讨论。
Watch OS 4 新 API 解决手表自动休眠问题
作者: 我是乔忘记疯狂
Watch OS 4 新的 API,用于解决手腕转动时手表屏幕可能自动休眠的问题。比如在 watch 上展示二维码给各种 POS 扫描,屏幕经常会自动休眠。经测支付宝 watch app 已经使用了此 API,不过官方推荐只针对某些特定界面打开此功能,而支付宝是整个 watch app 都打开了此功能。