Objective-C 싱글 톤은 어떻게 생겼나요? [닫은]


334

내 싱글 톤 접근 자 방법은 일반적으로 다음과 같은 변형입니다.

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

이것을 개선하기 위해 무엇을 할 수 있습니까?


27
전역 변수 선언을 + instance 메소드 (이를 설정하지 않으면 사용해야하는 유일한 위치)로 옮기고 + defaultMyClass 또는 +와 같은 이름을 사용할 수는 있지만 괜찮습니다. 메소드에 대한 sharedMyClass. + 인스턴스는 의도를 밝히지 않습니다.
Chris Hanson

이 질문에 대한 '답변'이 조만간 변경 될 것 같지 않으므로이 질문에 대한 역사적 잠금을 설정하고 있습니다. 두 가지 이유 1) 많은 견해, 투표 및 좋은 내용 2) 공개 / 종료의 요요를 방지하기 위해. 당시에는 큰 질문 이었지만 이러한 유형의 질문은 스택 오버플로에 적합하지 않습니다. 이제 작업 코드를 확인하기위한 코드 검토 가 있습니다. 이 질문에 대한 모든 토론을 이 메타 질문으로 가져 가십시오 .
George Stocker

답변:


207

다른 옵션은이 +(void)initialize방법 을 사용하는 것입니다. 설명서에서 :

런타임은 initialize클래스 바로 전에 한 번만 프로그램의 각 클래스로 보내 거나 클래스에서 상속 된 모든 클래스가 프로그램 내에서 첫 번째 메시지를 보냅니다. 클래스가 사용되지 않으면 메소드가 호출되지 않을 수 있습니다. 런타임은 initialize스레드에 안전한 방식으로 메시지를 클래스에 보냅니다 . 수퍼 클래스는 서브 클래스 전에이 메시지를 수신합니다.

따라서 이와 비슷한 것을 할 수 있습니다.

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

7
만약 런타임이 이것을 한번만 호출한다면, BOOL은 무엇을 하는가? 누군가 코드 에서이 함수를 명시 적으로 호출하는 경우의 예방 조치입니까?
Aftermathew

5
예, 함수를 직접 호출 할 수도 있으므로 예방 조치입니다.
로비 핸슨

33
서브 클래스가있을 수 있기 때문에이 작업 도 필요합니다 . +initialize슈퍼 클래스를 재정의하지 않으면 서브 클래스를 처음 사용하는 경우 구현이 호출됩니다.
Sven

3
@Paul release메소드 를 재정의하고 비울 수 있습니다 . :)

4
@aryaxt : 나열된 문서에서 이미 스레드 안전합니다. 따라서 호출은 런타임마다 한 번입니다. 이것은 정확하고 스레드 안전하며 최적의 효율적인 솔루션 인 것 같습니다.
lilbyrdie

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[출처]


7
이것이 싱글 톤에 일반적으로 사용해야하는 전부입니다. 무엇보다도 클래스를 개별적으로 인스턴스화 할 수 있도록하면 상태를 재설정하는 방법 대신 별도의 인스턴스를 테스트 할 수 있으므로 테스트하기가 더 쉽습니다.
Chris Hanson

3
Stig Brautaset : 아니요.이 예에서 @synchronized를 생략해도 괜찮습니다. 이 정적 함수를 동시에 실행하는 두 스레드의 가능한 경쟁 조건을 처리하고 동시에 "if (! sharedSingleton)"테스트를 통과하여 두 개의 [MySingleton alloc]이 발생합니다. .. @synchronized {scope block}은 가상의 두 번째 스레드가 첫 번째 스레드가 {scope 블록}을 종료하기 전에 첫 번째 스레드가 종료 될 때까지 대기하도록합니다. 이게 도움이 되길 바란다! =)
MechEthan

3
누군가가 여전히 자신의 객체 인스턴스를 만드는 것을 방해하는 것은 무엇입니까? MySingleton *s = [[MySingelton alloc] init];
lindon fox

