美文网首页
iOS知识小集 第7期(2016.08.31)

iOS知识小集 第7期(2016.08.31)

作者: kakukeme | 来源:发表于2016-11-24 16:01 被阅读34次

    好久没写这个系列了,一看都快一年了,当时说好的呢?嗯,说来总是有各种借口,所以还是不说,直接开始新的一期。之前在微博上发了不少知识小集,到现在应该有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");
    }
    

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

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

    imageimage

    发现程序是卡在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。

    imageimage

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

    小结

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

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


    南峰子技术博客

    相关文章

      网友评论

          本文标题:iOS知识小集 第7期(2016.08.31)

          本文链接:https://www.haomeiwen.com/subject/gtgbpttx.html