问题已经11年了,
Rob's answer没有任何问题,但我觉得不幸的是,它的核心部分(关于使用协议转换
Class
对象是否正确)从未得到适当的关注。
首先,在Objective-C中静态类型很虚假,它存在的唯一目的是让编译器发出警告(甚至不是错误)。让我们从
Class
对象的真正含义开始 - 如果您查看
文档,您会发现
Class
类型实际上是
objc_class *
类型的别名:
typedef struct objc_class *Class;
您可以在Apple的objc运行时库源代码中找到objc_class
类型的定义:
struct objc_class : objc_object { ... }
正如您所看到的,objc_class
只是objc_object
的扩展。任何Objective-C类实际上都是这个objc_object
的实例。例如,下面是NSObject
或id
别名的样子:
typedef struct objc_object NSObject;
typedef struct objc_object *id;
这意味着 Objective-C 中不存在“静态类型”,一个实例的“类型”是通过给定实例的内省(不同种类的元信息 objc_object
存储)来发生的。这使得所有的 Objective-C 类彼此兼容(首先 - 因为它是一个指针,其次 - 因为它是指向相同结构的指针)。例如,您可以编写如下代码:
Class obj = [NSObject new];
..然后它会愉快地编译。
然而,这种语言的纯动态性使其非常容易出错,暴露了程序员可能犯的各种错误。为了避免这种情况,clang实际上对指定类型进行编译时检查,但它完全依赖于程序员提供正确的数据类型,如果从Objective-C的角度来看类型不兼容,则编译器可以为您发出警告。这适用于实例对象,但不幸的是,在Objective-C中没有语法来为类对象打类型,除了使用Class
别名。这意味着对于编译器,在编译时所有这些对象都是无法区分的。
并且这一点也适用于协议类型。在这里,我的意思是当您将协议符合标记添加到变量类型(id<TDWLoadable> var
)时,您只是要求编译器检查所分配给变量的对象是否符合给定的协议:
@protocol TDWLoadable
+ (void)classMethod;
- (void)instanceMethod;
@end
@interface TDWObject : NSObject
@end
id<TDWLoadable> loadable = [TDWObject new];
对于类对象,相同的检查被忽略了,因为 Class
对象无法被类型化:
Class<TDWLoadable> loadable = [[TDWObject new] class];
这种行为在
《Objective-C编程语言》中协议部分的类型检查章节中有所描述(重点强调)。
...the declaration
id <Formatting> anObject;
groups all objects that conform to the Formatting protocol into a type, regardless of their positions in the class hierarchy. The compiler can make sure only objects that conform to the protocol are assigned to the type.
In each case, the type groups similar objects—either because they share a common inheritance, or because they converge on a common set of methods.
The two types can be combined in a single declaration:
Formatter <Formatting> *anObject;
Protocols can’t be used to type class objects. Only instances can be statically typed to a protocol, just as only instances can be statically typed to a class. (However, at runtime, both classes and instances respond to a conformsToProtocol:
message.)
此外,如果我们考虑到
objc_class
实际上只是
objc_object
的扩展,那么以下两个表达式:
Class<TDWLoadable> classObj;
id<TDWLoadable> obj;
应该遵循相同的约定(即
+(void)classMethod
必须引用
classObj
的元类,而
-(void)instanceMethod
则引用类对象本身)。
话虽如此,由于语法本质上没有影响并且被编译器忽略,因此您可以自由地提出自己的约定来进行 Class<Protocol>
类型。
@interface MyClass <MyProtocol>
或在.m中使用@interface MyClass () <MyProtocol>
)。然后,编译器将允许使用该协议的方法进行自动完成,而无需转换。通常最好修复对象或类定义,而不是强制转换它。另外,通过将类定义为符合特定协议,编译器还会警告您是否未实现某些所需方法的协议。 - Robnil
的arg
与传递FALSE
相同),其次你没有从此方法中返回任何内容。也许您正在使用旧版本的编译器,该编译器不会生成警告,或者您已关闭某些警告。如果arg
是id
或指针,则您的构造是有意义的,但如果是BOOL
则不是。另外,您没有返回instance
。当我编译您的代码时,我会收到这两个警告。 - Rob