1
@lindonfox 귀하의 질문에 대한 답변은 무엇입니까?
Raffi Khatchadourian

1
@Raffi-죄송합니다. 답변에 붙여 넣기를 잊어 버린 것 같습니다. 어쨌든, 나는 책을 얻었고 Pro Objective-C Design Patterns for iOS그것은 당신이 "엄격한"singelton을 만드는 방법을 설명합니다. 기본적으로 시작 메소드를 개인용으로 만들 수 없으므로 alloc 및 copy 메소드를 대체해야합니다. 따라서 시도하고 비슷한 작업을 수행 [[MySingelton alloc] init]하면 런타임 오류가 발생하지만 불행히도 컴파일 시간 오류는 발생하지 않습니다. 나는 당신이 구현하는 방법을 모든 객체 생성의 세부 사항,하지만 이해가 안 돼요 + (id) allocWithZone:(NSZone *)zone에서 호출되는sharedSingleton
린든 여우

59

아래의 다른 답변에 따라, 당신이해야한다고 생각합니다.

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}

6
위에서하고있는 모든 일에 신경 쓰지 마십시오. 싱글 톤을 개별적으로 인스턴스화 할 수 있도록하고 공유 / 기본 방법을 사용하십시오. 당신이 한 일은 진정으로 진정으로 수업의 단일 인스턴스를 원할 때만 필요합니다. 당신이하지 않는, 특히. 단위 테스트 용.
Chris Hanson

문제는 이것이 "싱글 톤 생성"을위한 Apple 샘플 코드입니다. 네, 그렇습니다.
Colin Barrett

1
"실제"싱글 톤 (즉, 한 번만 인스턴스화 할 수있는 객체)을 원한다면 Apple 샘플 코드는 정확하지만 Chris가 말했듯이 이것은 거의 원치 않는 것이지만 설정 가능한 공유 인스턴스는 당신이 원하는 것입니다. 보통은 원합니다.
Luke Redpath

여기서, 상기 방법에 대한 매크로이다 gist.github.com/1057420 . 이것이 내가 사용하는 것입니다.
Kobski

1
단위 테스트는 제쳐두고이 솔루션에 대해 아무 말도하지 않습니다. 맞습니까? 그리고 빠르고 안전합니다.
LearnCocos2D

58

이후 켄달 게시 비용을 잠금 방지하기 위해 그 시도 싱글 스레드 세이프를, 내가 아니라 하나를 던져 것이라고 생각 :

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

자, 이것이 어떻게 작동하는지 설명하겠습니다.

  1. 빠른 경우 : 정상 실행 sharedInstance에서 이미 설정되었으므로 while루프가 실행되지 않고 단순히 변수의 존재를 테스트 한 후에 함수가 반환됩니다.

  2. 느린 경우 : sharedInstance존재하지 않는 경우 인스턴스는 Compare And Swap ( 'CAS')을 사용하여 할당되고 복사됩니다.

  3. 경합 경우 : 두 개의 스레드 호출에 모두 시도하면 sharedInstance같은 시간에 sharedInstance 같은 시간에 존재하지 않는 다음 그들이 싱글 모두 초기화 새로운 인스턴스와 위치에 CAS 그것을 시도합니다. 어느 쪽이 CAS에 당첨 되든 즉시 반환, 둘 중 하나는 방금 할당 한 인스턴스를 해제하고 (현재 설정)을 반환합니다 sharedInstance. 단일 OSAtomicCompareAndSwapPtrBarrier은 설정 스레드의 쓰기 장벽과 테스트 스레드의 읽기 장벽 역할을합니다.


18
이것은 응용 프로그램 수명 동안 발생할 수있는 최대 한 번의 완전한 오버 킬입니다. 그럼에도 불구하고, 그것은 정확하고 정확하며 비교 및 ​​스왑 기술은 알아야 할 유용한 도구이므로 +1입니다.
Steve Madsen

정답-OSAtomic 제품군에 대해 알아두면 좋습니다.
Bill

