关于Objective-C范畴和扩展的细节

时间:2022-09-07 10:50:38

I learned something new while trying to figure out why my readwrite property declared in a private Category wasn't generating a setter. It was because my Category was named:

当我试图弄清楚为什么在私有类别中声明的readwrite属性没有生成setter时,我学到了一些新东西。因为我的类别被命名为:

// .m
@interface MyClass (private)
@property (readwrite, copy) NSArray* myProperty;
@end

Changing it to:

改变:

// .m
@interface MyClass ()
@property (readwrite, copy) NSArray* myProperty;
@end

and my setter is synthesized. I now know that Class Extension is not just another name for an anonymous Category. Leaving a Category unnamed causes it to morph into a different beast: one that now gives compile-time method implementation enforcement and allows you to add ivars. I now understand the general philosophies underlying each of these: Categories are generally used to add methods to any class at runtime, and Class Extensions are generally used to enforce private API implementation and add ivars. I accept this.

我的setter是合成的。我现在知道,类扩展不仅仅是匿名类别的另一个名称。留下一个未命名的类别会使它变成另一个野兽:一个现在提供编译时方法实现强制执行并允许您添加ivars的类别。现在,我理解了其中每一个的基本原理:类别通常用于在运行时向任何类添加方法,而类扩展通常用于实施私有API实现并添加ivars。我接受这个。

But there are trifles that confuse me. First, at a hight level: Why differentiate like this? These concepts seem like similar ideas that can't decide if they are the same, or different concepts. If they are the same, I would expect the exact same things to be possible using a Category with no name as is with a named Category (which they are not). If they are different, (which they are) I would expect a greater syntactical disparity between the two. It seems odd to say, "Oh, by the way, to implement a Class Extension, just write a Category, but leave out the name. It magically changes."

但有些琐事让我困惑。首先,在更高的层次上:为什么要像这样区分?这些概念似乎是相似的,不能决定它们是相同的,还是不同的概念。如果它们是相同的,我希望使用一个没有名称的类别(它们不是)可以实现完全相同的事情。如果它们是不同的(它们是不同的),我预计两者在句法上的差异会更大。说“哦,顺便说一句,要实现类扩展,只写一个类别,但不写名称,这似乎有点奇怪。”它神奇地改变。”

Second, on the topic of compile time enforcement: If you can't add properties in a named Category, why does doing so convince the compiler that you did just that? To clarify, I'll illustrate with my example. I can declare a readonly property in the header file:

其次,关于编译时强制执行的主题:如果不能在命名类别中添加属性,为什么要这样做才能使编译器相信您已经这样做了?为了澄清这一点,我将用我的例子来说明。我可以在头文件中声明一个readonly属性:

// .h
@interface MyClass : NSObject
@property (readonly, copy) NSString* myString;
@end

Now, I want to head over to the implementation file and give myself private readwrite access to the property. If I do it correctly:

现在,我想转到实现文件并为自己提供对属性的私有读写访问。如果我做对了:

// .m
@interface MyClass ()
@property (readwrite, copy) NSString* myString;
@end

I get a warning when I don't synthesize, and when I do, I can set the property and everything is peachy. But, frustratingly, if I happen to be slightly misguided about the difference between Category and Class Extension and I try:

当我不合成时,我得到一个警告,当我合成时,我可以设置属性,一切都很好。但是,令人沮丧的是,如果我碰巧在类别和类别扩展之间的区别上被误导了,我就会尝试:

// .m
@interface MyClass (private)
@property (readwrite, copy) NSString* myString;
@end

The compiler is completely pacified into thinking that the property is readwrite. I get no warning, and not even the nice compile error "Object cannot be set - either readonly property or no setter found" upon setting myString that I would had I not declared the readwrite property in the Category. I just get the "Does not respond to selector" exception at runtime. If adding ivars and properties is not supported by (named) Categories, is it too much to ask that the compiler play by the same rules? Am I missing some grand design philosophy?

