Chiếc singleton Objective-C của tôi trông như thế nào? [đóng cửa]


334

Phương thức truy cập đơn lẻ của tôi thường là một số biến thể của:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Tôi có thể làm gì để cải thiện điều này?


27
Những gì bạn có là ổn, mặc dù bạn có thể di chuyển khai báo biến toàn cục vào phương thức + của bạn (nơi duy nhất cần sử dụng, trừ khi bạn cho phép đặt nó cũng như vậy) và sử dụng tên như + defaultMyClass hoặc + sharedMyClass cho phương pháp của bạn. + dụ không tiết lộ ý định.
Chris Hanson

Vì không chắc là "câu trả lời" cho câu hỏi này sẽ sớm thay đổi, nên tôi đang đặt một câu hỏi lịch sử cho câu hỏi này. Hai lý do 1) Rất nhiều lượt xem, phiếu bầu và nội dung tốt 2) Để ngăn chặn việc mở / đóng. Đó là một câu hỏi tuyệt vời cho thời đại của nó, nhưng câu hỏi thuộc loại này không phù hợp với Stack Overflow. Bây giờ chúng tôi có Mã đánh giá để kiểm tra mã làm việc. Hãy đưa tất cả các cuộc thảo luận về câu hỏi này cho câu hỏi meta này .
George Stocker

Câu trả lời:


207

Một lựa chọn khác là sử dụng +(void)initializephương pháp. Từ tài liệu:

Thời gian chạy gửi initializeđến mỗi lớp trong một chương trình chính xác một lần ngay trước lớp hoặc bất kỳ lớp nào kế thừa từ nó, được gửi tin nhắn đầu tiên từ bên trong chương trình. (Do đó phương thức có thể không bao giờ được gọi nếu lớp không được sử dụng.) Thời gian chạy gửi initializetin nhắn đến các lớp theo cách an toàn luồng. Các siêu lớp nhận được thông báo này trước các lớp con của chúng.

Vì vậy, bạn có thể làm một cái gì đó giống như thế này:

static MySingleton *sharedSingleton;

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

7
Nếu thời gian chạy sẽ chỉ gọi điều này một lần, BOOL sẽ làm gì? Là một biện pháp phòng ngừa trong trường hợp có ai đó gọi chức năng này một cách rõ ràng từ mã của họ?
Aftermathew

5
Vâng, đó là một biện pháp phòng ngừa vì chức năng cũng có thể được gọi trực tiếp.
Robbie Hanson

33
Điều này cũng được yêu cầu bởi vì có thể có các lớp con. Nếu họ không ghi đè lên +initializethực hiện superclasses của họ sẽ được gọi nếu phân lớp được sử dụng đầu tiên.
Sven

3
@Paul bạn có thể ghi đè releasephương thức và làm cho nó trống. :)

4
@aryaxt: Từ các tài liệu được liệt kê, đây đã là chủ đề an toàn. Vì vậy, cuộc gọi là một lần trên mỗi thời gian chạy. Điều này dường như là đúng, thread-an toàn, giải pháp tối ưu hiệu quả.
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

[Nguồn]


7
Đây là tất cả những gì bạn thường nên sử dụng cho singletons. Trong số những thứ khác, giữ cho lớp học của bạn riêng biệt làm cho instantiable họ dễ dàng hơn để kiểm tra, bởi vì bạn có thể kiểm tra các trường hợp riêng biệt thay vì có một cách để thiết lập lại trạng thái của họ.
Chris Hanson

3
Stig Brautaset: Không, nó không phải là không quan trọng để bỏ qua @synchronized trong ví dụ này. Nó ở đó để xử lý tình trạng chủng tộc có thể có của hai luồng thực thi chức năng tĩnh này cùng một lúc, cả hai đều vượt qua bài kiểm tra "if (! SharedSingleton)" cùng một lúc, và do đó dẫn đến hai [MySingleton alloc]. .. các @synchronized {phạm vi khối} lực lượng sợi thứ hai giả thuyết để chờ cho thread đầu tiên để thoát khỏi {khối phạm vi} trước khi được phép làm thủ tục vào nó. Tôi hi vọng cái này giúp được! =)
MechEthan

3
Điều gì ngăn ai đó vẫn tạo ra đối tượng của riêng họ? MySingleton *s = [[MySingelton alloc] init];
cáo lindon

