2011-09-29 10 views
27

Tôi tìm thấy một số thông tin trong mạng để tạo ra một lớp singleton bằng cách sử dụng GCD. Thats mát mẻ vì nó an toàn thread với chi phí rất thấp. Đáng buồn là tôi không thể tìm thấy các giải pháp hoàn chỉnh mà chỉ tìm thấy các đoạn của phương thức sharedInstance. Vì vậy, tôi làm lớp riêng tôi bằng cách sử dụng phương pháp thử và sai - và et thì đấy - sau đây xuất hiện:Đúng đối tượng mẫu Singleton C (iOS)?

@implementation MySingleton 

// MARK: - 
// MARK: Singleton Pattern using GCD 

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } 
- (id)copyWithZone:(NSZone *)zone { return self; } 
- (id)autorelease { return self; } 
- (oneway void)release { /* Singletons can't be released */ } 
- (void)dealloc { [super dealloc]; /* should never be called */ } 
- (id)retain { return self; } 
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ } 

+ (MySingleton *)sharedInstance 
{ 
    static MySingleton * instance = nil; 

    static dispatch_once_t predicate; 
    dispatch_once(&predicate, ^{ 
     // --- call to super avoids a deadlock with the above allocWithZone 
     instance = [[super allocWithZone:nil] init]; 
    }); 

    return instance; 
} 

// MARK: - 
// MARK: Initialization 

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

@end 

Xin vui lòng nhận xét và cho tôi biết nếu tôi đã thiếu một cái gì đó hoặc làm điều gì đó hoàn toàn sai;)

Cheers Stefan

+0

tôi bị cám dỗ để thêm một '- (void) dealloc' mà ném một ngoại lệ, theo cách đó bạn sẽ có thể theo dõi các diễn viên vi phạm nếu ai đó đang nhận được một trường hợp singleton và sau đó phát hành nó. Ngoài việc là một sự lạm dụng của mô hình, mà sẽ để lại cho bạn một con trỏ lơ lửng. Câu hỏi meta – Tommy

+0

