iOS知识小集 第7期(2016.08.31)

好久没写这个系列了,一看都快一年了,当时说好的呢?嗯,说来总是有各种借口,所以还是不说,直接开始新的一期。之前在微博上发了不少知识小集,到现在应该有50多条了,都是平时开发遇到的一些问题,或者看书,看文档,看博客,看WWDC的一些笔记,分享出来。所以在这偷个懒,做一个合集,每期把微博上的知识小集集中一下,可别吐槽。

本期主要收集以下几个小问题:

  1. UIImageView显示gif图片有两种方式
  2. Objective-C中的BOOL类型
  3. dispatch_once死锁
  4. GNU 复合语句
  5. URL转义

UIImageView显示gif图片有两种方式

UIImageView显示gif图片有两种方式。当然前提都是先将gif中的每一帧取出来放到一个个UIImage对象中,将这些对象放到一个数组中,如下代码所示。

CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);

NSMutableArray *images = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
   CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
   [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen] orientation:UIImageOrientationUp]];
   CGImageRelease(image);
}

CFRelease(source)

一种方式是将这些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的类型串:

@encode(BOOL)       // 64位iOS系统:"B"
@encode(BOOL)       // 32位iOS系统,32/64位OS X:"c"

所有这边有一个问题,下面这段代码中变量b的值在不同环境下,其结果可能是不一样的:

BOOL a = 100 & 20;
BOOL b = (a == YES);

当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()方法,会有什么结果呢?

void test() {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        test();
    });

    printf("This is a test");
}

死锁。是的,死锁,线程直接卡住了。为什么呢?

我们暂停程序,可以看到程序的调用栈,如下图所示:

image
image

发现程序是卡在dispatch_once_f中。研究一下dispatch_once_f的实现吧,如下代码所示,会发现一些有意思的东西。

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    struct _dispatch_once_waiter_s * volatile *vval =
            (struct _dispatch_once_waiter_s**)val;
    struct _dispatch_once_waiter_s dow = { NULL, 0 };
    struct _dispatch_once_waiter_s *tail, *tmp;
    _dispatch_thread_semaphore_t sema;
    if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
        dispatch_atomic_acquire_barrier();
        _dispatch_client_callout(ctxt, func);
        dispatch_atomic_maximally_synchronizing_barrier();
        //dispatch_atomic_release_barrier(); // assumed contained in above
        tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
        tail = &dow;
        while (tail != tmp) {
            while (!tmp->dow_next) {
                _dispatch_hardware_pause();
            }
            sema = tmp->dow_sema;
            tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
            _dispatch_thread_semaphore_signal(sema);
        }
    } else {
        dow.dow_sema = _dispatch_get_thread_semaphore();
        for (;;) {
            tmp = *vval;
            if (tmp == DISPATCH_ONCE_DONE) {
                break;
            }
            dispatch_atomic_store_barrier();
            if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
                dow.dow_next = tmp;
                _dispatch_thread_semaphore_wait(dow.dow_sema);
            }
        }
        _dispatch_put_thread_semaphore(dow.dow_sema);
    }
}

简单描述一下吧。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做递归调用,不管是直接的还是间接的。

再说回单例,个人看法是单例是个好东西,但应该在适当的场景使用。不能因为简便就滥用。抛开内存问题不说,使用不当的话,单例类迟早会变成一个垃圾场。

参考

  1. Why am I getting deadlock with dispatch_once?
  2. 滥用单例之dispatch_once死锁
  3. libdispatch

GNU 复合语句

我们在看一些第三方的代码时,可能会看到类似于下面的代码。

[self.view addSubview:({
   UIView *view = [[UIView alloc] initWithFrame:(CGRect){CGPointZero, 100.0f, 100.0f}];
   view.backgroundColor = [UIColor blueColor];
   view.layer.masksToBounds = YES;
   view.layer.cornerRadius = 4.0f;
   view;
})];

addSubview的参数放在一个”({})”代码块中,而view的创建及属性设置都是在”({})”完成,代码块最后一句即我们要添加的子view。

这种写法沿用了GNU C的一个特性,即复合语句(compound statement)。即在”({})”代码块中,我们可以放置多个语句,这些语句可以是循环、分支、变量声明、函数调用等。而复合语句的最后一句是一个表达式,其作为整个复合语句的最终值。

在写Objective-C代码时,使用复合语句能让我们的代码变得更优雅,特别是创建并添加一堆子view时,能让我们的代码看上去更整洁。建议经常使用。

参考

  1. Statements and Declarations in Expressions

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。

image
image

我们所需要做的就是对不符合标准的字符串进行转义操作。NSString类提供了两个方法来做这种转义操作,一个是-stringByAddingPercentEscapesUsingEncoding:,不过这个方法在iOS 9.0已被废弃;现在更提倡的是用-stringByAddingPercentEncodingWithAllowedCharacters:方法,这个方法是iOS 7.0后添加的。

小结

知识是一点一点积累的,每天一两点,一段时间后,收获也会很大。知识小集的初衷就是这样。

当然,另一方面也需要系统性地去学习整理一些知识,才能把零零碎碎的东西串起来。


南峰子技术博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容