1
@Louis : 놀랍고도 놀라운 깨달음! 그러나 한 가지 질문 : 내 init방법이 당신의 접근법에서 어떻게해야 합니까? sharedInstance초기화 될 때 예외를 던지는 것은 좋은 생각이 아닙니다. 사용자가 init여러 번 직접 전화하지 못하도록하려면 어떻게해야 합니까?
matm September

2
나는 일반적으로 그것을 방지하지 않습니다. 일반적으로 싱글 톤을 곱하여 인스턴스화하는 것을 허용하는 유효한 이유가 종종 있으며, 가장 일반적인 방법은 특정 유형의 단위 테스트에 대한 것입니다. 단일 인스턴스를 실제로 적용하려면 전역 인스턴스가 있는지 확인하기 위해 init 메소드 검사를 수행하고 인스턴스가 있으면 자체 릴리스하고 전역을 반환합니다.
Louis Gerbarg

1
@Tony 비트 응답이 늦었지만 OSAtomicCompareAndSwapPtrBarrier에는 휘발성이 필요합니다. 아마도 volatile 키워드는 컴파일러가 검사를 최적화하지 못하게하는 것입니까? 참조 : stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/...
벤 플린

14
정적 MyClass * sharedInst = nil;

+ (id) shared 인스턴스
{
    @synchronize (자체) {
        if (sharedInst == nil) {
            / * sharedInst 설정 init * /
            [[자체 할당] init];
        }
    }
    return sharedInst;
}

