0

在 Big Nerd Ranch 的 iOS Programming Book(第 3 版)中,他们在 pg.194 上说……知识渊博的程序员仍然可以通过 allocWithZone: 创建 BNRItemStore 的实例,这将绕过我们偷偷摸摸的 alloc 陷阱。为了防止这种可能性,请覆盖 allocWithZone:在 BNRItemStore.m 中返回单个 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
}

输出是:

  • 2013-10-18 18:24:40.132 BNRItemStore[381:707] 检索超级对象
  • 2013-10-18 18:24:40.134 BNRItemStore[381:707] BNRItemStore:0x7f8c72c091e0

如果子类 '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 方法内部?请澄清..

4

2 回答 2

1

第一个,最重要的教训是你不应该覆盖+allocWithZone:. 我知道 BNR 书描述了它(BNR 书一般都很好)。你不应该这样做。我知道 Apple 包含一些示例代码。你不应该这样做。(Apple 在解释中指出很少需要这个。)应该使用dispatch_once模式创建单例。

您没有给出初始代码,但我怀疑他们的示例代码会覆盖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()在您的情况下)是否可以绕过单例实例化,而她不能。(**)

(*) 它在这一点上没有说明,而且可能应该说明的一点是,当调用者要求您分配一个新对象时,通过悄悄地返回一个单例来“强制执行单例状态”通常是一个坏主意。如果您需要强制执行单例状态,IMO 最好使用 in 中的断言来执行此操作init,因为对第二次分配的请求表示编程错误。也就是说,有时出于性能原因,不可变对象的“透明”单例可能很有用,例如特殊的单例NSNumber提供了某些公共整数,并且这种技术在这些情况下是合适的。(通过“透明”,我的意思是单例是调用者永远不应该担心的实现细节。这至少假定对象是不可变的。)

(**) 其实只要她下定决心,她就可以。她总是可以打电话给NSAllocateObject()自己,完全绕过+alloc,然后打电话-init。这当然是疯了,没有理由这样做来“保护”她免受自己的伤害。SDK 的工作不是保护自己免受调用者的影响。保护调用者免受可能的错误只是 SDK 的工作。调用者永远不是敌人。

于 2013-10-18T17:47:44.567 回答
0

我不确定这是否完全回答了您的问题,但是“allocWithZone:”在当天被用来对分配的内存进行分区。此后,Apple 已经放弃了这个概念,并希望将所有内容都分配在同一个堆空间中。“allocWithZone:” 甚至不再像以前那样运行,而且苹果明确表示不要使用它。

于 2013-10-18T17:35:01.013 回答