Tại sao Apple khuyên bạn nên sử dụng Clark_once để triển khai mẫu singleton trong ARC?


305

Lý do chính xác cho việc sử dụng Clark_once trong trình truy cập thể hiện được chia sẻ của một singleton trong ARC là gì?

+ (MyClass *)sharedInstance
{
    //  Static local predicate must be initialized to 0
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

Không phải là một ý tưởng tồi để khởi tạo các singleton không đồng bộ trong nền sao? Tôi có nghĩa là những gì xảy ra nếu tôi yêu cầu trường hợp được chia sẻ đó và dựa vào nó ngay lập tức, nhưng Clark_once mất đến Giáng sinh để tạo đối tượng của tôi? Nó không trở lại ngay lập tức phải không? Ít nhất đó dường như là toàn bộ quan điểm của Grand Central Dispatch.

Vậy tại sao họ làm điều này?


Note: static and global variables default to zero.
ikkentim

Câu trả lời:


418

dispatch_once()là hoàn toàn đồng bộ. Không phải tất cả các phương thức GCD đều thực hiện mọi thứ không đồng bộ (trong trường hợp, dispatch_sync()là đồng bộ). Việc sử dụng dispatch_once()thay thế thành ngữ sau:

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

Lợi ích của dispatch_once()việc này là nó nhanh hơn. Nó cũng sạch hơn về mặt ngữ nghĩa, bởi vì nó cũng bảo vệ bạn khỏi nhiều luồng thực hiện cấp phát init chia sẻ của bạn - nếu tất cả chúng đều cố gắng vào cùng một thời điểm chính xác. Nó sẽ không cho phép hai trường hợp được tạo ra. Toàn bộ ý tưởng dispatch_once()là "thực hiện một cái gì đó một lần và chỉ một lần", đó chính xác là những gì chúng tôi đang làm.


4
Để tranh luận, tôi cần lưu ý rằng tài liệu không nói rằng nó được thực thi đồng bộ. Nó chỉ nói rằng nhiều lời mời đồng thời sẽ được nối tiếp.
chuyên gia

5
Bạn cho rằng nó nhanh hơn - nhanh hơn bao nhiêu? Tôi không có lý do để nghĩ rằng bạn không nói sự thật, nhưng tôi muốn xem một điểm chuẩn đơn giản.
Joshua Gross

29
Tôi vừa thực hiện một điểm chuẩn đơn giản (trên iPhone 5) và có vẻ như Clark_once nhanh hơn khoảng 2 lần so với @ không đồng bộ.
Joshua Gross

3
@ReneDohan: Nếu bạn chắc chắn 100% rằng không ai từng gọi phương thức đó từ một luồng khác, thì nó hoạt động. Nhưng việc sử dụng dispatch_once()thực sự đơn giản (đặc biệt là vì Xcode thậm chí sẽ tự động hoàn thành nó thành một đoạn mã đầy đủ cho bạn) và có nghĩa là bạn thậm chí không bao giờ phải xem xét liệu phương thức này có an toàn cho luồng hay không.
Lily Ballard

1
@siuying: Thật ra, điều đó không đúng. Đầu tiên, mọi thứ được thực hiện +initializesẽ xảy ra trước khi lớp được chạm vào, ngay cả khi bạn chưa cố gắng tạo cá thể chia sẻ của mình. Nói chung, khởi tạo lười biếng (chỉ tạo ra một cái gì đó khi cần thiết) là tốt hơn. Thứ hai, ngay cả yêu cầu về hiệu suất của bạn cũng không đúng. dispatch_once()mất gần như chính xác cùng một lượng thời gian như nói if (self == [MyClass class])+initialize. Nếu bạn đã có +initialize, thì có, việc tạo cá thể chia sẻ sẽ nhanh hơn, nhưng hầu hết các lớp thì không.
Lily Ballard

41

Bởi vì nó sẽ chỉ chạy một lần. Vì vậy, nếu bạn thử và truy cập nó hai lần từ các luồng khác nhau, nó sẽ không gây ra vấn đề.

Mike Ash có một mô tả đầy đủ trong bài đăng trên blog Chăm sóc và Nuôi dưỡng Singletons của mình.

Không phải tất cả các khối GCD được chạy không đồng bộ.


Lily là một câu trả lời tốt hơn, nhưng tôi sẽ rời khỏi tôi để giữ liên kết đến bài viết của Mike Ash.
Abizern 18/03/19
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.