-(ID) 초기화
{
    if (sharedInst! = nil) {
        [NSException raise : NSInternalInconsistencyException
            형식 : @ "[% @ % @]을 (를) 호출 할 수 없습니다. 대신 + [% @ % @] 사용"],
            NSStringFromClass ([self class]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([자체 클래스]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = 자기;
        / * 여기에 특정 클래스가 무엇이든 * /
    }
    return sharedInst;
}

/ * 이것들은 아마 아무것도하지 않습니다
   GC 앱. 싱글 톤 유지
   실제 싱글 톤으로
   비 CG 앱
* /
-(NSUInteger) 유지 횟수
{
    NSUIntegerMax를 반환합니다.
}

-(일방 통행) 해제
{
}

-(id) 유지
{
    return sharedInst;
}

-(ID) 자동 출시
{
    return sharedInst;
}

3
[[self alloc] init]sharedInst에 결과를 할당하지 않으면 clang이 누출에 대해 불평하는 것으로 나타났습니다 .
pix0r

이와 같은 init을 파괴하는 것은 꽤 못생긴 접근법입니다. init 및 / 또는 실제 객체 생성을 망설이지 마십시오. 단일 인스턴스를 개체에 하드 베이킹하지 않고 공유 인스턴스에 대한 제어 된 액세스 지점을 사용하는 경우 나중에 테스트 등을 작성하면 더 행복한 시간이 있습니다. 하드 단일 개체가 너무 많이 사용됩니다.
occulus

12

편집 :이 구현은 ARC에서 더 이상 사용되지 않습니다. ARC와 호환되는 Objective-C 싱글 톤을 어떻게 구현합니까?를 살펴보십시오 . 올바른 구현을 위해.

다른 답변에서 읽은 초기화의 모든 구현은 일반적인 오류를 공유합니다.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

Apple 설명서는 초기화 블록에서 클래스 유형을 확인하도록 권장합니다. 서브 클래스는 기본적으로 initialize를 호출하기 때문입니다. KVO를 통해 서브 클래스를 간접적으로 생성 할 수있는 명백한 경우가 있습니다. 다른 클래스에 다음 줄을 추가하는 경우 :

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Objective-C는 내재적으로 MySingletonClass의 서브 클래스를 작성하여 두 번째 트리거를 발생 +initialize시킵니다.

다음과 같이 init 블록에서 중복 초기화를 암시 적으로 확인해야한다고 생각할 수 있습니다.

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

그러나 당신은 발에 자신을 쏠 것입니다; 또는 다른 개발자에게 발을 쏠 기회를 줄 수도 있습니다.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL; DR, 여기 내 구현입니다

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

ZAssert를 자체 어설 션 매크로 또는 NSAssert로 바꿉니다.


1
나는 단지 더 단순하게 살고 초기화를 피할 것입니다.
톰 앤더슨


9

스레드 안전하지만 초기화 후에 잠기지 않는 sharedInstance에 흥미로운 변형이 있습니다. 나는 요청에 따라 최상위 답변을 수정하기에 아직 확실하지 않지만 추가 토론을 위해 제시합니다.

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

1
정말 흥미로운 +1입니다. 의 복제본으로 class_replaceMethod변환 하는 데 사용할 수 있습니다 . 그렇게하면 잠금 장치를 다시 구입하는 것에 대해 걱정할 필요가 없습니다 . sharedInstancesimpleSharedInstance@synchronized
Dave DeLong

exchangeImplementations를 사용하는 것도 같은 효과입니다. init를 호출 할 때 초기화 한 후에는 실제로 simpleSharedInstance를 호출한다는 의미입니다. 사실 replaceMethod로 시작하지만, 필요한 경우 원래는 여전히 존재하므로 더 나은 단지에 있었다 주위의 구현을 전환했다 ...
켄달 Helmstetter Gelner

추가 테스트에서 replaceMethod를 작동시킬 수 없었습니다. 반복 호출에서 코드는 여전히 simpleSharedInstance 대신 원래 sharedInstance를 호출했습니다. 둘 다 클래스 수준의 메소드이기 때문일 수 있습니다 ... 내가 사용한 대체는 다음과 같습니다. class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); 및 이들의 일부 변형. 내가 게시 한 코드를 확인할 수 있으며 sharedInstance를 처음 통과 한 후 simpleSharedInstance가 호출됩니다.
Kendall Helmstetter Gelner

런타임 뭉치를하지 않고 초기화 후 잠금 비용을 지불하지 않는 스레드 안전 버전을 만들 수 있습니다. 아래에 구현을 게시했습니다.
Louis Gerbarg

1
좋은 아이디어 +1. 나는 런타임으로 할 수있는 일을 좋아합니다. 그러나 대부분의 경우 이것은 아마도 조기 최적화입니다. 동기화 비용을 제거해야 할 경우 Louis의 잠금없는 버전을 사용했을 것입니다.
Sven

6

짧은 대답 : 훌륭합니다.

긴 대답 : 뭔가 ...

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

+ (id) allocWithZone:(NSZone *)paramZone {
    return [[self instance] retain];
}

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

dispatch / once.h 헤더 를 읽고 무슨 일이 일어나고 있는지 이해하십시오. 이 경우 문서 나 맨 페이지보다 헤더 설명이 더 적합합니다.


5

싱글 톤을 클래스로 굴렸으므로 다른 클래스는 싱글 톤 속성을 상속 할 수 있습니다.

Singleton.h :

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m :

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

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

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

그리고 여기 당신이 싱글 톤이되고 싶은 클래스의 예가 있습니다.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Singleton 클래스에 대한 유일한 제한은 NSObject 서브 클래스라는 것입니다. 그러나 대부분의 코드에서 싱글 톤을 사용하는 것은 실제로 NSObject 서브 클래스이므로이 클래스는 실제로 내 인생을 편하게하고 코드를 더 깨끗하게 만듭니다.


@synchronized엄청나게 느리고 피해야 하기 때문에 다른 잠금 메커니즘을 사용하고 싶을 수도 있습니다 .
DarkDust

2

이것은 가비지 수집 환경에서도 작동합니다.

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


+(id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            sharedMySingleton = [super allocWithZone:zone];
            return sharedMySingleton;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


-(void)dealloc {
    [super dealloc];
}

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


-(id)retain {
    return self;
}


-(unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be release
}


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end

2

이것은 스레드 안전성이 아니며 첫 번째 호출 후 비싼 잠금을 피합니까?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

2
여기에 사용 된 이중 확인 잠금 기술은 일부 환경에서 종종 실제 문제입니다 ( aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 또는 Google it 참조 ). 다르게 표시 될 때까지 Objective-C가 면역성이 없다고 가정합니다. wincent.com/a/knowledge-base/archives/2006/01/… 도 참조하십시오 .
Steve Madsen


2

어때요?

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

초기화 후 동기화 비용을 피할 수 있습니까?


다른 답변에서 이중 점검 잠금에 대한 토론을 참조하십시오.
i_am_jorf


1

KLSingleton은 다음과 같습니다.

  1. 하위 분류 가능 (n도)
  2. 아크 호환
  3. 와 안전 allocinit
  4. 느리게로드
  5. 스레드 안전
  6. 잠금 해제 (@synchronize가 아닌 + 초기화 사용)
  7. 매크로 프리
  8. 스위블 프리
  9. 단순한

KLSingleton


1
프로젝트에 NSSingleton을 사용하고 있으며 KVO와 호환되지 않는 것 같습니다. 문제는 KVO가 접두사 NSKVONotifying_ MyClass 를 사용하여 모든 KVO 객체에 대한 서브 클래스를 생성한다는 것입니다 . 그리고 MyClass + initialize 및 -init 메소드를 두 번 호출합니다.
Oleg Trakhman

최신 Xcode에서 이것을 테스트했으며 KVO 이벤트를 등록하거나 수신하는 데 아무런 문제가 없었습니다. 다음 코드를 사용하여이를 확인할 수 있습니다. gist.github.com/3065038 Twitter에서 언급했듯이 + initialize 메소드는 NSSingleton에 대해 한 번, 각 서브 클래스에 대해 한 번 호출됩니다. 이것은 Objective-C의 속성입니다.
kevinlawler

당신이 추가하면 NSLog(@"initialize: %@", NSStringFromClass([self class]));받는 +initialize방법 당신은 클래스가 한 번만 초기화되는 것을 확인할 수 있습니다.
kevinlawler

NSLog (@ "초기화 : % @", NSStringFromClass ([self class]));
Oleg Trakhman

IB와 호환되도록 할 수도 있습니다. 광산은 : stackoverflow.com/questions/4609609/…
Dan Rosenstark

0

자체 동기화를 원하지 않습니다 ... 자체 개체가 아직 없기 때문에! 임시 id 값을 잠 그게됩니다. 다른 사람이 클래스 메소드 (sharedInstance, alloc, allocWithZone : 등)를 실행할 수 없도록하려면 클래스 오브젝트를 대신 동기화해야합니다.

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

1
나머지 메소드, 접근 자 메소드, 뮤 테이터 메소드 등은 자체적으로 동기화되어야합니다. 모든 class (+) 메소드와 이니셜 라이저 (그리고 아마도 -dealloc)는 클래스 객체에서 동기화되어야합니다. 접근 자 / 돌연변이 방법 대신 Objective-C 2.0 속성을 사용하는 경우 수동으로 동기화하지 않아도됩니다. 모든 object.property 및 object.property = foo는 자동으로 자동 동기화됩니다.
Rob Dotson

3
self클래스 메소드에 오브젝트가 존재하지 않는다고 생각하는 이유를 설명하십시오 . 런타임은 self모든 메소드 (클래스 또는 인스턴스) 와 동일한 값을 기반으로 호출 할 메소드 구현을 결정합니다 .
드림 락스

2
클래스 메소드의 내부 self 입니다 클래스 객체. 직접 해보십시오 :#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs

0

나는 이것을 잃어 버리지 않기 위해 여기에두고 싶었습니다. 이것의 장점은 InterfaceBuilder에서 사용할 수 있다는 것인데, 이는 큰 이점입니다. 이것은 내가 물었던 또 다른 질문에서 가져온 것입니다 .

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}

0
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end

0

나는이 "질문"에 대해 많은 의견이 있다는 것을 알고 있지만, 많은 사람들이 싱글 톤을 정의하기 위해 매크로를 사용하도록 제안하는 것을 보지 못한다. 그것은 일반적인 패턴이며 매크로는 싱글 톤을 크게 단순화시킵니다.

내가 본 몇 가지 Objc 구현을 기반으로 작성한 매크로는 다음과 같습니다.

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

사용 예 :

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

인터페이스 매크로가 거의 비어있는 이유는 무엇입니까? 헤더와 코드 파일 간의 코드 일관성; 더 많은 자동 방법을 추가하거나 변경하려는 경우 유지 관리 기능.

나는 initialize 메소드를 사용하여 가장 인기있는 답변 (쓰기 시점)에서 사용되는 싱글 톤을 만듭니다.


0

Objective C 클래스 메소드를 사용하면 다음과 같은 일반적인 방법으로 싱글 톤 패턴을 사용하지 않아도됩니다.

[[Librarian sharedInstance] openLibrary]

에:

[Librarian openLibrary]

Class Methods 만있는 다른 클래스 안에 클래스를 래핑하면 인스턴스를 만들지 않으므로 실수로 중복 인스턴스를 만들 가능성이 없습니다!

나는 여기에 더 자세한 블로그를 썼습니다 :)