编译器完全被安抚成认为该属性已被读写。我没有得到任何警告,甚至连漂亮的编译错误“对象都不能设置——要么是readonly属性,要么是没有找到setter”。我只是在运行时得到“不响应选择器”的异常。如果(命名)类别不支持添加ivars和属性,那么要求编译器按照相同的规则运行是不是太过分了?我是否错过了一些宏伟的设计理念?

4 个解决方案

#1


53  

Class extensions were added in Objective-C 2.0 to solve two specific problems:

在Objective-C 2.0中添加了类扩展,以解决两个具体问题:

  1. Allow an object to have a "private" interface that is checked by the compiler.
  2. 允许对象具有由编译器检查的“私有”接口。
  3. Allow publicly-readable, privately-writable properties.
  4. 允许公开可读,privately-writable属性。

Private Interface

Before Objective-C 2.0, if a developer wanted to have a set of methods in Objective-C, they often declared a "Private" category in the class's implementation file:

在Objective-C 2.0之前,如果开发人员想要在Objective-C中有一组方法,他们通常会在类的实现文件中声明一个“私有”类别:

@interface MyClass (Private)
- (id)awesomePrivateMethod;
@end

However, these private methods were often mixed into the class's @implementation block (not a separate @implementation block for the Private category). And why not? These aren't really extensions to the class; they just make up for the lack of public/private restrictions in Objective-C categories.

但是,这些私有方法常常混合到类的@implementation块中(不是私有类别的单独的@implementation块)。为什么不呢?这些并不是类的扩展;它们只是弥补了Objective-C类别中公共/私有限制的不足。

The problem is that Objective-C compilers assume that methods declared in a category will be implemented elsewhere, so they don't check to make sure the methods are implemented. Thus, a developer could declare awesomePrivateMethod but fail to implement it, and the compiler wouldn't warn them of the problem. That is the problem you noticed: in a category, you can declare a property (or a method) but fail to get a warning if you never actually implement it -- that's because the compiler expects it to be implemented "somewhere" (most likely, in another compilation unit independent of this one).

问题是Objective-C编译器假设在某个类别中声明的方法将在其他地方实现,因此它们不会检查确保这些方法被实现。因此,开发人员可以声明awesomePrivateMethod,但是没有实现它,编译器不会警告他们这个问题。这就是您注意到的问题:在一个类别中,您可以声明一个属性(或一个方法),但是如果您从未真正实现它,您将得不到警告——这是因为编译器期望它在“某个地方”实现(很可能是在另一个独立于此的编译单元中实现)。

Enter class extensions. Methods declared in a class extension are assumed to be implemented in the main @implementation block; if they're not, the compiler will issue a warning.

输入类扩展。在一个类扩展中声明的方法被假定为在主@implementation块中实现;如果不是,编译器将发出警告。

Publicly-Readable, Privately-Writeable Properties

It is often beneficial to implement an immutable data structure -- that is, one in which outside code can't use a setter to modify the object's state. However, it can still be nice to have a writable property for internal use. Class extensions allow that: in the public interface, a developer can declare a property to be read-only, but then declare it to be writable in the class extension. To outside code, the property will be read-only, but a setter can be used internally.

实现不可变的数据结构通常是有益的——也就是说,外部代码不能使用setter来修改对象的状态。但是,有一个可写属性供内部使用仍然是很好的。类扩展允许这样:在公共接口中,开发人员可以将属性声明为只读,然后在类扩展中声明为可写的。对于外部代码,属性将是只读的,但是setter可以在内部使用。

So Why Can't I Declare a Writable Property in a Category?

Categories cannot add instance variables. A setter often requires some sort of backing storage. It was decided that allowing a category to declare a property that likely required a backing store was A Bad Thing™. Hence, a category cannot declare a writable property.

类别不能添加实例变量。setter通常需要某种后备存储器。决定,允许一个类别声明一个属性,可能需要一个后备存储器是一件坏事™。因此,类别不能声明可写属性。

They Look Similar, But Are Different

The confusion lies in the idea that a class extension is just an "unnamed category". The syntax is similar and implies this idea; I imagine it was just chosen because it was familiar to Objective-C programmers and, in some ways, class extensions are like categories. They are alike in that both features allow you to add methods (and properties) to an existing class, but they serve different purposes and thus allow different behaviors.

