Objective c 访问NSObject';s(超类)版本的allocWithZone,同时绕过allocWithZone的重写(子类)版本

Objective c 访问NSObject';s(超类)版本的allocWithZone,同时绕过allocWithZone的重写(子类)版本,objective-c,Objective C,在Big Nerd Ranch(第三版)的iOS编程书中,他们在第194页这样说 …一个知识渊博的程序员仍然可以通过allocWithZone:创建一个BNRItemStore实例,这将绕过我们狡猾的alloc陷阱。为了防止这种可能性,请重写BNRItemStore.m中的allocWithZone:以返回单个BNRItemStore实例 +(id) allocWithZone:(NSZone *)zone { return [self sharedStore]; } 这句话让我感到困

在Big Nerd Ranch(第三版)的iOS编程书中,他们在第194页这样说 …一个知识渊博的程序员仍然可以通过allocWithZone:创建一个BNRItemStore实例,这将绕过我们狡猾的alloc陷阱。为了防止这种可能性,请重写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
}
如果这个实现(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();
}

那么有人能澄清这个关于所谓“鬼鬼祟祟”陷阱的疑问吗。这个陷阱是不是用来阻止任何人单独实例化,也就是说,除非在sharedStore方法中,否则不能使用NSObject的allocWithZone?请澄清..

我不确定这是否完全回答了您的问题,但“allocWithZone:”在当时被用来对分配的内存进行分区。苹果从此放弃了这个概念,希望所有的东西都能分配到相同的堆空间中。allocWithZone:“甚至不再像以前那样工作了,苹果明确表示不再使用它

这里的第一个也是最重要的教训是,您不应该覆盖
+allocWithZone:
。我知道BNR的书描述了它(而且BNR的书一般都很好)。你不应该这样做。我知道苹果公司提供了一些这样做的示例代码。你不应该这样做。(苹果在解释中指出,很少需要这样做。)单例应该使用

您没有给出初始代码,但我怀疑他们的示例代码覆盖了
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
覆盖。然后它指出“为了强制执行singleton状态……您必须确保无法分配另一个
bRitemStore
实例。”(*)

作者继续建议,我们可以通过重写
+alloc
来强制执行单例状态,但立即指出这是不够的,因为调用者可以使用
+allocWithZone:
。由于有文件证明,
[NSObject alloc]
调用
[self allocWithZone:
,因此覆盖
+allocWithZone:
是必要且充分的,覆盖
+alloc
是不必要且不充分的


您在代码中所做的是演示您可以在
+alloc
中修改
britemstore
以调用
[super allocWithZone:
。这不是重点。如果您可以修改
bRitemStore
,也可以将其设置为非单例。关键是外部调用方(
main()
在您的情况下)是否可以绕过单例实例化,而她不能。(**)

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


(**)如果她决心这样做,实际上她可以。她总是可以自己调用
NSAllocateObject()
,完全绕过
+alloc
,然后调用
-init
。这当然是疯狂的,没有理由这样做来“保护”她自己。SDK的任务不是保护自己不受调用方的影响。SDK的工作只是保护调用者不受可能出现的错误的影响。打电话的人从来都不是敌人。

是的,这肯定是真的,但因为我现在正处于学习阶段,并且一章一章地遵循BNR的书,所以我会坚持这条路线。这是真的。覆盖这些方法是不明智的。但是,由于我已经在上面的代码中,您能告诉我关于allocWithZone的绕过子类版本是错是对的吗。我相信这本书的密码,认为你无法逃脱陷阱。但事实证明你可以。“关键是外部调用方(在您的情况下是main())是否可以绕过singleton实例化,而她不能。(**)”您的意思是,在不干预预先存在的代码(包含allocWithZone实现的子类版本的代码)的情况下,让任何类型的外部函数调用通过“陷阱”就足够了,该陷阱从allocWithZone(子类ver)开始,到sharedStore结束
@interface BNRItemStore : NSObject
+(BNRItemStore *)sharedStore;
@end

@implementation BNRItemStore   
+(BNRItemStore *)sharedStore{
    static BNRItemStore *sharedStore=nil;
    if (!sharedStore){
        sharedStore = [[super allocWithZone:nil] init];
    }
    return sharedStore;
}
@end