링크가 더 이상 작동하지 않습니다.
i_am_jorf

0

@ robbie-hanson에서 예제를 확장하려면 ...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}

0

내 방법은 다음과 같이 간단합니다.

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

싱글 톤이 이미 초기화 된 경우 LOCK 블록이 입력되지 않습니다. 두 번째 검사 if (! initialized)는 현재 스레드가 LOCK을 얻을 때 아직 초기화되지 않았는지 확인하는 것입니다.


표시하는 것이 분명하지 않다 initialized로하는 것은 volatile충분하다. aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf를 참조하십시오 .
i_am_jorf

0

모든 솔루션을 읽지 않았 으므로이 코드가 중복되면 용서하십시오.

이것은 제 생각에 가장 안전한 스레드 구현입니다.

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}

-4

나는 보통 벤 호프 슈타인의 답변과 비슷한 코드를 사용합니다 (또한 Wikipedia에서 나왔습니다). 나는 Chris Hanson이 그의 의견에서 언급 한 이유로 그것을 사용합니다.

그러나 때로는 싱글 톤을 NIB에 배치해야 할 필요가 있으며이 경우 다음을 사용합니다.

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

-retain위 코드는 가비지 수집 환경에서 필요한 모든 것이지만 독자 등에 게 구현을 남겨 둡니다 .