令人困惑的是,类扩展只是一个“未命名的类别”。语法是相似的,暗示了这个想法;我想之所以选择它,是因为Objective-C程序员对它很熟悉,在某些方面,类扩展就像类别。它们在这两个特性上都很相似,允许您将方法(和属性)添加到现有的类中,但是它们服务于不同的目的,从而允许不同的行为。

#2


8  

You're confused by the syntactic similarity. A class extension is not just an unnamed category. A class extension is a way to make part of your interface private and part public — both are treated as part of the class's interface declaration. Being part of the class's interface, an extension must be defined as part of the class.

你被语法相似性弄糊涂了。类扩展不只是一个未命名的类别。类扩展是使接口的部分私有和部分公有的一种方式——两者都被视为类的接口声明的一部分。作为类接口的一部分,扩展必须被定义为类的一部分。

A category, on the other hand, is a way of adding methods to an existing class at runtime. This could be, for example, in a separate bundle that is only loaded on Thursdays.

另一方面,类别是在运行时向现有类添加方法的一种方式。例如,这可以是一个单独的bundle,只在周四加载。

For most of Objective-C's history, it was impossible to add instance variables to a class at runtime, when categories are loaded. This has been worked around very recently in the new runtime, but the language still shows the scars of its fragile base classes. One of these is that the language doesn't support categories adding instance variables. You'll have to write out the getters and setters yourself, old-school style.

对于Objective-C的大部分历史,在加载类别时,不可能在运行时向类添加实例变量。在最近的新运行时中,这一功能一直在发挥作用,但是语言仍然显示了它脆弱的基类的伤疤。其中之一是语言不支持添加实例变量的类别。你必须自己写出getters和setters,老式风格。

Instance variables in categories are somewhat tricky, too. Since they aren't necessarily present when the instance is created and the initializer may not know anything about them, initializing them is a problem that doesn't exist with normal instance variables.

类别中的实例变量也有些棘手。由于在创建实例时它们不一定出现,而且初始化器可能不知道它们的任何信息,所以初始化它们是一个普通实例变量不存在的问题。

#3


3  

You can add a property in a category, you just can't synthesize it. If you use a category, you will not get a compile warning because it expects the setter to be implemented in the category.

你可以在一个类别中添加一个属性,但是你不能合成它。如果您使用一个类别,您将不会得到一个编译警告,因为它期望setter在该类别中实现。

#4


0  

Just a little clarification about the REASON for the different behavior of unnamed categories (now known as Class Extensions) and normal (named) categories.

只是稍微说明一下未命名类别(现在称为类扩展)和普通类别(命名)的不同行为的原因。

The thing is very simple. You can have MANY categories extending the same class, loaded at runtime, without the compiler and linker ever knowing. (consider the many beautiful extensions people wrote to NSObject, that add it functionality post-hoc).

事情很简单。您可以有许多类别扩展相同的类,在运行时加载,而编译器和链接器永远不会知道。(考虑到人们为NSObject编写的许多漂亮的扩展,它们在以后添加it功能)。

Now Objective-C has no concept of NAME SPACE. Therefore, having iVars defined in a named category could create a symbol * in runtime. If two different categories would be able to define the same

Objective-C没有名字空间的概念。因此,在命名类别中定义iVars可以在运行时创建符号冲突。如果两个不同的类别能够定义相同的。

@interface myObject (extensionA) {
 NSString *myPrivateName;
}
@end
@interface myObject (extensionB) {
 NSString *myPrivateName;
}
@end

then at the very least, there will be memory overrun at runtime.

然后,至少在运行时存在内存溢出。

In contradiction, Class extensions have NO NAME, and thus there can be only ONE. That's why you can define iVars there. They are assured to be unique.

相反,类扩展没有名称,因此只能有一个。这就是为什么可以在这里定义iVars。他们被保证是独一无二的。

As for the compiler errors and warnings related to categories and class extensions + ivars and property definitions, I have to agree they are not so helpful, and I spent too much time trying to understand why things compile or not, and how they work (if they work) after they compile.

