Tạo singleton bằng cách sử dụng Clark_once của GCD trong Objective-C


341

Nếu bạn có thể nhắm mục tiêu iOS 4.0 trở lên

Sử dụng GCD, đây có phải là cách tốt nhất để tạo singleton trong Objective-C (thread safe) không?

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

2
Có cách nào để ngăn người dùng của lớp gọi cấp phát / sao chép không?
Nicolas Miari

3
Clark_once_t và Clark_once dường như đã được giới thiệu trong 4.0, chứ không phải 4.1 (xem: developer.apple.com/l
Ben Flynn

1
Phương thức này trở nên có vấn đề nếu init yêu cầu sử dụng đối tượng singleton. Mã của Matt Gallagher đã làm việc cho tôi hơn một vài lần. cocoawithlove.com/2008/11/ từ
greg

1
Tôi biết nó không quan trọng trong ví dụ này; nhưng tại sao mọi người không sử dụng 'mới' hơn. dispatch_once (& một lần, ^ {sharedInstance = [tự mới];} chỉ trông thấy chút gọn gàng Đó là tương đương với alloc + init..
Chris Hatton

3
Hãy chắc chắn để bắt đầu sử dụng loại trả lại instancetype. Mã hoàn thành tốt hơn nhiều khi sử dụng thay vì id.
Ông Rogers

Câu trả lời:


215

Đây là một cách hoàn toàn có thể chấp nhận và an toàn theo luồng để tạo một thể hiện của lớp của bạn. Về mặt kỹ thuật nó có thể không phải là một "đơn lẻ" (trong đó chỉ có thể có 1 trong số các đối tượng này), nhưng miễn là bạn chỉ sử dụng [Foo sharedFoo]phương thức để truy cập vào đối tượng, điều này là đủ tốt.


4
Làm thế nào để bạn phát hành nó mặc dù?
samvermette

65
@samvermette bạn không. quan điểm của người độc thân là nó sẽ luôn tồn tại. do đó, bạn không giải phóng nó và bộ nhớ được lấy lại khi thoát quá trình.
Dave DeLong

6
@Dave DeLong: Theo tôi mục đích của việc có singleton không phải là một sự chắc chắn về sự bất tử của nó, nhưng chắc chắn rằng chúng ta có một ví dụ. Điều gì xảy ra nếu singleton giảm một semaphore? Bạn không thể tùy tiện nói rằng nó sẽ luôn tồn tại.
jacekmigacz

4
@hooleyhoop Vâng, trong tài liệu của nó . "Nếu được gọi đồng thời từ nhiều luồng, chức năng này sẽ chờ đồng bộ cho đến khi khối hoàn thành."
Kevin

3
@ WalterMartinVargas-Pena tham chiếu mạnh được giữ bởi biến tĩnh
Dave DeLong

36

instancetype

instancetypechỉ là một trong nhiều phần mở rộng ngôn ngữ Objective-C, với nhiều phần được thêm vào với mỗi bản phát hành mới.

Biết nó, yêu nó.

Và lấy nó làm ví dụ về việc chú ý đến các chi tiết cấp thấp có thể cung cấp cho bạn cái nhìn sâu sắc về những cách mới mạnh mẽ để biến đổi Objective-C.

Tham khảo tại đây: instancetype


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

4
mẹo tuyệt vời, cảm ơn! instancetype là một từ khóa theo ngữ cảnh có thể được sử dụng làm loại kết quả để báo hiệu rằng một phương thức trả về một loại kết quả liên quan. ... Với instancetype, trình biên dịch sẽ suy ra chính xác kiểu.
Fattie

1
Tôi không rõ hai đoạn này có ý nghĩa gì ở đây, chúng có tương đương với nhau không? Người này thích người kia hơn? Sẽ tốt hơn nếu tác giả có thể thêm một chút giải thích cho điều này.
galactica

33

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@implementation MySingleton

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

-(instancetype)initUniqueInstance {
    return [super init];
}

@end

Làm thế nào là init không có sẵn? Nó ít nhất là có sẵn cho một init?
Mật ong

2
Singleton chỉ nên có một điểm truy cập. Và điểm này được chia sẻ. Nếu chúng ta có phương thức init trong tệp * .h thì bạn có thể tạo một cá thể singleton khác. Điều này mâu thuẫn với định nghĩa của một singleton.
Serge Petruk

1
@ asma22 __attribute __ ((không có sẵn ()) làm cho không có sẵn để sử dụng các phương pháp này Nếu lập trình viên khác muốn phương pháp sử dụng đánh dấu là không có sẵn, anh nhận được lỗi.
Sergey Petruk

1
Tôi hoàn toàn hiểu và tôi rất vui vì tôi đã học được một điều mới, không có gì sai với câu trả lời của bạn, chỉ có thể hơi khó hiểu cho người mới ...
Honey

1
Điều này chỉ hoạt động MySingleton, ví dụ như trong khi MySingleton.mtôi đang gọi[super alloc]
Serge Petruk

6

Bạn có thể tránh việc lớp được phân bổ bằng cách ghi đè phương thức phân bổ.

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}

1
Điều này trả lời câu hỏi của tôi trong các ý kiến ​​trên. Không phải tôi quá thích lập trình phòng thủ, nhưng ...
Nicolas Miari

5

Dave là chính xác, điều đó là hoàn toàn tốt. Bạn có thể muốn kiểm tra các tài liệu của Apple về việc tạo một singleton để biết các mẹo thực hiện một số phương thức khác để đảm bảo rằng chỉ có một phương thức có thể được tạo nếu các lớp chọn KHÔNG sử dụng phương thức sharedFoo.


8
eh ... đó không phải là ví dụ tuyệt vời nhất để tạo ra một singleton. Ghi đè các phương thức quản lý bộ nhớ là không cần thiết.
Dave DeLong

19
Điều này là hoàn toàn không hợp lệ bằng cách sử dụng ARC.
logancautrell

Tài liệu trích dẫn đã được nghỉ hưu. Ngoài ra, câu trả lời chỉ liên kết đến nội dung bên ngoài thường là câu trả lời SO kém. Tại một phần trích dẫn tối thiểu có liên quan trong câu trả lời của bạn. Đừng bận tâm ở đây trừ khi bạn muốn cách cũ được lưu lại cho hậu thế.
cụ

4

Nếu bạn muốn đảm bảo rằng [[MyClass alloc] init] trả về cùng một đối tượng như sharedInstance (theo quan điểm của tôi, không cần thiết, nhưng một số người muốn nó), điều đó có thể được thực hiện rất dễ dàng và an toàn khi sử dụng công cụ thứ hai:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

Điều này cho phép mọi sự kết hợp của [[MyClass alloc] init] và [MyClass sharedInstance] để trả về cùng một đối tượng; [MyClass sharedInstance] sẽ hiệu quả hơn một chút. Cách thức hoạt động: [MyClass sharedInstance] sẽ gọi [[MyClass alloc] init] một lần. Mã khác có thể gọi nó là tốt, bất kỳ số lần. Người gọi đầu tiên đến init sẽ thực hiện khởi tạo "bình thường" và lưu trữ đối tượng singleton đi trong phương thức init. Bất kỳ cuộc gọi nào sau này đến init sẽ hoàn toàn bỏ qua những gì phân bổ được trả về và trả lại cùng một chia sẻ; kết quả của phân bổ sẽ được giải quyết.

Phương thức + sharedInstance sẽ hoạt động như mọi khi. Nếu đó không phải là người gọi đầu tiên gọi [[MyClass alloc] init], thì kết quả của init không phải là kết quả của lệnh gọi cấp phát, nhưng điều đó vẫn ổn.


2

Bạn hỏi liệu đây có phải là "cách tốt nhất để tạo singleton" không.

Một vài suy nghĩ:

  1. Đầu tiên, vâng, đây là một giải pháp an toàn chủ đề. Đây dispatch_oncemô hình là thread-an toàn, cách hiện đại để tạo ra độc thân trong Objective-C. Không phải lo lắng ở đó.

  2. Tuy nhiên, bạn đã hỏi liệu đây có phải là cách "tốt nhất" để làm điều đó không. Tuy nhiên, người ta phải thừa nhận rằng instancetype[[self alloc] init]có khả năng gây hiểu lầm khi được sử dụng cùng với singletons.

    Lợi ích của instancetypeviệc đó là một cách rõ ràng để tuyên bố rằng lớp có thể được phân lớp mà không cần dùng đến một loại id, giống như chúng ta phải làm trong năm qua.

    Nhưng statictrong phương pháp này đưa ra những thách thức phân lớp. Điều gì xảy ra nếu ImageCacheBlobCachesingletons đều là các lớp con từ một Cachesiêu lớp mà không thực hiện sharedCachephương thức riêng của chúng ?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Để làm việc này, bạn phải đảm bảo các lớp con thực hiện phương thức riêng của chúng sharedInstance(hoặc bất cứ điều gì bạn gọi nó cho lớp cụ thể của bạn).

    Tóm lại, bản gốc của bạn có sharedInstance vẻ như sẽ hỗ trợ các lớp con, nhưng nó sẽ không. Nếu bạn có ý định hỗ trợ phân lớp, ít nhất bao gồm tài liệu cảnh báo các nhà phát triển trong tương lai rằng họ phải ghi đè phương thức này.

  3. Để có khả năng tương tác tốt nhất với Swift, có lẽ bạn muốn xác định đây là một thuộc tính, không phải là một phương thức lớp, ví dụ:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Sau đó, bạn có thể tiếp tục và viết một getter cho thuộc tính này (việc triển khai sẽ sử dụng dispatch_oncemẫu mà bạn đề xuất):

    + (Foo *)sharedFoo { ... }

    Lợi ích của việc này là nếu người dùng Swift sử dụng nó, họ sẽ làm một cái gì đó như:

    let foo = Foo.shared

    Lưu ý, không có (), bởi vì chúng tôi đã triển khai nó như một tài sản. Bắt đầu Swift 3, đây là cách các singletons thường được truy cập. Vì vậy, xác định nó là một tài sản giúp tạo điều kiện cho khả năng tương tác đó.

    Ở một khía cạnh khác, nếu bạn nhìn vào cách Apple định nghĩa các singletons của họ, thì đây là mô hình mà họ đã áp dụng, ví dụ như NSURLSessionsingleton của họ được định nghĩa như sau:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Một xem xét khả năng tương tác Swift rất nhỏ khác là tên của singleton. Tốt nhất là bạn có thể kết hợp tên của loại chứ không phải sharedInstance. Ví dụ: nếu lớp là Foo, bạn có thể định nghĩa thuộc tính singleton là sharedFoo. Hoặc nếu lớp là DatabaseManager, bạn có thể gọi tài sản sharedManager. Sau đó, người dùng Swift có thể làm:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Rõ ràng, nếu bạn thực sự muốn sử dụng sharedInstance, bạn luôn có thể khai báo tên Swift nếu bạn muốn:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Rõ ràng, khi viết mã Objective-C, chúng ta không nên để khả năng tương tác của Swift vượt xa các cân nhắc thiết kế khác, nhưng vẫn có thể, nếu chúng ta có thể viết mã hỗ trợ duyên dáng cho cả hai ngôn ngữ, điều đó tốt hơn.

  5. Tôi đồng ý với những người khác chỉ ra rằng nếu bạn muốn đây là một người độc thân thực sự nơi các nhà phát triển không thể / không nên (vô tình) khởi tạo các thể hiện của riêng họ, thì unavailablevòng loại trên initnewthận trọng.


0

Để tạo chủ đề singleton an toàn, bạn có thể làm như thế này:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end

/* thread safe */
@implementation SomeManager

static id sharedManager = nil;

+ (void)initialize {
    if (self == [SomeManager class]) {
        sharedManager = [[self alloc] init];
    }
}

+ (id)sharedManager {
    return sharedManager;
}
@end

và blog này giải thích singleton rất tốt singletons trong objc / ca cao


bạn đang liên kết đến một bài viết rất cũ trong khi OP yêu cầu các đặc điểm về cách triển khai hiện đại nhất.
vikingosegundo

1
Câu hỏi là về một thực hiện cụ thể. Bạn chỉ cần đăng một thực hiện khác. Ở đó - cho bạn thậm chí không cố gắng trả lời câu hỏi.
vikingosegundo

1
@vikingosegundo Người hỏi hỏi thời tiết GCD là cách tốt nhất để tạo ra một chủ đề an toàn cho chủ đề, câu trả lời của tôi đưa ra lựa chọn khác. Bạn có sai không?
Hancock_Xu

người hỏi hỏi nếu một triển khai nhất định là an toàn chủ đề. anh ta không yêu cầu lựa chọn.
vikingosegundo

0
//Create Singleton  
  +( instancetype )defaultDBManager
    {

        static dispatch_once_t onceToken = 0;
        __strong static id _sharedObject = nil;

        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });

        return _sharedObject;
    }


//In it method
-(instancetype)init
{
    self = [super init];
  if(self)
     {
   //Do your custom initialization
     }
     return self;
}

0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
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.