0

当您@protocol(SomeProtocol)作为参数传递给方法时,结果指针是否可以被认为具有静态存储持续时间?

现在考虑到协议是在编译时在 .h 文件中定义的,这是否意味着它的指针在程序的整个生命周期中都是相同的,并且在某种意义上可以安全地称为静态在运行时?

4

1 回答 1

2

TL;DR:是的,但有一些警告。

关于指针是什么存在一些非常明显的混淆Protocol,因此我将尝试澄清有关此的任何混淆。

在过去的 ObjC 时代,协议根本不存在。正因为如此,当 NeXT 的 API 需要多重继承时,他们将其“破解”为一种语言作为自己的类,并用特殊语法修改编译器以接受这一点。

它运行良好,直到 ObjC2 推出,协议将作为官方语言(和运行时 API)功能添加,但这导致了一些向后兼容性问题,因为我们已经有一个Protocol由 NeXT 定义的类.

出现的解决方案是保留 Protocol 类,但弃用它。因此,从技术上讲,我们仍然在 runtimeProtocol中保留了类,但是在 ObjC2 中没有一个方法可以工作(实际上有很多不同的旧结构,例如选择器,以及其他一些小东西,但是我离题了)。forward::objc_msgSendv

你不应该(读不能)向这个类发送消息——虽然它在技术上仍然是一个对象,但你不应该期望它符合你的应用程序中存在的其他对象的相同约定(协议?)。

那么解决方案就是在运行时中使用前缀为 的函数protocol_,例如:

  • protocol_getName()而不是-name.
  • protocol_conformsToProtocol()代替-conformsTo:

等等

请注意,在协议类的当前实现下,这些方法实际上仍然可以被调用,并且只是对它们的 C 函数对应物的填充。

话虽如此,如果您的所有协议都是通过 using 获得的objc_getProtocol,那么它们在当前版本的运行时保证具有“静态”存储,正如我们从实现中看到的那样:

/***********************************************************************
* objc_getProtocol
* Get a protocol by name, or return nil
* Locking: read-locks runtimeLock
**********************************************************************/
Protocol *objc_getProtocol(const char *name)
{
    rwlock_read(&runtimeLock); 
    Protocol *result = (Protocol *)NXMapGet(protocols(), name);
    rwlock_unlock_read(&runtimeLock);
    return result;
}

不制作协议副本,并且每次后续调用都将返回相同的指针。这也适用于使用@protocol(name)表达式,它本质上(虽然不完全是,有一些其他的编译器魔术完成)被认为是对objc_getProtocol.

现在 - 然而,理论上有人可以故意创建协议“对象”的副本,因为结构非常简单,如下所述

struct protocol_t : objc_object {
    const char *name;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    const char **extendedMethodTypes;
};

(请注意,这是一个模仿objective-c 对象的C++ 结构,如果您尝试自己模拟它可能会导致一些困难)。

memcpy这里要注意的重要一点是,只需对运行时提供给您的协议指针执行一个操作就会引起很少的问题,因此,您应该始终使用protocol_isEqual()用于比较协议,这将检查协议的字段确保它们实际上是等价的,即使它们有不同的指针。

然而,简单地将 aProtocol *视为静态是完全可以的,并且是在代码中引用协议的正确方法。

但是请注意,与任何运行时功能一样,这一切都可能会随着 API、编译器、ABI、目标架构和其他内容的另一个版本而改变,因此请务必阅读最新的信息学科!

于 2014-04-12T07:24:50.273 回答