因为作者的疏忽,之前的 GCD源码分析之base.h 一文中,遗漏了一个知识点。今天特地更新了原文并单发一篇文章讲解,希望能够和更多的开发者一起分享知识。
sentinel
sentinel
的中文翻译是哨兵。
经常接触底层库开发的程序员可能会经常遇到它,在计算机的世界中,它是一个表示程序开始或结束的符号(常见的值为 NULL
)。
iOS 开发者可以通过在 objc4-NSObject.mm 页面中搜索
POOL_SENTINEL
来查看其用法。POOL_SENTINEL
的作用是用来当做不同autorelease pool
的边界。
当 attribute 和 sentinel 放到一起时,它可以起到通知编译器:当接收可变参数时,NULL
所在的位置。
下面,我们通过几个具体的例子来查看它的用法。
上面的例子中,所有的函数参数都分为两部分:指针 + 可变参数列表。
__attribute__ ((sentinel))
__attribute__ ((sentinel(0)))
__attribute__ ((sentinel(0, 0)))
三种写法的作用是一样的,它们表示可变参数中,从后朝前数,第0个参数为 NULL
。
__attribute__ ((sentinel(2)))
和 __attribute__ ((sentinel(2, 0)))
的作用是一样的,它们表示可变参数中,从后朝前数,第2个参数为 NULL
小结1:__attribute__ ((sentinel(n)))
限制了可变参数的参数数量至少为 n+1
细心的读者已经注意到,最后一个函数的描述符的写法是 __attribute__ ((sentinel(2, 1)));
。它和上一个描述符的区别是由 0
变为了 1
。调用该函数时,可变参数列表部分没有 NULL
。
实际上,在 sentinel
的相关用法中,开发者可以通过设置第二个参数为 1
的方式告诉编译器,可变参数列表前面的参数也当做可变参数列表的一部分。这也意味着,当前面的参数为NULL
时,即使参数个数为 2
,没有满足 n+1
,也是合法的行为。
小结2:__attribute__ ((sentinel(n,1)))
意味着可变参数的参数数量在前一个参数为NULL
的情况下,可以为 n
个。
修饰符指定了从后往前数第三个为 NULL
,如果不满足这个规则,编译器会产生警告️。
sentinel(n, 0) 和 sentinel(n, 1)
sentinel(n, 0) 和 sentinel(n, 1) 之间的区别是处理变长参数前面最后一个被命名参数的方式不同。比如,下面示例中的 void* a
。sentinel(0, 0) 意味着该参数 不被包含到空值中断列表中。sentinel(0,1) 意味着该参数被包含到空值中断列表中。
在下面的示例中,第二个函数使用 sentinel(0, 1)
进行修饰,用容易理解的读法就是,可变列表中,最后一个 NULL
后面有 0
个变量,该 NULL
值可以出现在 void *a
的位置也可以。
而第一个函数使用 sentinel(0, 0)
进行修饰,用容易理解的读法就是, void *a
后面的可变列表中,最后一个 NULL
后面有 0
个变量。因为调用函数时,只传了一个 NULL
,参数,该参数只满足了对 void *a
的赋值,却不满足从后往前数第0个为 NULL
,所以会产生可变参数不够的警告️。