zoukankan      html  css  js  c++  java
  • [转]allocWithZone 和 单例模式

    一、问题起源

    一切起源于Apple官方文档里面关于单例(Singleton)的示范代码:Creating a Singleton Instance.

    主要的争议集中在下面这一段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static MyGizmoClass *sharedGizmoManager = nil;
    + (MyGizmoClass*)sharedManager
    {
    if (sharedGizmoManager == nil) {
    sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
     
    return sharedGizmoManager;
    }
     
    + (id)allocWithZone:(NSZone *)zone
    {
    return [[self sharedManager] retain];
    }

    其中:

    1 sharedGizmoManager = [[super allocWithZone:NULL] init];

    这段有另一个版本,不使用 allocWithZone 而是直接 alloc,如下:

    1 sharedGizmoManager = [[super alloc] init];

    这就引发了一个讨论,为什么要覆盖allocWithZone方法,到底 alloc 和 allocWithZone 有啥区别呢?

    PS:关于ObjC单例的实现,@Venj 的这篇博文有比较详细的讨论,包括了线程安全的考虑,有兴趣的童鞋可以围观一下。

    二、allocWithZone

    首先我们知道,我们需要保证单例类只有一个唯一的实例,而平时我们在初始化一个对象的时候, [[Class alloc] init],其实是做了两件事。 alloc 给对象分配内存空间,init是对对象的初始化,包括设置成员变量初值这些工作。而给对象分配空间,除了alloc方法之外,还有另一个方法: allocWithZone.

    在NSObject 这个类的官方文档里面,allocWithZone方法介绍说,该方法的参数是被忽略的,正确的做法是传nil或者NULL参数给它。而这个方法之所以存在,是历史遗留原因。

    Do not override allocWithZone: to include any initialization code. Instead, class-specific versions of init… methods.

    This method exists for historical reasons; memory zones are no longer used by Objective-C.

    文档里面提到,memory zone已经被弃用了,只是历史原因才保留这个接口。详细是什么历史原因我没找到,不过后面介绍的内容会稍微涉及到。

    而实践证明,使用alloc方法初始化一个类的实例的时候,默认是调用了 allocWithZone 的方法。于是覆盖allocWithZone方法的原因已经很明显了:为了保持单例类实例的唯一性,需要覆盖所有会生成新的实例的方法,如果有人初始化这个单例类的时候不走[[Class alloc] init] ,而是直接 allocWithZone, 那么这个单例就不再是单例了,所以必须把这个方法也堵上。allocWithZone的答案到此算是解决了,但是,问题是无止境的。

    这里引出了另外一个问题: What the hell is Memory Zone?

    三、NSZone

    Apple官方文档里面就简单的几句,吝啬得很:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    NSZone
     
    Used to identify and manage memory zones.
     
    typedef struct _NSZone NSZone;
     
    Availability
     
    Available in OS X v10.0 and later.
     
    Declared In
     
    NSZone.h

    CocaDev的wiki就写得详细的多了,原文地址在这里:http://cocoadev.com/wiki/NSZone

    大意上是说NSZone是Apple用来分配和释放内存的一种方式,它不是一个对象,而是使用C结构存储了关于对象的内存管理的信息。基本上开发者是不需要去理会这个东西的,cocoa Application使用一个系统默认的NSZone来对应用的对象进行管理。那么在什么时候你会想要有一个自己控制的NSZone呢?当默认的NSZone里面管理了大量的对象的时候。这种时候,大量对象的释放可能会导致内存严重碎片化,cocoa本身有做过优化,每次alloc的时候会试图去填满内存的空隙,但是这样做的话时间的开销很大。于是乎,你可以自己创建一个NSZone,这样当你有大量的alloc请求的时候就全部转移到指定的NSZone里面去,减少了大量的时间开销。而且,使用NSZone还可以一口气把你创建的zone里面的东西都清除掉,省掉了大量的时间去一个个dealloc对象。

    总的来说,当你需要创建大量的对象的时候,使用NSZone还是能节省一些时间的,不过前提是你得知道怎么去用它。这篇wiki里面也写了NSZone的用法,感兴趣的童鞋可以看看,不过另一篇2002年的文章就说开发者已经不能创建一个真正的NSZone了(看来也许这就是历史原因了),只能创建main zone的一个child zone。文章在这里:http://www.cocoabuilder.com/archive/cocoa/65056-what-an-nszone.html#65056 Timothy J.wood 的回答。

    Timothy还讲到如果可以使用NSZone的话,多个对象在同一时间alloc可以减少分页使用,而且在同一个时间dealloc可以减少内存碎片。想必后来Apple在这方面是做了处理了,对开发者透明,无需开发者自己去做。

    四、结论

    allocWithZone不被Apple鼓励使用,基本上多数时候程序员也不需要自己去管理自己的zone。当然多了解一些东西总是好的嘛。

    单例模式在Cocoa和Cocoa Touch中非常常见。比如这两个,[UIApplication sharedApplication][NSApplication sharedApplication],大家应该都见过。但是我们应该如何在代码中实现一个单例模式呢?

    如果你对苹果的文档很熟悉的话,你一定知道,在Cocoa Foundamentals Guide中有一段实现单例模式的示例代码。大致如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    /* Singleton.h */
    #import <Foundation/Foundation.h>
    @interface Singleton : NSObject
    + (Singleton *)instance;
    @end
    /* Singleton.m */
    #import "Singleton.h"
    static Singleton *instance = nil;
    @implementation Singleton
    + (Singleton *)instance {
        if (!instance) {
            instance = [[super allocWithZone:NULL] init];
        }
        return instance;
    }
    + (id)allocWithZone:(NSZone *)zone {
        return [self instance];
    }
    - (id)copyWithZone:(NSZone *)zone {
        return self;
    }
    - (id)init {
        if (instance) {
            return instance;
        }
        self = [super init];
        return self;
    }
    - (id)retain {
        return self;
    }
    - (oneway void)release {
        // Do nothing
    }
    - (id)autorelease {
        return self;
    }
    - (NSUInteger)retainCount {
        return NSUIntegerMax;
    }
    @end
    

    这是一种很标准的Singleton实现,中规中矩。不过这种实现并不是线程安全的。所以各路大神都各显神威,给出了多种单例模式的实现。

    Matt Gallagher在博客中放出了一个Macro,用来实现单例模式。虽然是一个宏定义的代码,但是具体实现还是很清楚的。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    
    //
    //  SynthesizeSingleton.h
    //  CocoaWithLove
    //
    //  Created by Matt Gallagher on 20/10/08.
    //  Copyright 2009 Matt Gallagher. All rights reserved.
    //
    //  Permission is given to use this source code file without charge in any
    //  project, commercial or otherwise, entirely at your risk, with the condition
    //  that any redistribution (in part or whole) of source code must retain
    //  this copyright and permission notice. Attribution in compiled projects is
    //  appreciated but not required.
    //
    #define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) 
     
    static classname *shared##classname = nil; 
     
    + (classname *)shared##classname 
    { 
        @synchronized(self) 
        { 
            if (shared##classname == nil) 
            { 
                shared##classname = [[self alloc] init]; 
            } 
        } 
         
        return shared##classname; 
    } 
     
    + (id)allocWithZone:(NSZone *)zone 
    { 
        @synchronized(self) 
        { 
            if (shared##classname == nil) 
            { 
                shared##classname = [super allocWithZone:zone]; 
                return shared##classname; 
            } 
        } 
         
        return nil; 
    } 
     
    - (id)copyWithZone:(NSZone *)zone 
    { 
        return self; 
    } 
     
    - (id)retain 
    { 
        return self; 
    } 
     
    - (NSUInteger)retainCount 
    { 
        return NSUIntegerMax; 
    } 
     
    - (void)release 
    { 
    } 
     
    - (id)autorelease 
    { 
        return self; 
    }
    

    然而,eschaton则觉得这些实现都太繁琐了,他给出的实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    @interface SomeManager : NSObject
    + (id)sharedManager;
    @end
    /* 非线程安全的实现 */
    @implementation SomeManager
    + (id)sharedManager {
        static id sharedManager = nil;
        if (sharedManager == nil) {
            sharedManager = [[self alloc] init];
        }
        return sharedManager;
    }
    @end
    /* 线程安全的实现 */
    @implementation SomeManager
    static id sharedManager = nil;
    + (void)initialize {
        if (self == [SomeManager class]) {
            sharedManager = [[self alloc] init];
        }
    }
    + (id)sharedManager {
        return sharedManager;
    }
    @end
    

    关于为什么上述代码就能实现单例模式,以及关于线程安全问题的考量,请参考他的博客

    最后介绍一个比较现代的单例模式实现。为什么说现代呢?因为这种实现利用了GCD(Grand Central Dispatch)和ARC(Automatic Reference Counting)。核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    + (id)sharedInstance
    {
      static dispatch_once_t pred = 0;
      __strong static id _sharedObject = nil;
      dispatch_once(&pred, ^{
        _sharedObject = [[self alloc] init]; // or some other init method
      });
      return _sharedObject;
    }
    

    作者还写了一个宏(gist)来方便使用,大家可以阅读作者的博文A note on Objective-C singletons了解详情。

    大多数情况下,Apple官方文档里的单例模式的示例代码实现已经够用了。虽然它最繁琐,但是也是本文介绍的几种单例模式中最容易理解的一个。至于其他的实现就留给读者们根据需要选择和应用了。

  • 相关阅读:
    三个心态做人做学问 沧海
    成功走职场要找准自己的"快捷键" 沧海
    免费离线下载 拂晓风起
    Hibernate 获取某个表全部记录时 奇怪现象 (重复出现某个记录) 拂晓风起
    无法读取mdb 如果连接不了ACCESS mdb文件,就尝试安装MDAC 拂晓风起
    Netbeans 使用 Hibernate 逆向工程 生成hbm和pojo 拂晓风起
    如何点击单选框 radio 后面的文字,选中单选框 拂晓风起
    Java 连接access 使用access文件 不用配置 拂晓风起
    mysql下如何执行sql脚本 拂晓风起
    Hibernate配置access Hibernate 连接 access 拂晓风起
  • 原文地址:https://www.cnblogs.com/lihuang/p/3711738.html
Copyright © 2011-2022 走看看