ARC와 호환되는 Objective-C 싱글 톤을 어떻게 구현합니까?


172

Xcode 4.2에서 자동 참조 카운팅 (ARC)을 사용할 때 올바르게 컴파일되고 동작하는 싱글 톤 클래스를 어떻게 변환 (또는 작성)합니까?


1
나는 최근 Matt Galloway의 기사가 ARC 및 수동 메모리 관리 환경 모두에서 Singletons에 대해 심층적으로 다루고 있음을 발견했습니다. galloway.me.uk/tutorials/singleton-classes
cescofry

답변:


391

당신이 이미하고있는 것과 똑같은 방식으로 :

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

9
그냥 메모리 관리 날조의 유치장을 수행하지 않는 것은 애플에서 추천하는 데 사용 developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
크리스토퍼 Pickslay

1
@MakingScienceFictionFact, 당신은 이 게시물을
kervich

6
static메소드 / 함수 내에 선언 된 @David 변수는 메소드 / 함수 static외부에 선언 된 변수 와 동일하며 해당 메소드 / 함수의 범위 내에서만 유효합니다. +sharedInstance메소드를 통한 모든 개별 실행 (다른 스레드에서도)은 동일한 sharedInstance변수를 '볼'것 입니다.
Nick Forge

9
누군가가 [[MyClass alloc] init]를 호출하면 어떨까요? 그것은 새로운 객체를 만들 것입니다. 이것을 피할 수있는 방법 (정적 MyClass * sharedInstance = nil을 메소드 외부에서 선언하는 것 제외).
Ricardo Sanchez-Saez

2
다른 프로그래머가 sharedInstance 또는 이와 유사한 것을 호출해야 할 때 엉망이되고 init를 호출하면 오류입니다. 실수를 저지르는 다른 사람들을 막기 위해 언어의 기본과 기본 계약을 바꾸는 것은 꽤 잘못된 것 같습니다. 더 논의에있어 boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus

8

필요에 따라 다른 인스턴스를 작성하려면 다음을 수행하십시오.

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

그렇지 않으면 다음을 수행해야합니다.

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

1
참 / 거짓 :이 dispatch_once()비트는 첫 번째 예에서도 추가 인스턴스를 얻지 못한다는 것을 의미합니다.
Olie

4
@Olie : 클라이언트 코드가 액세스를 [[MyClass alloc] init]우회하고 무시할 수 있으므로 False sharedInstance입니다. DongXu, Peter Hosey의 Singleton 기사를 보아야 합니다. 재정의 위하여려고하는 경우에 allocWithZone:생성되는 이상의 인스턴스를 방지하기 위해, 당신은 또한 오버라이드 (override) 할 필요가 init다시 초기화되는 것을 공유 인스턴스를 방지 할 수 있습니다.
jscs

좋아, 그것이 내가 생각한 것이므로 allocWithZone:버전입니다. 고마워.
Olie

2
이것은 allocWithZone의 계약을 완전히 위반합니다.
occulus

1
singleton은 단지 "언제든지 메모리에있는 하나의 객체"를 의미합니다. 이것은 다시 초기화되는 것이 또 다른 것입니다.
DongXu

5

ARC 및 비 ARC 용 버전입니다.

사용하는 방법:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end

2

이것은 ARC에서 제 패턴입니다. GCD를 사용하여 새로운 패턴을 만족시키고 Apple의 오래된 인스턴스화 방지 패턴도 만족시킵니다.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end

1
이것이 '슈퍼 클래스 c1의 인스턴스가 되지 AAA않습니까? 당신은 호출 할 필요 +allocself하지에 super.
Nick Forge

@NickForge super는 수퍼 클래스 객체를 의미하지 않습니다. 수퍼 클래스 객체를 얻을 수 없습니다. 메시지를 수퍼 클래스 버전의 메서드로 라우팅하는 것입니다. super여전히 포인트 self클래스. 수퍼 클래스 객체를 얻으려면 런타임 리플렉션 함수를 가져와야합니다.
eonil

