---- 2011.07.21 更新 ----
// 2011.07.21
// Xcode 4.0.2
// 64-bit
@interface IvarNameTest : NSObject {
@private
}
@property(nonatomic) NSNumber *number;
@property(nonatomic) float f;
- (void)printValue;
@end
#import "IvarNameTest.h"
@implementation IvarNameTest
@synthesize number = anyIdentifier;
@synthesize f = anyIdentifier2;
- (void)printValue
{
anyIdentifier = [NSNumber numberWithDouble:77.77];
anyIdentifier2 = 7.7f;
NSLog(@"%@, %f", anyIdentifier, anyIdentifier2);
}
@end
说明:
在 64-bit 平台下编译,在 @interface 块中如果没有定义 instance variable,给出了 @property 声明,同时在 @implementation 块中给出了 @synthesize。
结论:
1)如果是 @synthesize name; 形式,则编译器自动创建的 instance variable 名字就是 name,也就是 @property 声明中的名字;
2)如果是 @synthesize name = XXXX; 形式,则编译器自动创建的 instance variable 的名字就是 XXXX。
---- 2011.07.21 更新 ----
调查这个错误的原因不是错误本身,而是看李晨的《iPad应用开发实践》时,手动在 Xcode 4 中写了书中的例子。
发现个问题。
类的头文件:
#import
@class HelloWorldMailViewController;
@interface HelloWorldMailAppDelegate : NSObject
{
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet HelloWorldMailViewController *viewController;
@end
实现文件:
// ...
@implementation HelloWorldMailAppDelegate
@synthesize window=_window;
@synthesize viewController=_viewController;
// ...
@end
对于这些代码,有两点疑问:
1)既然有 @property 声明和 @synthesize,为什么在类的 @interface 中没有 ivars 声明:
UIWindow *window;
HelloWorldMailViewController *viewController;
2)@implementation部分的:
@synthesize window=_window;
@synthesize viewController=_viewController;
是什么意思?
为此,自己写了类似代码测试,结果得到标题中的错误提示(用 32-bit 编译时得到的)。Google 到这个帖子:
http://www.cocoabuilder.com/archive/cocoa/198573-property-problem.html
回复中,问题基本阐述清楚:
在 32-bit 时,如果类的 @interface 部分没有进行 ivar 声明,但有 @property 声明,在类的 @implementation 部分有相应的 @synthesize,则会得到类似下面的编译错误:
Synthesized property 'xX' must either be named the same as a compatible ivar or must explicitly name an ivar
在 64-bit时,运行时系统会自动给类添加 ivar,添加的 ivar 以一个下划线"_"做前缀。
上面声明部分的 @synthesize window=_window; 意思是说,window 属性为 _window 实例变量合成访问器方法。
在 CocoaChina 的帖子:http://www.cocoachina.com/bbs/read.php?tid-66688.html
On 2/11/08, Randall Meadows wrote:
> AFAICT, "capturing" IS the name of my ivar. What is it *really*
> complaining about? What did I do wrong?
You are incorrect. Re-read the documentation closely; on 32-bit
architectures (the "non-fragile instance variable runtime") you must
provide an instance variable for your property. If it does not have
the same name as the property, you must specify this in your
declaration.
On 64-bit architectures, you can omit the ivar declaration.
Also, you have not specified a memory management method in your
@property declaration, which you must do if you are not using garbage
collection.
HTH,
--Kyle Sluder
> I've boiled the problem down to this snippet:
>
> @interface MyWindow : NSWindow
> {
> }
> @property(readwrite) BOOL capturing;
...
> error: synthesized property 'capturing' must either be named the
> same as a compatible ivar or must explicitly name an ivar
You haven't declared a compatible ivar named "capturing". You'd need
to do something like this:
@interface MyWindow : NSWindow
{
BOOL capturing; // <-- here!
}
@property(readwrite) BOOL capturing;
The line marked above declared an ivar with the same name and the same
type as your property declaration, so now synthesize will be able to
do its thing.
Obj-C *can* be smart enough to generate this ivar for you behind the
scenes, but only if you're using the 64bit runtime. If you don't
require 32bit compatibility, turn it off, and then you don't have to
worry about declaring these ivars for synthesize.
Cheers,
-Joshua Emmons
FYI: The property name doesn't have to match the instance variable
name, so it's OK to keep using prefixes on your ivar names. So you
could declare the class as
@interface MyWindow : NSWindow {
BOOL _capturing; // or mCapturing or whatever
}
@property(readwrite) BOOL capturing;
@end
and then in the implementation use
@synthesize capturing=_capturing;
I strongly recommend prefixing all instance variables, to avoid
confusion with locals. I've also found out the hard way that it's even
more important to do this with properties.
Not prefixing property instance vars can lead to a type of mistake
where, in the implementation of the same class, you accidentally write
something like
contents = [NSArray array];
when you meant to write
self.contents = [NSArray array];
Do you see the problem? Assuming no GC, and the 'contents' property is
marked 'retain' or 'copy', the first line will cause a crash sometime
later on, because you directly assigned an autoreleased value to an
instance variable, so sometime after this method leaves scope, that
array is going to be dealloced and 'contents' will be a bad pointer.
The second line invokes the property's setter method, which correctly
retains or copies the array.
—Jens
PS: To forstall an FAQ: Yes, it is kosher to use "_" as your ivar
prefix, even though the Foundation and AppKit classes do the same.
Name collisions are not a problem. It is, however, recommended to not
use "_" as a method name prefix, because you can collide with and
accidentally override internal superclass methods.
>> PS: To forstall an FAQ: Yes, it is kosher to use "_" as your ivar
>> prefix, even though the Foundation and AppKit classes do the same.
>> Name collisions are not a problem.
>
> Could you elaborate? If I subclass NSView and add an ivar named
> "_sisterView" then in some future SDK NSView.h also gets a _sisterView
> ivar, wouldn't there be a compiler error?
Yes, and that's why I believe Jens is telling you that it's OK to use
the same prefix for ivars as anyone else that you're sharing code
with: Because any name clashes would be immediately apparent. This in
contrast to, for example, method names.
> Pity private ivars have to be in public headers...
Agreed, that is truly a pity.
j o a r
> Could you elaborate? If I subclass NSView and add an ivar named
> "_sisterView" then in some future SDK NSView.h also gets a _sisterView
> ivar, wouldn't there be a compiler error?
Yes, but not a runtime error, and it wouldn't break binary
compatibility. So it wouldn't affect users, only you (or other people
using your source code), and it's easy to fix with a quick Refactor
command.
Unlike a method-name conflict, which _would_ break existing binary
copies of your app, in strange and hard-to-predict ways.
—Jens
> Yes, and that's why I believe Jens is telling you that it's OK to
> use the same prefix for ivars as anyone else that you're sharing
> code with: Because any name clashes would be immediately apparent.
> This in contrast to, for example, method names.
Note that this isn't true in the 64-bit runtime, where ivars don't
have to be declared publicly. There, AFAIK, ivars are uniqued by name
still, so unexpected clashes are possible (and wouldn't be detected at
compile time, necessarily... someone less lazy than me should write
some test cases and find out :) ).
Wade
On Feb 17, 2008, at 12:03 PM, Sherm Pendley wrote:
> Why would the parent's size be hard-coded? You can get the size of
> the parent at run time, by dereferencing a couple of levels into
> isa. All the compiler would have to do is emit code that walks down
> isa to get the parent's size, and add it to self to establish a base
> address. The only hard-coded offsets would then be relative to that
> base, for the current class' own ivars.
>
> Changing the size of the parent class wouldn't break such a scheme,
> so far as ordinary usage goes. People playing games with sizeof() or
> @defs deserve whatever breakage they get. :-)
>
> Doesn't the current (I refuse to call it "legacy" quite yet...) 32-
> bit compiler emit code like that when ivars are accessed?
To dive a little deeper, consider the instance variables for NSView,
including inherited ivars. Well, a subset, really:
@interface NSObject {
Class isa;
}
@interface NSResponder : NSObject
{
id _nextResponder;
}
@interface NSView : NSResponder
{
NSRect _frame;
NSRect _bounds;
id _superview;
id _subviews;
NSWindow *_window;
}
Within the legacy runtime ;), the memory for NSView looks just like a
C struct with this layout:
struct NSViewLikeStruct {
Class isa;
id _nextResponder;
NSRect _frame;
NSRect _bounds;
id _superview;
id _subviews;
NSWindow *_window;
};
So, in the implementation of an NSView, an access to an instance
variable -- say _subviews -- is compiled down to something equivalent
to:
((struct NSViewLikeStruct *) aView)->_subviews
Now, if an instance variable is added to NSObject or NSResponder and
the subclasses are not compiled, the above code will have the wrong
offset.
This is fragile, but allows instances to be allocated as a single
chunk of memory while also reducing the amount of metadata and/or
indirection required to get at any given instance variable's slot.
To fix the issue, it has to be done partially at runtime and requires
a different layout for the instances...
b.bum
On 17 Feb '08, at 12:03 PM, Sherm Pendley wrote:
> Why would the parent's size be hard-coded? You can get the size of the
> parent at run time, by dereferencing a couple of levels into isa.
> All the
> compiler would have to do is emit code that walks down isa to get the
> parent's size, and add it to self to establish a base address. The
> only
> hard-coded offsets would then be relative to that base, for the
> current
> class' own ivars.
This has been done before, most notably in IBM/Apple's "SOM" runtime
that was used in OpenDoc in the mid-'90s. The main drawback is that it
becomes more expensive to access instance variables. Instead of
indexing off a constant offset from the object pointer, you have to
call a runtime function that returns a pointer to the class's ivars,
and index off that.
It also adds some complexity to both the compiler and the runtime, to
deal with the dynamic location of the ivars.
The "Fragile Base Class" problem is common to many object runtimes. C+
+ has it even worse, in that the normal vtable-based runtime makes it
impossible to add even *methods* to a base class without breaking
subclasses. It turns out that fragile methods are much more of a
problem than fragile instance variables; it's fairly easy to reserve a
few unused ivars in advance and use them later on. So Obj-C has been
able to work quite well in dynamic libraries, while C++ has usually
fallen on its face. But it's nice that this last restriction is now
lifted in the 64-bit environment.
(Scripting languages don't generally suffer from this. Most of them
store an object's ivars in a hashtable, which is much slower but much
more flexible, so you can do crazy stuff like adding ivars to
individual instances.)
—Jens