1
@lindonfox Câu trả lời cho câu hỏi của bạn là gì?
Raffi Khatchadourian

1
@Raffi - xin lỗi tôi nghĩ rằng tôi đã quên dán vào câu trả lời của tôi. Dù sao, tôi đã nhận được cuốn sách Pro Objective-C Design Patterns for iOSvà nó nói lên cách bạn tạo ra một singelton "nghiêm ngặt". Về cơ bản vì bạn không thể đặt các phương thức khởi tạo thành riêng tư, bạn cần ghi đè lên các phương thức phân bổ và sao chép. Vì vậy, nếu bạn cố gắng và làm một cái gì đó giống như [[MySingelton alloc] init]bạn sẽ gặp lỗi thời gian chạy (mặc dù không phải là lỗi thời gian biên dịch không may). Tôi không hiểu làm thế nào tất cả các chi tiết của việc tạo đối tượng, nhưng bạn thực hiện + (id) allocWithZone:(NSZone *)zonecái được gọi làsharedSingleton
lindon cáo

59

Theo câu trả lời khác của tôi dưới đây, tôi nghĩ bạn nên làm:

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

6
Đừng bận tâm với tất cả những gì bạn đang làm ở trên. Làm cho các singletons (hy vọng cực kỳ ít) của bạn trở nên riêng biệt ngay lập tức và chỉ cần có một phương thức chia sẻ / mặc định. Những gì bạn đã làm chỉ cần thiết nếu bạn thực sự, thực sự, CHỈ muốn một trường hợp duy nhất của lớp bạn. Mà bạn không, đặc biệt. cho các bài kiểm tra đơn vị.
Chris Hanson

Vấn đề là đây là Apple mẫu mã cho "tạo ra một singleton". Nhưng yeah, bạn hoàn toàn đúng.
Colin Barrett

1
Mã mẫu của Apple là chính xác nếu bạn muốn một singleton "thật" (nghĩa là một đối tượng chỉ có thể được khởi tạo một lần, bao giờ) nhưng như Chris nói, đây hiếm khi là những gì bạn muốn hoặc cần trong khi một thể hiện chia sẻ có thể giải quyết được là những gì bạn thường muốn.
Luke Redpath

Đây là một vĩ mô cho các phương pháp trên: gist.github.com/1057420 . Đây là những gì tôi sử dụng.
Kobski

1
Đơn vị kiểm tra sang một bên, không có gì nói chống lại giải pháp này là, có đúng không? Và nó nhanh và an toàn.
LearnCocos2D

58

Kendall đã đăng một singleton singleton cố gắng tránh khóa chi phí, tôi nghĩ tôi cũng sẽ ném nó lên:

#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;                                                                        
}

Được rồi, hãy để tôi giải thích làm thế nào điều này hoạt động:

  1. Trường hợp nhanh: Trong thực thi bình thường sharedInstanceđã được thiết lập, do đó whilevòng lặp không bao giờ được thực thi và hàm trả về sau khi chỉ cần kiểm tra sự tồn tại của biến;

  2. Trường hợp chậm: Nếu sharedInstancekhông tồn tại, thì một thể hiện được phân bổ và sao chép vào nó bằng cách sử dụng So sánh và hoán đổi ('CAS');

  3. Trường hợp dự kiến: Nếu cả hai luồng cố gắng gọi sharedInstancecùng một lúc sharedInstance không tồn tại cùng một lúc thì cả hai sẽ khởi tạo các thể hiện mới của singleton và cố gắng đưa CAS vào vị trí. Bất cứ ai thắng CAS đều trả về ngay lập tức, bất kỳ ai thua sẽ giải phóng thể hiện mà nó vừa phân bổ và trả về (hiện được đặt) sharedInstance. Đơn OSAtomicCompareAndSwapPtrBarrierđóng vai trò vừa là rào cản ghi cho luồng cài đặt vừa là rào cản đọc từ luồng thử nghiệm.


18
Điều này là quá mức cần thiết trong ít nhất một lần nó có thể xảy ra trong suốt vòng đời của ứng dụng. Tuy nhiên, đây là điểm chính xác và kỹ thuật so sánh và trao đổi là một công cụ hữu ích để biết, vì vậy +1.
Steve Madsen

