Objective-C self->_ivar 访问与显式与隐式 self->
Posted
技术标签:
【中文标题】Objective-C self->_ivar 访问与显式与隐式 self->【英文标题】:Objective-C self->_ivar access with explicit vs implicit self-> 【发布时间】:2013-11-08 19:40:29 【问题描述】:一般问题
直到现在,我一直认为self->_ivar
等价于_ivar
。今天我发现这并不完全正确。
例如看下面的代码sn-p:
@interface TestClass : NSObject
NSString *_testIVar;
@end
@implementation TestClass
- (instancetype)init
if ((self = [super init]))
_testIVar = @"Testing Only";
return self;
- (void)test
NSInteger self = 42;
NSLog(@"without arrow: %@", _testIVar); /* OK */
NSLog(@"with arrow: %@", self->_testIVar); /* COMPILER ERROR! */
@end
即使我用一些NSInteger
也称为self
隐藏了原始self
,隐式ivar 语法_testIVar
仍然可以找到“原始”自我,而self->_testIVar
显然没有。在后一种情况下,编译器正确地抱怨
成员引用类型“NSInteger”(又名“long”)不是指针
然而,在第一种情况下,它可以正常工作。
现实世界的问题
这个例子可能看起来很人为,但根本不是。例如 ExtObjC 项目(由 ReactiveCocoa 使用)定义了非常方便的 @weakify(var)
和 @strongify(var)
通过定义非常方便的语法(无需写的奇怪又麻烦再写__weak typeof(self) weakSelf = self; [...] ^ __strong typeof(self) strongSelf = weakSelf; [...]
了)。例如:
- (void)someMethod
@weakify(self);
dispatch_async(self.someQueue, ^
@strongify(self);
NSLog(@"self @ %p", self);
如果没有@weakify
和@strongify
,该块将捕获对self
的强引用。对于@weakify
和@strongify
,它不会。所以self
的释放不会被推迟,直到块运行。但主要优点是您无需记住使用 weakSelf
或 strongSelf
代替 self
,因为“原始”self
是隐藏的。
这非常方便,ExtObjC 通过使用宏生成类似以下内容来实现 @weakify
/ @strongify
:
- (void)someMethod
__weak typeof(self) _weakSelf = self;
dispatch_async(self.someQueue, ^
__strong typeof(self) self = _weakSelf;
NSLog(@"self @ %p", self);
公平地说,这更好,因为我们可以继续使用self
,而无需实际捕获对self
的强引用。但是,一旦我们使用了implicit-ivars-of-self-syntax,对“原始”self
的强引用仍然会被捕获!
- (void)someMethod
@weakify(self);
dispatch_async(self.someQueue, ^
@strongify(self); /* compiler warning: Unused variable self here!!! */
NSLog(@"self->_testIVar: %@", _testIVar);
杂项
在块中使用 ivars 时,我们肯定会捕获 self
。例如,请参阅此屏幕截图:
.
屏幕截图的另一个有趣之处是警告消息是
未使用的变量'self'
在下面一行
在此块中强烈捕获“自我”可能会导致保留周期
这就是为什么我认为self
有两个版本:-)
问题
这里的实际问题是:_testIVar
到底是什么意思?它如何找到“原始”self
指针?
澄清(也见我的截图):正如@MartinR 指出的(这也是我的想法),self
有一些特殊版本,不能更改,仅用于implicit-self-ivar -使用权。这在某处有记录吗?基本上在哪里定义隐含的self
指的是什么?它的行为似乎与例如 Java 的行为相同(使用 this
),但不同之处在于 this
是您无法覆盖的保留关键字。
问题也不在于如何“修复”它,在@weakify
/@strongify
示例中,只需编写self->_testIVar
即可。更重要的是,我认为使用 @weakify
/@strongify
您不会再犯隐式强捕获 self
的错误,但事实似乎并非如此。
【问题讨论】:
我很惊讶self
不是保留字 o_O
但是在每个- (instancetype)init
方法中,你都会使用if ((self = [super init])) ...
,不是吗?所以你分配到self
,因此它不能是保留关键字。
我的意思是保留,以防止您在类方法中声明具有该名称的变量。与您的问题无关,实际上只是评论。
我的猜想是_ivar
等价于self->_ivar
其中self
是每个Objective-C 方法调用所具有的隐式第一个参数,即使有同名的局部变量。我没有官方参考(否则我会写一个答案:-),但我第一次尝试阅读生成的汇编代码证实了这个猜想。
@JohannesWeiß:另一方面,self = [super init]
也会覆盖 self,在这种情况下 预期 _ivar = ...
会设置“新自我”的实例变量”。所以这可能是块的一个特殊问题。
【参考方案1】:
所有 Objective-C 方法都使用两个隐藏参数调用(来自 "Objective-C Runtime Programming Guide"):
接收对象 方法的选择器并且方法可以将接收对象称为self
(并将其自己的选择器称为_cmd
)。
现在_ivar
等同于self->_ivar
其中self
是这个首先隐含
函数参数。
只要您不在内部范围内定义新变量self
,_ivar == self->_ivar
就成立。
如果你在内部范围内定义一个新变量self
,那么你有
self
,
作为第一个函数参数的“隐式自我”,
而_ivar
仍然指的是“隐式自我”!这解释了您的块中的编译器警告,这似乎相互矛盾:
self
,
"Capturing 'self' strong in this block ..."指的是函数的“隐式自我”。
以下代码也演示了这一点:
@interface MyClass : NSObject
NSString *_ivar;
@end
@implementation MyClass
- (void)test
_ivar = @"foo"; // Set instance variable of receiver
MyClass *self = [MyClass new]; // Redefine self in inner scope
self->_ivar = @"bar"; // Set instance variable of redefined self
NSLog(@"%@ - %@", self->_ivar, _ivar);
// Output: bar - foo
@end
【讨论】:
“所以_ivar(在实例方法中)指的是方法接收者的实例变量。”不一定是真的。如果分配给self
(不是在内部范围内创建新变量;而是分配给已经存在的self
变量),然后访问实例变量,它们将访问self
现在指向的对象上的实例变量to,不一定与方法的接收者相同。
@newacct:是的,你是对的。稍后我会尝试改进答案。谢谢指正!
@newacct:实际上,如果您分配一个新值,例如,Apple 文档中的“A 方法将接收对象称为 self”的语句也是错误的。 self = [super init]
.
@newacct:我已经重写了答案,欢迎您的反馈。
很高兴知道。我总是使用明确的self->
访问,这给了我一个理由。谢谢:)以上是关于Objective-C self->_ivar 访问与显式与隐式 self->的主要内容,如果未能解决你的问题,请参考以下文章
Objective-c:关于self = [super init]的问题
Objective-C:使用 self.navigationController 和自定义 UINavigationController