: đây có phải là [http://codereview.stackexchange.com/] không? – Joren

+2

táo khuyên không nên tạo ra những người độc thân ghi đè giữ lại/giải phóng! điều này sẽ phá vỡ các ứng dụng đang chuyển sang ARC –

Trả lời

81

Giữ nó đơn giản:

+(instancetype)sharedInstance 
{ 
    static dispatch_once_t pred; 
    static id sharedInstance = nil; 
    dispatch_once(&pred, ^{ 
     sharedInstance = [[self alloc] init]; 
    }); 
    return sharedInstance; 
} 

- (void)dealloc 
{ 
    // implement -dealloc & remove abort() when refactoring for 
    // non-singleton use. 
    abort(); 
} 

Đó là nó. Ghi đè retain, release, retainCount và phần còn lại chỉ ẩn các lỗi và thêm một loạt các dòng mã không cần thiết. Mỗi dòng mã là một lỗi đang chờ xảy ra. Trên thực tế, nếu bạn đang gây ra dealloc để được gọi trên phiên bản được chia sẻ của mình, bạn có một lỗi rất nghiêm trọng trong ứng dụng. Lỗi đó phải được sửa, không bị ẩn.

Cách tiếp cận này cũng tự vay để tái cấu trúc để hỗ trợ chế độ sử dụng không phải singleton.Khá nhiều mỗi singleton tồn tại ngoài một vài phiên bản cuối cùng sẽ được tái cấu trúc thành một dạng không đơn lẻ. Một số (như NSFileManager) tiếp tục hỗ trợ chế độ đơn trong khi cũng hỗ trợ khởi tạo tùy ý.

Lưu ý rằng ở trên cũng "chỉ hoạt động" trong ARC.

+0

cảm ơn vì điều này ... chỉ là một câu hỏi ở đây liên quan đến đối tượng được tạo tĩnh. Bạn không nên phát hành đối tượng mà tôi dealloc? tôi luôn luôn bối rối về quyền sở hữu của các đối tượng tĩnh. Vì vậy, bạn không nên có một cái gì đó như [[MyClass sharedInstance] phát hành]; trong dealloc? – Abolfoooud

+5

Độc thân tồn tại từ thời điểm chúng được yêu cầu cho đến khi ứng dụng chấm dứt. Chúng không được phân phối lại và tái khởi tạo. Vì không có lý do gì để giải quyết bất cứ điều gì về việc chấm dứt ứng dụng, không có lý do gì để 'giải phóng' singleton. Bởi vì không chắc rằng sự hủy diệt của singleton đã từng được thử nghiệm, việc thực hiện 'dealloc' như thể hiện là một biện pháp phòng thủ hoàn toàn để nhắc nhở bạn về tương lai mà bạn không nghĩ về quản lý bộ nhớ cho lớp này. – bbum

+0

Cảm ơn bạn đã làm rõ – Abolfoooud

19
// See Mike Ash "Care and Feeding of Singletons" 
// See Cocoa Samurai "Singletons: You're doing them wrong" 
+(MySingleton *)singleton { 
    static dispatch_once_t pred; 
    static MySingleton *shared = nil; 
    dispatch_once(&pred, ^{ 
     shared = [[MySingleton alloc] init]; 
     shared.someIvar = @"blah"; 
    }); 
    return shared; 
} 

Hãy nhận biết rằng dispatch_once is not reentrant, vì vậy tự xưng là từ bên trong khối dispatch_once sẽ bế tắc chương trình.

Đừng cố mã hóa phòng thủ chống lại chính mình. Nếu bạn không viết mã một khung, hãy xử lý lớp của bạn như bình thường sau đó dán thành ngữ đơn lên trên. Hãy suy nghĩ về thành ngữ singleton như một phương pháp thuận tiện, không phải là một đặc điểm xác định của lớp học của bạn. Bạn muốn đối xử với lớp của bạn như là một lớp bình thường trong quá trình kiểm tra đơn vị, do đó, nó là OK để lại một hàm tạo có thể truy cập.

Đừng bận tâm sử dụng allocWithZone:

  • Nó bỏ qua đối số của nó và hoạt động chính xác như alloc. Vùng bộ nhớ không còn được sử dụng trong Objective-C vì vậy allocWithZone: chỉ được duy trì để tương thích với mã cũ.
  • Nó không hoạt động. Bạn không thể thực thi hành vi đơn trong Mục tiêu-C vì có thể tạo nhiều phiên bản hơn bằng cách sử dụng NSAllocateObject()class_createInstance().

Một phương thức factory singleton luôn trả về một trong ba loại:

  • id để chỉ ra kiểu trả về là không được biết đầy đủ (trường hợp bạn đang xây dựng một cụm lớp).
  • instancetype để cho biết rằng loại trả lại là một phiên bản của lớp kèm theo.
  • Bản thân tên lớp (MySingleton trong ví dụ) để đơn giản.

Vì bạn gắn thẻ iOS này, một thay thế cho một singleton được tiết kiệm Ivar để các đại biểu ứng dụng và sau đó sử dụng một macro thuận tiện mà bạn có thể xác định lại nếu bạn thay đổi suy nghĩ của bạn:

#define coreDataManager() \ 
     ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager 
1

Nếu bạn muốn kiểm tra đơn vị singleton của bạn, bạn cũng có để làm cho nó để bạn có thể thay thế nó bằng một singleton giả và/hoặc thiết lập lại nó vào bình thường một:

@implementation ArticleManager 

static ArticleManager *_sharedInstance = nil; 
static dispatch_once_t once_token = 0; 

+(ArticleManager *)sharedInstance { 
    dispatch_once(&once_token, ^{ 
     if (_sharedInstance == nil) { 
      _sharedInstance = [[ArticleManager alloc] init]; 
     } 
    }); 
    return _sharedInstance; 
} 

+(void)setSharedInstance:(ArticleManager *)instance { 
    once_token = 0; // resets the once_token so dispatch_once will run again 
    _sharedInstance = instance; 
} 

@end 
+0

Tôi đi qua từng dòng của mẫu mã đó trong một bài đăng trên blog tại đây: http://twobitlabs.com/2013/01/objective-c-singleton-pattern-unit-testing/ – ToddH