Câu trả lời hay - gia đình OSAtomic là một điều tốt để biết về
Bill

1
@Louis: Tuyệt vời, câu trả lời thực sự khai sáng! Một câu hỏi mặc dù: initphương pháp của tôi nên làm gì trong cách tiếp cận của bạn? Ném một ngoại lệ khi sharedInstanceđược khởi tạo không phải là một ý tưởng tốt, tôi tin. Làm gì sau đó để ngăn người dùng gọi inittrực tiếp nhiều lần?
matm

2
Tôi thường không ngăn chặn nó. Thường có những lý do hợp lệ để cho phép những gì nói chung là một singleton được nhân lên ngay lập tức, hầu hết các commons là dành cho một số loại thử nghiệm đơn vị nhất định. Nếu tôi thực sự muốn thực thi một trường hợp duy nhất, có lẽ tôi sẽ kiểm tra phương thức init để xem liệu toàn cầu có tồn tại không, và nếu có thì tôi sẽ tự giải phóng và trả lại toàn cầu.
Louis Gerbarg

1
@Tony hơi trễ phản hồi, nhưng OSAtomicCompareAndSwapPtrBarrier yêu cầu một biến động. Có lẽ từ khóa dễ bay hơi là để giữ cho trình biên dịch không tối ưu hóa việc kiểm tra? Xem: stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/...
Bến Flynn

14
MyClass tĩnh * sharedInst = nil;

+ (id) sharedInstance
{
    @synyncize (tự) {
        if (sharedInst == nil) {
            / * sharedInst được thiết lập trong init * /
            [[tự phân bổ] init];
        }
    }
    trả lại chia sẻInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [Nâng cao NSException: NSIternalInconsistencyException
            định dạng: @ "[% @% @] không thể được gọi; sử dụng + [% @% @] thay vào đó"],
            NSStringFromClass ([lớp tự]], NSStringFromSelector (_cmd), 
            NSStringFromClass ([lớp tự]],
            NSStringFromSelector (@selector (sharedInstance) "];
    } if if (self = [super init]) {
        sharedInst = tự;
        / * Bất cứ lớp học cụ thể nào ở đây * /
    }
    trả lại chia sẻInst;
}

/ * Những thứ này có lẽ không làm gì cả
   một ứng dụng GC. Giữ đơn
   như một người độc thân thực sự trong một
   ứng dụng không phải CG
* /
- (NSUInteger) keepCount
{
    trả lại NSUIntegerMax;
}

- phát hành (oneway void)
{
}

- (id) giữ lại
{
    trả lại chia sẻInst;
}

- (id) tự động thoát
{
    trả lại chia sẻInst;
}

3
Tôi nhận thấy rằng tiếng kêu phàn nàn về sự rò rỉ nếu bạn không chỉ định kết quả của [[self alloc] init]sharedInst.
pix0r

Phá vỡ init như thế này là một cách tiếp cận khá xấu xí IMO. Đừng lộn xộn với init và / hoặc việc tạo thực tế của đối tượng. Thay vào đó, nếu bạn đi đến một điểm được kiểm soát truy cập vào một cá thể được chia sẻ, trong khi không nướng đơn lẻ vào đối tượng, bạn sẽ có một thời gian hạnh phúc hơn sau đó nếu viết bài kiểm tra, v.v.
thỉnh thoảng

12

Chỉnh sửa: Việc triển khai này đã lỗi thời với ARC. Xin vui lòng xem Làm thế nào để tôi triển khai một singleton Objective-C tương thích với ARC? để thực hiện đúng.

Tất cả các triển khai khởi tạo Tôi đã đọc trong các câu trả lời khác đều có chung một lỗi.

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

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

Tài liệu của Apple khuyên bạn nên kiểm tra loại lớp trong khối khởi tạo của mình. Bởi vì các lớp con gọi khởi tạo theo mặc định. Tồn tại một trường hợp không rõ ràng trong đó các lớp con có thể được tạo gián tiếp thông qua KVO. Vì nếu bạn thêm dòng sau vào một lớp khác:

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

Objective-C sẽ ngầm tạo một lớp con của MySingletonClass dẫn đến việc kích hoạt lần thứ hai +initialize.

Bạn có thể nghĩ rằng bạn nên ngầm kiểm tra việc khởi tạo trùng lặp trong khối init của mình như sau:

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