至于编译器错误和警告类别和相关类扩展+实例变量和属性定义,我不得不同意他们不是很有帮助,我花了太多的时间试图理解为什么事情编译,以及它们如何工作(如果他们工作)后编译。

#1


53  

Class extensions were added in Objective-C 2.0 to solve two specific problems:

在Objective-C 2.0中添加了类扩展,以解决两个具体问题:

  1. Allow an object to have a "private" interface that is checked by the compiler.
  2. 允许对象具有由编译器检查的“私有”接口。
  3. Allow publicly-readable, privately-writable properties.
  4. 允许公开可读,privately-writable属性。

Private Interface

Before Objective-C 2.0, if a developer wanted to have a set of methods in Objective-C, they often declared a "Private" category in the class's implementation file:

在Objective-C 2.0之前,如果开发人员想要在Objective-C中有一组方法,他们通常会在类的实现文件中声明一个“私有”类别:

@interface MyClass (Private)
- (id)awesomePrivateMethod;
@end

However, these private methods were often mixed into the class's @implementation block (not a separate @implementation block for the Private category). And why not? These aren't really extensions to the class; they just make up for the lack of public/private restrictions in Objective-C categories.

但是,这些私有方法常常混合到类的@implementation块中(不是私有类别的单独的@implementation块)。为什么不呢?这些并不是类的扩展;它们只是弥补了Objective-C类别中公共/私有限制的不足。

The problem is that Objective-C compilers assume that methods declared in a category will be implemented elsewhere, so they don't check to make sure the methods are implemented. Thus, a developer could declare awesomePrivateMethod but fail to implement it, and the compiler wouldn't warn them of the problem. That is the problem you noticed: in a category, you can declare a property (or a method) but fail to get a warning if you never actually implement it -- that's because the compiler expects it to be implemented "somewhere" (most likely, in another compilation unit independent of this one).

问题是Objective-C编译器假设在某个类别中声明的方法将在其他地方实现,因此它们不会检查确保这些方法被实现。因此,开发人员可以声明awesomePrivateMethod,但是没有实现它,编译器不会警告他们这个问题。这就是您注意到的问题:在一个类别中,您可以声明一个属性(或一个方法),但是如果您从未真正实现它,您将得不到警告——这是因为编译器期望它在“某个地方”实现(很可能是在另一个独立于此的编译单元中实现)。

Enter class extensions. Methods declared in a class extension are assumed to be implemented in the main @implementation block; if they're not, the compiler will issue a warning.

输入类扩展。在一个类扩展中声明的方法被假定为在主@implementation块中实现;如果不是,编译器将发出警告。

Publicly-Readable, Privately-Writeable Properties

It is often beneficial to implement an immutable data structure -- that is, one in which outside code can't use a setter to modify the object's state. However, it can still be nice to have a writable property for internal use. Class extensions allow that: in the public interface, a developer can declare a property to be read-only, but then declare it to be writable in the class extension. To outside code, the property will be read-only, but a setter can be used internally.

实现不可变的数据结构通常是有益的——也就是说,外部代码不能使用setter来修改对象的状态。但是,有一个可写属性供内部使用仍然是很好的。类扩展允许这样:在公共接口中,开发人员可以将属性声明为只读,然后在类扩展中声明为可写的。对于外部代码,属性将是只读的,但是setter可以在内部使用。

So Why Can't I Declare a Writable Property in a Category?

Categories cannot add instance variables. A setter often requires some sort of backing storage. It was decided that allowing a category to declare a property that likely required a backing store was A Bad Thing™. Hence, a category cannot declare a writable property.

类别不能添加实例变量。setter通常需要某种后备存储器。决定,允许一个类别声明一个属性,可能需要一个后备存储器是一件坏事™。因此,类别不能声明可写属性。

They Look Similar, But Are Different

The confusion lies in the idea that a class extension is just an "unnamed category". The syntax is similar and implies this idea; I imagine it was just chosen because it was familiar to Objective-C programmers and, in some ways, class extensions are like categories. They are alike in that both features allow you to add methods (and properties) to an existing class, but they serve different purposes and thus allow different behaviors.

