好久没写这个系列了,一看都快一年了,当时说好的呢?嗯,说来总是有各种借口,所以还是不说,直接开始新的一期。之前在微博上发了不少知识小集,到现在应该有50多条了,都是平时开发遇到的一些问题,或者看书,看文档,看博客,看WWDC的一些笔记,分享出来。所以在这偷个懒,做一个合集,每期把微博上的知识小集集中一下,可别吐槽。
本期主要收集以下几个小问题:
- UIImageView显示gif图片有两种方式
- Objective-C中的BOOL类型
- dispatch_once死锁
- GNU 复合语句
- URL转义
UIImageView显示gif图片有两种方式
UIImageView显示gif图片有两种方式。当然前提都是先将gif中的每一帧取出来放到一个个UIImage对象中,将这些对象放到一个数组中,如下代码所示。
|
|
一种方式是将这些UIImage对象通过UIImage的类方法+animatedImageWithImages:duration:
组合成一个UIImage对象,然后赋值给UIImageView对象的image属性。
第二种方式是将UIImage对象的数组赋值给UIImageView对象的animationImages
属性,然后调用UIImageView对象的startAnimating
方法来启动动画。
当然,两种方式都需要计算duration
。
Objective-C中的BOOL类型
Objective-C中的BOOL类型在Watch和64位iOS上的原始类型为bool,而在其它情况下是signed char
。我们用@encode
去看看BOOL的类型串:
|
|
所有这边有一个问题,下面这段代码中变量b的值在不同环境下,其结果可能是不一样的:
|
|
当BOOL为bool时,b的值为1;而当BOOL为signed char
时,b的值为0。所以,如果我们判断一个BOOL值是否为真时,不应该通过if(a == YES)
这种方式来判断,要么直接就if (a)
,要么就if (a != NO)
。
dispatch_once死锁
在iOS开发中,我们经常会使用到单例,现在Objective-C中写单例的标配是使用dispatch_once
。相信这个函数的意义大家都非常清楚了,就是希望dispatch_once
参数中的block在全局只执行一次。这个基本上没什么问题。
不过,今天在工程中看到类似于下面这样的代码。在主线程中调用test()
方法,会有什么结果呢?
|
|
死锁。是的,死锁,线程直接卡住了。为什么呢?
我们暂停程序,可以看到程序的调用栈,如下图所示:
发现程序是卡在dispatch_once_f
中。研究一下dispatch_once_f
的实现吧,如下代码所示,会发现一些有意思的东西。
|
|
简单描述一下吧。onceToken
在第一次执行block之前,其值将由NULL变为指向第一个调用者的指针(&dow
)。如果在block
完成之前,有其它的调用者进来,则会把这些调用者放到一个waiter
链表中(走else分支),直到block
执行完成。waiter
链中的每个调用者都会等待一个信号量(dow.dow_sema
)。在block执行完成后,除了将onceToken
置为DISPATCH_ONCE_DONE
外,还会去遍历waiter
链中的所有waiter
,抛出相应的信号量,以告知waiter
们调用结束。
因此上面的死锁问题就好理解了。递归调用test()
时,第二次调用作为一个waiter
,在等待block完成,而block的完成依赖于test()
的执行完成,这就成了一个死锁。
所以应该避免在dispatch_once
做递归调用,不管是直接的还是间接的。
再说回单例,个人看法是单例是个好东西,但应该在适当的场景使用。不能因为简便就滥用。抛开内存问题不说,使用不当的话,单例类迟早会变成一个垃圾场。
参考
GNU 复合语句
我们在看一些第三方的代码时,可能会看到类似于下面的代码。
|
|
addSubview
的参数放在一个”({})”代码块中,而view的创建及属性设置都是在”({})”完成,代码块最后一句即我们要添加的子view。
这种写法沿用了GNU C
的一个特性,即复合语句(compound statement
)。即在”({})”代码块中,我们可以放置多个语句,这些语句可以是循环、分支、变量声明、函数调用等。而复合语句的最后一句是一个表达式,其作为整个复合语句的最终值。
在写Objective-C代码时,使用复合语句能让我们的代码变得更优雅,特别是创建并添加一堆子view时,能让我们的代码看上去更整洁。建议经常使用。
参考
URL转义
在使用+URLWithString:
或-initWithString:
来创建一个URL对象时,提供的参数字符串必须符合RFC 2396标准Uniform Resource Identifiers (URI): Generic Syntax。而这两个方法又是根据RFC 1738 Uniform Resource Locators (URL)和1808 Relative Uniform Resource Locators两个标准来解析字符串的。故弄玄虚一下。当然我们不需要去了解所有的细节,简单了解一下就行,可以参考一下阮大侠的这篇关于URL编码。
这里要说明的就是:对于我们而言,如果用带有中文的字符串(如”https://www.baidu.com?q=北京“)去创建一个URL对象的话,返回的是一个nil。
我们所需要做的就是对不符合标准的字符串进行转义操作。NSString
类提供了两个方法来做这种转义操作,一个是-stringByAddingPercentEscapesUsingEncoding:
,不过这个方法在iOS 9.0已被废弃;现在更提倡的是用-stringByAddingPercentEncodingWithAllowedCharacters:
方法,这个方法是iOS 7.0后添加的。
小结
知识是一点一点积累的,每天一两点,一段时间后,收获也会很大。知识小集的初衷就是这样。
当然,另一方面也需要系统性地去学习整理一些知识,才能把零零碎碎的东西串起来。
欢迎关注我的微信公众号:iOS知识小集,扫扫左边站点概览里的二维码就OK了。对了,还有微博:@南峰子_老驴。