iOS中的Singleton Objective C不会阻止多个实例

时间:2015-06-12 09:28:15

标签: ios objective-c singleton

我知道这有几个主题,但没有人回答我的问题。

我已经像这样实现了我的单身人士课程(意识到关于单身人士的争议):

var code = 'The Code';
var text = 'The Text';
var ip = 'The IP';

document.write('<table><tr><th>Name</th><td><a href="javascript:alert(\''+code+'\')">' + text + '</a></td></tr><tr><th>IP Address</th><td>' + ip + '</td></tr><tr><th>Code</th><td><a href="javascript:alert(\'empty string\');">' + code + '</a></td></tr></table>');

我尝试实例化一个不同的对象,并将它与sharedInstance返回的对象与'=='进行比较,它们确实不同。

问题:

  1. 不应该创建单个类的多个对象吗?这不是重点吗? Java中的单例实现可以防止它。
  2. 如果是这样,怎么样?我应该创建一个设置方法并调用它而不是实现init并执行它吗?
  3. 这是正确的实施吗?

9 个答案:

答案 0 :(得分:14)

您的观察是正确的,您在Objective-C中看到的许多“单例”模式根本不是单例,而是可以创建其他实例的“共享实例”模型。

在旧的MRC时代,Apple曾经有过示例代码,展示了如何实现真正的单身人士。

您拥有的代码是ARC和线程安全单例的推荐模式,您只需将其放在init方法中:

- (instancetype) init
{
   static MyClass *initedObject;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      initedObject = [super init];
   });
   return initedObject;
}

此代码将确保只有MyClass的实例,无论进行了多少次[MyClass new][[MyClass alloc] init]次调用。

这就是你需要所做的一切,但你可以走得更远。首先,如果您希望有一个类方法来返回单例,那么它只是:

+ (instancetype) singletonInstance
{
   return [self new];
}

此方法最终调用init,返回单例,并在需要时创建它。

如果MyClass实施NSCopying,那么您还需要实施copyWithZone: - 这是copy调用的方法。你有一个单身人士,这很简单:

- (instancetype) copyWithZone:(NSZone *)zone
{
   return self;
}

最后,在Objective-C中,分配新对象实例并对其进行初始化的操作是不同的。上述方案确保只初始化和使用MyClass的一个实例,但是对于newalloc的每次调用,都会分配另一个实例,然后由init立即丢弃并清除通过ARC。这有点浪费!

通过实施allocWithZone:(与上面的copy类似,alloc实际上最终调用的方法)遵循与init相同的模式,可以轻松解决这个问题:

+ (instancetype) allocWithZone:(NSZone *)zone
{
   static MyClass *allocatedObject;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      allocatedObject = [super allocWithZone:zone];
   });
   return allocatedObject;
}

第一次创建实例时,allocWithZone:将分配它,然后init将初始化它,所有后续调用将返回已存在的对象。没有丢弃不需要的分配。

就是这样,一个真正的单身人士,并不比那些常见的人造单身人士更难。

HTH

答案 1 :(得分:5)

您不能将init方法设为私有,就像使用构造函数在Java中一样。因此,没有什么可以阻止你调用[[MyClass alloc] init]来确实创建一个不同的对象。只要你不这样做,但坚持使用sharedInstance方法,你的实现就可以了。

你可以做什么:让init方法引发异常(例如使用[self doesNotRecognizeSelector:@_cmd])并以不同的方法(例如privateInit)执行初始化头文件。

答案 2 :(得分:3)

使用objective-c,可以防止单例类创建多个对象。您可以使用单例类来阻止alloc和init调用。

#import <Foundation/Foundation.h>

@interface SingletonClass : NSObject

+ (id) sharedInstance;
- (void) someMethodCall;
- (instancetype) init __attribute__((unavailable("Use +[SingletonClass sharedInstance] instead")));
+ (instancetype) new __attribute__ ((unavailable("Use +[SingletonClass sharedInstance] instead")));

@end


#import "SingletonClass.h"

@implementation SingletonClass

+ (id) sharedInstance{
    static SingletonClass * sharedObject = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        sharedObject = [[self alloc] initPrivate];
    });

    return sharedObject;
}

- (instancetype)init {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"You can't override the init call in class %@", NSStringFromClass([self class])] userInfo:nil];
}

- (instancetype)initPrivate {
    if (self = [super init]) {

    }
    return self;
}


- (void) someMethodCall{
    NSLog(@"Method Call");
}

@end

1#如果您尝试在SingletonClass上调用init或new方法,则无法调用这些方法。