令人困惑的是,类扩展只是一个“未命名的类别”。语法是相似的,暗示了这个想法;我想之所以选择它,是因为Objective-C程序员对它很熟悉,在某些方面,类扩展就像类别。它们在这两个特性上都很相似,允许您将方法(和属性)添加到现有的类中,但是它们服务于不同的目的,从而允许不同的行为。

#2


8  

You're confused by the syntactic similarity. A class extension is not just an unnamed category. A class extension is a way to make part of your interface private and part public — both are treated as part of the class's interface declaration. Being part of the class's interface, an extension must be defined as part of the class.

你被语法相似性弄糊涂了。类扩展不只是一个未命名的类别。类扩展是使接口的部分私有和部分公有的一种方式——两者都被视为类的接口声明的一部分。作为类接口的一部分,扩展必须被定义为类的一部分。

A category, on the other hand, is a way of adding methods to an existing class at runtime. This could be, for example, in a separate bundle that is only loaded on Thursdays.

另一方面,类别是在运行时向现有类添加方法的一种方式。例如,这可以是一个单独的bundle,只在周四加载。

For most of Objective-C's history, it was impossible to add instance variables to a class at runtime, when categories are loaded. This has been worked around very recently in the new runtime, but the language still shows the scars of its fragile base classes. One of these is that the language doesn't support categories adding instance variables. You'll have to write out the getters and setters yourself, old-school style.

对于Objective-C的大部分历史,在加载类别时,不可能在运行时向类添加实例变量。在最近的新运行时中,这一功能一直在发挥作用,但是语言仍然显示了它脆弱的基类的伤疤。其中之一是语言不支持添加实例变量的类别。你必须自己写出getters和setters,老式风格。

Instance variables in categories are somewhat tricky, too. Since they aren't necessarily present when the instance is created and the initializer may not know anything about them, initializing them is a problem that doesn't exist with normal instance variables.

类别中的实例变量也有些棘手。由于在创建实例时它们不一定出现,而且初始化器可能不知道它们的任何信息,所以初始化它们是一个普通实例变量不存在的问题。

#3


3  

You can add a property in a category, you just can't synthesize it. If you use a category, you will not get a compile warning because it expects the setter to be implemented in the category.

你可以在一个类别中添加一个属性,但是你不能合成它。如果您使用一个类别,您将不会得到一个编译警告,因为它期望setter在该类别中实现。

#4


0  

Just a little clarification about the REASON for the different behavior of unnamed categories (now known as Class Extensions) and normal (named) categories.

只是稍微说明一下未命名类别(现在称为类扩展)和普通类别(命名)的不同行为的原因。

The thing is very simple. You can have MANY categories extending the same class, loaded at runtime, without the compiler and linker ever knowing. (consider the many beautiful extensions people wrote to NSObject, that add it functionality post-hoc).

事情很简单。您可以有许多类别扩展相同的类,在运行时加载,而编译器和链接器永远不会知道。(考虑到人们为NSObject编写的许多漂亮的扩展,它们在以后添加it功能)。

Now Objective-C has no concept of NAME SPACE. Therefore, having iVars defined in a named category could create a symbol * in runtime. If two different categories would be able to define the same

Objective-C没有名字空间的概念。因此,在命名类别中定义iVars可以在运行时创建符号冲突。如果两个不同的类别能够定义相同的。

@interface myObject (extensionA) {
 NSString *myPrivateName;
}
@end
@interface myObject (extensionB) {
 NSString *myPrivateName;
}
@end

then at the very least, there will be memory overrun at runtime.

然后,至少在运行时存在内存溢出。

In contradiction, Class extensions have NO NAME, and thus there can be only ONE. That's why you can define iVars there. They are assured to be unique.

相反,类扩展没有名称,因此只能有一个。这就是为什么可以在这里定义iVars。他们被保证是独一无二的。

As for the compiler errors and warnings related to categories and class extensions + ivars and property definitions, I have to agree they are not so helpful, and I spent too much time trying to understand why things compile or not, and how they work (if they work) after they compile.

至于编译器错误和警告类别和相关类扩展+实例变量和属性定义,我不得不同意他们不是很有帮助,我花了太多的时间试图理解为什么事情编译,以及它们如何工作(如果他们工作)后编译。