Nhưng bạn sẽ tự bắn vào chân mình; hoặc tệ hơn là cho một nhà phát triển khác cơ hội tự bắn vào chân mình.

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

TL; DR, đây là triển khai của tôi

@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

(Thay thế ZAssert bằng macro xác nhận riêng của chúng tôi; hoặc chỉ NSAssert.)


1
Tôi sẽ chỉ sống đơn giản hơn và tránh khởi tạo hoàn toàn.
Tom Andersen


9

Tôi có một biến thể thú vị trên sharedInstance là luồng an toàn, nhưng không khóa sau khi khởi tạo. Tôi chưa chắc chắn đủ để sửa đổi câu trả lời hàng đầu theo yêu cầu, nhưng tôi trình bày nó để thảo luận thêm:

// 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 đó thực sự hấp dẫn. Tôi có thể sử dụng class_replaceMethodđể biến sharedInstancethành một bản sao simpleSharedInstance. Bằng cách đó, bạn sẽ không bao giờ phải lo lắng về việc mua lại @synchronizedkhóa.
Dave DeLong

Đó là hiệu ứng tương tự, sử dụng exchangeImcellenceations có nghĩa là sau khi init khi bạn gọi sharedInstance, bạn thực sự đang gọi SimpleSharedInstance. Tôi thực sự bắt đầu với thay thếMethod, nhưng quyết định tốt hơn là chỉ chuyển đổi các triển khai xung quanh để bản gốc vẫn tồn tại nếu cần ...
Kendall Helmstetter Gelner

Trong thử nghiệm tiếp theo, tôi không thể thay thếMethod hoạt động - trong các cuộc gọi lặp lại, mã vẫn được gọi là sharedInstance ban đầu thay vì SimpleSharedInstance. Tôi nghĩ rằng đó có thể là vì cả hai đều là các phương thức cấp lớp ... Sự thay thế mà tôi đã sử dụng là: class_VplaceMethod (self, origSel, method_getImcellenceation (newMethod), method_getTypeEncoding (newMethod)); và một số biến thể của chúng. Tôi có thể xác minh mã tôi đã đăng công việc và SimpleSharedInstance được gọi sau lần đầu tiên chuyển qua sharedInstance.
Kendall Helmstetter Gelner

Bạn có thể tạo một phiên bản an toàn cho chủ đề mà không phải trả chi phí khóa sau khi khởi tạo mà không cần thực hiện một loạt thao tác chạy bộ, tôi đã đăng một triển khai bên dưới.
Louis Gerbarg

1
+1 ý tưởng tuyệt vời. Tôi chỉ thích những thứ người ta có thể làm với thời gian chạy. Nhưng trong hầu hết các trường hợp, điều này có lẽ là tối ưu hóa sớm. Nếu tôi thực sự phải loại bỏ chi phí đồng bộ hóa thì có lẽ tôi sẽ sử dụng phiên bản không khóa của Louis.
Sven

6

Câu trả lời ngắn gọn: Tuyệt vời.

Câu trả lời dài: Một cái gì đó như ....

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

Hãy chắc chắn đọc tiêu đề công văn / một lần.h để hiểu những gì đang xảy ra. Trong trường hợp này, các bình luận tiêu đề được áp dụng nhiều hơn các tài liệu hoặc trang man.


5

Tôi đã cuộn singleton vào một lớp, vì vậy các lớp khác có thể kế thừa các thuộc tính của singleton.

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

Và đây là một ví dụ về một số lớp, rằng bạn muốn trở thành người độc thân.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Hạn chế duy nhất về lớp Singleton, đó là lớp con NSObject. Nhưng hầu hết thời gian tôi sử dụng singletons trong mã của mình, chúng thực tế là các lớp con NSObject, vì vậy lớp này thực sự dễ dàng cho cuộc sống của tôi và làm cho mã sạch hơn.


Bạn có thể muốn sử dụng một số cơ chế khóa khác vì @synchronizedchậm kinh khủng và nên tránh.
DarkDust

2

Điều này cũng hoạt động trong một môi trường không thu gom rác.

@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

Đây không phải là chủ đề an toàn và tránh khóa đắt tiền sau cuộc gọi đầu tiên?

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

