在Big Nerd Ranch(第3版)的iOS编程书中,他们在第194页上说 知识渊博的程序员仍然可以通过allocWithZone:创建一个BNRItemStore实例,它可以绕过我们偷偷摸摸的alloc trap。为了防止这种可能性,在BNRItemStore.m中覆盖allocWithZone:返回单个BNRItemStore实例。
+(id) allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
这句话似乎让我感到困惑。以下代码不能以某种方式证明这是错误的 -
#import <Foundation/Foundation.h>
@interface BNRItemStore : NSObject
+(BNRItemStore *)sharedStore;
+(id)retrieveObject;
@end
@implementation BNRItemStore
+(BNRItemStore *)sharedStore{
static BNRItemStore *sharedStore=nil;
if (!sharedStore){
NSLog(@"Test2");
sharedStore= [[super allocWithZone:nil] init];
}
NSLog(@"sharedStore-> %@",sharedStore);
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone{
NSLog(@"Test1");
return [self sharedStore];
}
+(id)alloc{
NSLog(@"Retrieving super object");
NSLog(@"%@", [super allocWithZone:nil]);//Bypassing the subclass version of allocWithZone.
return [super allocWithZone:nil];
}
@end
int main(){
[[BNRItemStore alloc] init]; //the alloc message triggers a call to the subclass (overriding) version of +(id)alloc method
}
输出结果为:
如果子类'alloc'方法内的调用[super allocWithZone:nil]会触发对子类allocWithZone的调用,则控制台将记录“Test1”和“Test2”,最后会导致静态指针被分配。但这并没有发生。 这意味着如果我们直接调用[NSObject allocWithZone:nil]或[super allocWithZone:nil],该消息将不会重定向到allocWithZone的重写版本(子类版本),但会直接访问NSAllocateObject()函数,该函数执行实际操作分配。 NSObject中的+(id)allocWithZone代码必须看起来像这样 -
+(id)allocWithZone:(NSZone *)zone{
return NSAllocateObject();
}
如果这个实现(NSObject的allocWithZone :)包含类似[self allocWithZone]的东西,则消息调度机制将包含allocWithZone的子类版本,这将使我们通过涉及调用sharedStore方法的“偷偷摸摸”陷阱。以下就是我所说的情况。现在,如果是这种情况,代码肯定会有无限循环。很明显,情况并非如此。
+(id)allocWithZone:(NSZone *)zone{
if([self allocWithZone:zone]) //this would trigger a call to subclass ver. which would call sharedStore method which would then have [super allocWithZone:nil].Infinite Loop
return NSAllocateObject();
}
所以有人可以清除这个所谓的“鬼鬼祟祟”陷阱的查询。该陷阱是否意味着阻止任何人单独实例化。即不能使用NSObject的allocWithZone,除非在sharedStore方法内部?请澄清..
答案 0 :(得分:1)
这里第一个也是最重要的一课是你不应该覆盖+allocWithZone:
。我知道BNR书描述了它(而且BNR书一般都非常好)。你不应该这样做。我知道Apple包含了一些代码来实现它。你不应该这样做。 (并且Apple在解释中注意到很少需要这个。)单身人士应该使用dispatch_once
pattern创建。
您没有提供初始代码,但我怀疑他们的示例代码会覆盖alloc
,但不会覆盖allocWithZone:
。他们只是说,如果来电者使用allocWithZone:
,则不会通过alloc
,因此他们也会覆盖alloc
来捕获它。 (当然,正确的答案只是覆盖allocWithZone:
和而不是 alloc
。但在任何情况下都不应该覆盖这些方法。)
编辑:
我相信你误解了“我们偷偷摸摸的分配陷阱”在这里意味着什么。作者在文本的这一点上假设了以下代码:
@interface BNRItemStore : NSObject
+(BNRItemStore *)sharedStore;
@end
@implementation BNRItemStore
+(BNRItemStore *)sharedStore{
static BNRItemStore *sharedStore=nil;
if (!sharedStore){
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
@end
就是这样;根本没有+alloc
覆盖。然后它指出“要强制执行单例状态......您必须确保无法分配另一个BNRItemStore
实例。” (*)
作者继续建议我们可以通过覆盖+alloc
来强制执行单身人士状态,但会立即注意到这是不够的,因为调用者可以使用+allocWithZone:
。由于记录了[NSObject alloc]
调用[self allocWithZone:]
,因此覆盖+allocWithZone:
并且不必要且不足以覆盖+alloc
是必要且充分的。
您在代码中所做的工作证明您可以修改BNRItemStore
以致电[super allocWithZone:]
中的+alloc
。这不是重点。如果您可以修改BNRItemStore
,也可以将其设为非单身人士。关键是外部调用者(在您的情况下为main()
)是否可以绕过单例实例化,而她不能。 (**)
(*)在这一点上,并且可能应该做的一点是,当呼叫者要求你分配一个新的时候,通过悄悄地返回一个单身来“强制执行单身人士状态”通常是一个坏主意。宾语。如果您需要来强制执行单例状态,则最好通过init
中的断言来执行此操作,因为第二次分配的请求表示编程错误。也就是说,有时候不可变对象的“透明”单例可能因性能原因而有用,例如特殊单例NSNumber
提供某些常见整数,并且这种技术在这些情况下是合适的。 (通过“透明”,我的意思是单例是调用者不应该担心的实现细节。这至少假定对象是不可变的。)
(**)如果她决定这样做,她实际上可以。她总是可以自己拨打NSAllocateObject()
,完全绕过+alloc
,然后拨打-init
。这当然是疯了,没有理由“保护”她自己做这件事。 SDK的工作不是保护自己免受呼叫者的侵害。只有SDK的工作才能保护呼叫者免受可能的错误。呼叫者永远不是敌人。
答案 1 :(得分:0)
我不确定这是否能完全回答你的问题,但当天使用“allocWithZone:”来分区分配的内存。从那时起,苹果已经摆脱了这个概念,并希望将所有内容分配到同一个堆空间中。 “allocWithZone:”甚至不像以前那样运作,苹果特别表示不使用它。