2#如果您在头文件中注释掉下面提到的方法并尝试在SingletonClass方法上调用init,那么应用程序将崩溃,原因是“您无法覆盖类SingletonClass中的init调用”。

   - (instancetype) init __attribute__((unavailable("Use +[SingletonClass sharedInstance] instead")));
   + (instancetype) new __attribute__ ((unavailable("Use +[SingletonClass sharedInstance] instead")));

只需使用此代码为Singleton Pattern创建单个对象,并防止对来自其他类的singleton模式的alloc init调用。我用xCode 7.0+测试了这段代码并且工作正常。

答案 3 :(得分:2)

要防止创建单个类的多个对象,您需要执行以下操作。你可以很好地创建单例对象。但是在调用init,copy,mutable copy时,你需要处理这种方式。

- (instancetype)init{

    if (!_sharedInstance) {
        _sharedInstance = [MyClass sharedInstance];
    }
    return _sharedInstance;
}




- (id)copy{

        if (!_sharedInstance) {
            _sharedInstance = [MyClass sharedInstance];
        }
        return _sharedInstance;
    }

同样的东西也是可变副本。所以这个实现确保一旦一个实例可用..

愿这对你有所帮助。

答案 4 :(得分:1)

@VeryPoliteNerd只需在init上将new.h方法标记为不可用:

- (instancetype)init __attribute__((unavailable("Use +[MyClass sharedInstance] instead")));

+ (instancetype)new __attribute__((unavailable("Use +[MyClass sharedInstance] instead")));

如果调用者尝试手动实例化此对象,这将导致编译器抱怨

答案 5 :(得分:0)

调用alloc / init来获取Singleton类的第二个实例被认为是一个明显的编程错误。为了避免这种编程错误,你不要编写复杂的代码来防止它,你做代码审查并告诉每个人都试图这样做,就像你遇到任何编程错误一样。

答案 6 :(得分:0)

这对我有用:

static AudioRecordingGraph * __strong sharedInstance;

+(instancetype)sharedInstance {

    @synchronized(self) {

        if(!sharedInstance) {

            sharedInstance = [AudioRecordingGraph new];
        }
        return sharedInstance;
    }
}

答案 7 :(得分:0)

我想为现代的Objective-C单例实现最佳Swift互操作性添加观察。但是,让我从代码开始。我们先假设该类是用于管理一系列请求的,所以我可以称其为RequestManager

// RequestManager.h

NS_ASSUME_NONNULL_BEGIN

@interface RequestManager : NSObject

@property (nonatomic, strong, readonly, class) RequestManager *sharedManager;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)copy NS_UNAVAILABLE;
- (instancetype)mutableCopy NS_UNAVAILABLE;

- (void)someMethod;

@end

NS_ASSUME_NONNULL_END

// RequestManager.m

@implementation RequestManager

+ (instancetype)sharedManager {
    static RequestManager *_sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        //setup code
    }
    return self;
}

- (void)someMethod { ... }

@end

注意:

  1. 注意,我将单例设为类属性而不是类方法。

    从Objective-C方面来说,这是没有区别的区别,但是从Swift来说,您可以做MyClass.shared之类的事情而不会受到()的干扰。这是苹果公司所有单身人士都采用的模式。

  2. 一个人可能会给单身人士一个更有意义的名称。例如,如果类是RequestManager,则单例可能称为sharedManager,而不是sharedInstance

    如果执行此操作,Swift将自动检测实例名称的显着部分,并将其以shared而非sharedInstance或其他任何形式显示给Swift。

    如果您确实要在Objective-C代码中使用名称sharedInstance,则只需提供NS_SWIFT_NAME的显式shared即可采用美观,简洁和Swift中一致的单例命名惯例:

     @property (nonatomic, strong, readonly, class) RequestManager *sharedInstance NS_SWIFT_NAME(shared);
    
  3. 请注意NS_UNAVAILABLE的使用。这样可以防止呼叫者意外实例化自己的副本。例如。来自Objective-C:

    enter image description here

    或者从Swift:

    enter image description here

  4. 也许不用说,我已经审核了此方法的可空性,即使用NS_ASSUME_NONNULL_BEGIN / END来将默认值设置为非空。或者,显然,您可以将单例属性标记为nonnull

答案 8 :(得分:-2)

这对我有用:

static DataModel *singleInstance;

+ (DataModel*)getInstance{
    if (singleInstance == nil) {
        singleInstance = [[super alloc] init];
    }
    return singleInstance;
}

您可以使用

进行调用
_model = [DataModel getInstance];