2
Kỹ thuật khóa được kiểm tra hai lần được sử dụng ở đây thường là một vấn đề thực sự trong một số môi trường (xem aristeia.com/Papers/DDJ_Jul_Aug_2004_Vvised.pdf hoặc Google nó). Cho đến khi được hiển thị khác, tôi cho rằng Objective-C không miễn dịch. Đồng thời xem wincent.com/a/ledgeledge-base/archives/2006/01/ .
Steve Madsen


2

Làm thế nào về

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Vì vậy, bạn tránh chi phí đồng bộ hóa sau khi khởi tạo?


Xem các cuộc thảo luận về Khóa kiểm tra kép trong các câu trả lời khác.
i_am_jorf


1

KLSingleton là:

  1. Phân lớp (đến mức thứ n)
  2. Tương thích ARC
  3. An toàn với allocinit
  4. Tải một cách lười biếng
  5. An toàn chủ đề
  6. Khóa miễn phí (sử dụng + khởi tạo, không phải @ đồng bộ hóa)
  7. Không có vĩ mô
  8. Không có sóng gió
  9. Đơn giản

KLSingleton


1
Tôi đang sử dụng NSSingleton cho dự án của mình và dường như nó không tương thích với KVO. Vấn đề là KVO tạo lớp con cho mọi đối tượng KVO với tiền tố là NSKVONotifying_ MyClass . Và nó làm cho các phương thức MyClass + khởi tạo và -init được gọi hai lần.
Oleg Trakhman

Tôi đã thử nghiệm điều này trên Xcode mới nhất và không gặp khó khăn gì khi đăng ký hoặc nhận các sự kiện KVO. Bạn có thể xác minh điều này bằng mã sau: gist.github.com/3065038 Như tôi đã đề cập trên Twitter, các phương thức khởi tạo + được gọi một lần cho NSSingleton và một lần cho mỗi lớp con. Đây là một tài sản của Objective-C.
kevinlawler

Nếu bạn thêm NSLog(@"initialize: %@", NSStringFromClass([self class]));vào +initializephương thức, bạn có thể xác minh rằng các lớp chỉ được khởi tạo một lần.
kevinlawler

NSLog (@ "khởi tạo:% @", NSStringFromClass ([lớp tự]]);
Oleg Trakhman

Bạn cũng có thể muốn nó tương thích với IB. Của tôi là: stackoverflow.com/questions/4609609/ từ
Dan Rosenstark

0

Bạn không muốn tự đồng bộ hóa ... Vì đối tượng tự chưa tồn tại! Bạn cuối cùng khóa trên một giá trị id tạm thời. Bạn muốn đảm bảo rằng không ai khác có thể chạy các phương thức lớp (sharedInstance, alloc, allocWithZone:, v.v.), vì vậy bạn cần phải đồng bộ hóa trên đối tượng lớp thay thế:

@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
Phần còn lại của các phương thức, phương thức truy cập, phương thức trình biến đổi, v.v ... nên tự đồng bộ hóa. Tất cả các phương thức lớp (+) và bộ khởi tạo (và có lẽ -dealloc) nên đồng bộ hóa trên đối tượng lớp. Bạn có thể tránh phải đồng bộ hóa thủ công nếu bạn sử dụng các thuộc tính Objective-C 2.0 thay vì các phương thức accessor / mutator. Tất cả object.property và object.property = foo, được tự động đồng bộ hóa thành tự.
Rob Dotson

3
Vui lòng giải thích lý do tại sao bạn nghĩ rằng selfđối tượng không tồn tại trong một phương thức lớp. Thời gian chạy xác định thực hiện phương thức nào để gọi dựa trên cùng một giá trị chính xác mà nó cung cấp selfcho mọi phương thức (lớp hoặc thể hiện).
dreamlax

2
Bên trong một phương thức lớp, self đối tượng lớp. Hãy tự thử:#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

Chỉ muốn để nó ở đây vì vậy tôi không mất nó. Ưu điểm của cái này là nó có thể sử dụng được trong InterfaceBuilder, đây là một lợi thế lớn. Điều này được lấy từ một câu hỏi khác mà tôi đã hỏi :

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

Tôi biết có rất nhiều ý kiến ​​về "câu hỏi" này, nhưng tôi không thấy nhiều người đề xuất sử dụng macro để định nghĩa singleton. Đó là một mô hình phổ biến và một macro đơn giản hóa rất nhiều cho singleton.