2
코드가 스레드 안전하지 않습니다. alloc 메소드에서는 동기화를 사용하지만 init 메소드에서는 동기화되지 않습니다. 초기화 된 부울을 확인하는 것은 스레드로부터 안전하지 않습니다.
Mecki 2016 년

-5

수락 된 답변은 컴파일되었지만 올바르지 않습니다.

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

Apple 문서 당 :

... 비슷한 방법을 사용하여 self 대신 Class 객체를 사용하여 연결된 클래스의 클래스 메서드를 동기화 할 수 있습니다.

자기 일을 사용하더라도 그렇게해서는 안되며 이것은 복사하여 붙여 넣는 실수처럼 보입니다. 클래스 팩토리 메소드의 올바른 구현은 다음과 같습니다.

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

6
자기가 가장 확실 하지 그것을 클래스 범위 존재한다. 클래스의 인스턴스 대신 클래스를 나타냅니다. 클래스는 (주로) 일류 객체입니다.
schwa

왜 @synchroninzed WITHIN을 메소드에 넣는가?
user4951

1
슈 이미 말했듯이, self 클래스 메소드의 클래스 객체의 내부.이것을 보여주는 스 니펫에 대한 내 의견참조하십시오 .
jscs

self존재하지만 식별자로 전달 @synchronized하여 인스턴스의 메소드에 대한 액세스를 동기화합니다. @ user490696이 지적했듯이 클래스 객체를 사용하는 것이 바람직한 경우 (단일 톤과 같은) 경우가 있습니다. Obj-C 프로그래밍 가이드 :You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
quellish
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.