@NickForge And -allocWithZone:메소드는 오버라이드 포인트를 제공하기위한 런타임 할당 함수에 대한 간단한 체인입니다. 따라서 궁극적으로 selfpointer == 현재 클래스 객체가 할당 자에게 전달되고 마지막으로 AAA인스턴스가 할당됩니다.
eonil

당신은 맞습니다, 나는 super클래스 메소드에서 작동 하는 방식 의 미묘함을 잊어 버렸습니다 .
Nick Forge

# import를 <objc / objc-runtime.h>를 사용해야합니다
라이언 Heitner

2

이 답변을 읽고 다른 답변을 읽으십시오.

솔루션을 이해하지 못하는 것보다 싱글 톤의 의미와 요구 사항이 무엇인지 이해해야합니다.

싱글 톤을 성공적으로 만들려면 다음 3을 수행 할 수 있어야합니다.

  • 경쟁 조건 이있는 경우 여러 SharedInstance 인스턴스를 동시에 만들 수 없어야합니다!
  • 여러 번의 호출 중 가치를 기억하고 유지하십시오.
  • 한 번만 작성하십시오. 진입 점을 제어합니다.

dispatch_once_t블록을 한 번만 전달 하여 경쟁 조건 을 해결하는 데 도움이됩니다 .

Static여러 번의 호출에서 값을 "기억"하는 데 도움이됩니다. 어떻게 기억합니까? 정확한 이름의 sharedInstance를 가진 새 인스턴스를 다시 만들 수는 없으며 원래 만들어진 인스턴스와 만 작동합니다.

sharedInstance 클래스에서 호출을 사용하지 않고alloc init (즉 alloc init, NSObject 서브 클래스이므로 메소드를 사용하지 않아야 함) +(instancetype)sharedInstance, 다른 스레드의 다중 시도에 관계없이 한 번만 시작 하도록 제한 되는을 사용하여이를 달성 합니다. 동시에 그 가치를 기억하십시오.

코코아 자체와 함께 제공되는 가장 일반적인 시스템 싱글 톤은 다음과 같습니다.

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

기본적으로 중앙 집중식 효과가 필요한 것은 일종의 싱글 톤 디자인 패턴을 따라야합니다.


1

또는 Objective-C는 NSObject 및 모든 하위 클래스에 + (void) initialize 메소드를 제공합니다. 항상 클래스의 메소드보다 먼저 호출됩니다.

iOS 6에서 중단 점을 한 번에 설정했으며 dispatch_once가 스택 프레임에 나타났습니다.


0

싱글 톤 클래스 : 어떤 경우에도 또는 어떤 방식으로도 둘 이상의 클래스 객체를 만들 수 없습니다.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}

1
누군가 init를 호출하면 init은 sharedInstance를 호출하고 sharedInstance는 init를 호출하고 init는 다시 sharedInstance를 호출 한 다음 충돌합니다! 첫째, 이것은 무한 재귀 루프입니다. 둘째, dispatch_once 호출의 두 번째 반복은 dispatch_once 내부에서 다시 호출 할 수 없기 때문에 중단됩니다.
척 크루 팅거

0

허용되는 답변에는 두 가지 문제가 있으며, 귀하의 목적과 관련이있을 수도 있고 그렇지 않을 수도 있습니다.

  1. init 메소드에서 어떻게 든 sharedInstance 메소드가 다시 호출되면 (예를 들어 싱글 톤을 사용하는 다른 객체가 구성되어 있기 때문에) 스택 오버플로가 발생합니다.
  2. 클래스 계층의 경우 계층의 콘크리트 클래스 당 하나의 싱글 톤이 아닌 단일 톤 (즉, sharedInstance 메소드가 호출 된 계층의 첫 번째 클래스) 만 있습니다.

다음 코드는이 두 문제를 모두 처리합니다.

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}

-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

위의 코드가 도움이 될 것입니다.


-2

싱글 톤을 신속하게 만들어야하는 경우

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

또는

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

이 방법으로 사용할 수 있습니다

let sharedClass = LibraryAPI.sharedInstance
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.