Dưới đây là các macro tôi đã viết dựa trên một số triển khai Objc mà tôi đã thấy.

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; \
}

Ví dụ sử dụng:

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

Tại sao một macro giao diện khi nó gần như trống rỗng? Mã thống nhất giữa các tệp tiêu đề và mã; khả năng bảo trì trong trường hợp bạn muốn thêm các phương thức tự động hơn hoặc thay đổi nó xung quanh.

Tôi đang sử dụng phương thức khởi tạo để tạo ra singleton như được sử dụng trong câu trả lời phổ biến nhất ở đây (tại thời điểm viết).


0

Với các phương thức lớp C khách quan, chúng ta có thể tránh sử dụng mẫu singleton theo cách thông thường, từ:

[[Librarian sharedInstance] openLibrary]

đến:

[Librarian openLibrary]

bằng cách gói lớp bên trong một lớp khác chỉ có Phương thức lớp , theo cách đó, không có cơ hội vô tình tạo các thể hiện trùng lặp, vì chúng tôi không tạo ra bất kỳ trường hợp nào!

Tôi đã viết một blog chi tiết hơn ở đây :)


Liên kết của bạn không còn hoạt động.
i_am_jorf

0

Để mở rộng ví dụ từ @ 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

Cách của tôi rất đơn giản như thế này:

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;
}

Nếu singleton đã được khởi tạo, khối LOCK sẽ không được nhập. Kiểm tra thứ hai nếu (! Khởi tạo) là để đảm bảo rằng nó chưa được khởi tạo khi luồng hiện tại mua LOCK.


Nó không rõ ràng rằng đánh dấu initializednhư volatilelà đủ. Xem aristeia.com/Papers/DDJ_Jul_Aug_2004_Vvised.pdf .
i_am_jorf

0

Tôi đã không đọc qua tất cả các giải pháp, vì vậy hãy tha thứ nếu mã này là dư thừa.

Đây là chủ đề thực hiện an toàn nhất theo ý kiến ​​của tôi.

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

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

    return sharedResourcesObj;
}

-4

Tôi thường sử dụng mã gần giống với mã trong câu trả lời của Ben Hoffstein (mà tôi cũng đã thoát ra khỏi Wikipedia). Tôi sử dụng nó cho các lý do được nêu bởi Chris Hanson trong bình luận của mình.

Tuy nhiên, đôi khi tôi có nhu cầu đặt một singleton vào NIB và trong trường hợp đó tôi sử dụng như sau:

@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

Tôi để việc thực hiện -retain(v.v.) cho người đọc, mặc dù đoạn mã trên là tất cả những gì bạn cần trong một môi trường thu gom rác.


2
Mã của bạn không an toàn cho chuỗi. Nó sử dụng đồng bộ hóa trong phương thức phân bổ, nhưng không phải trong phương thức init. Kiểm tra bool khởi tạo không phải là chủ đề an toàn.
Mecki

-5

Câu trả lời được chấp nhận, mặc dù nó biên dịch, là không chính xác.

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

Theo tài liệu của Apple:

... Bạn có thể thực hiện một cách tiếp cận tương tự để đồng bộ hóa các phương thức lớp của lớp được liên kết, sử dụng đối tượng Class thay vì tự.

Ngay cả khi sử dụng tự hoạt động, nó không nên và nó trông giống như một bản sao và dán nhầm với tôi. Việc thực hiện đúng cho một phương thức nhà máy lớp sẽ là:

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

6
tự chắc chắn nhất không tồn tại nó phạm vi lớp học. Nó đề cập đến lớp thay vì thể hiện của lớp. Các lớp là (hầu hết) các đối tượng hạng nhất.
schwa

Tại sao bạn đặt @synyncinened VỚIIN một phương thức?
dùng4951

1
Như schwa đã nói, self đối tượng lớp bên trong một phương thức lớp. Xem bình luận của tôi cho một đoạn trích chứng minh điều này.
jscs

selftồn tại, nhưng sử dụng nó làm định danh được truyền vào @synchronizedsẽ đồng bộ hóa quyền truy cập vào các phương thức của thể hiện. Như @ user490696 chỉ ra, có những trường hợp (như singletons) trong đó sử dụng đối tượng lớp là thích hợp hơn. Từ Hướng dẫn lập trình 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.