Làm cách nào để triển khai một đơn vị Objective-C tương thích với ARC?


172

Làm cách nào để chuyển đổi (hoặc tạo) một lớp singleton biên dịch và hành xử chính xác khi sử dụng tính năng tham chiếu tự động (ARC) trong Xcode 4.2?


1
Gần đây tôi đã tìm thấy một bài viết từ Matt Galloway đi sâu về Singletons cho cả môi trường quản lý bộ nhớ thủ công và ARC. galloway.me.uk/tutorials/singleton-
classes

Câu trả lời:


391

Theo cách chính xác giống như cách mà bạn (nên) đã làm nó:

+ (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
Bạn không thực hiện bất kỳ thao tác quản lý bộ nhớ nào mà Apple đã sử dụng để giới thiệu trong developer.apple.com/l
Library / mac / documentation / Coloa / Conceptionual

1
@MakingScienceFictionFact, bạn có thể muốn xem bài đăng này
kervich

6
Các staticbiến @David được khai báo trong một phương thức / hàm giống như một staticbiến được khai báo bên ngoài một phương thức / hàm, chúng chỉ có giá trị trong phạm vi của phương thức / hàm đó. Mỗi lần chạy riêng biệt thông qua +sharedInstancephương thức (ngay cả trên các luồng khác nhau) sẽ 'thấy' cùng một sharedInstancebiến.
Nick Forge

9
Nếu ai đó gọi [[MyClass alloc] init] thì sao? Điều đó sẽ tạo ra một đối tượng mới. Làm thế nào chúng ta có thể tránh điều này (ngoài việc khai báo MyClass tĩnh * sharedInstance = nil bên ngoài phương thức).
Ricardo Sanchez-Saez

2
Nếu một lập trình viên khác nhắn tin và gọi init khi họ nên gọi sharedInstance hoặc tương tự, thì đó là lỗi của họ. Phá vỡ các nguyên tắc cơ bản và hợp đồng cơ bản của ngôn ngữ để ngăn chặn những người khác có khả năng mắc lỗi có vẻ khá sai. Có nhiều cuộc thảo luận tại chánzo.org/blog/archives/2009-06-17/doing
it

8

nếu bạn muốn tạo phiên bản khác khi cần thiết. Làm thế này:

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

khác, bạn nên làm điều này:

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

1
Đúng / Sai: dispatch_once()Bit có nghĩa là bạn sẽ không nhận được các trường hợp bổ sung, ngay cả trong ví dụ đầu tiên ...?
Olie

4
@Olie: Sai, vì mã máy khách có thể làm [[MyClass alloc] init]và bỏ qua sharedInstancequyền truy cập. DongXu, bạn nên xem bài viết Singleton của Peter Hosey . Nếu bạn định ghi đè allocWithZone:để ngăn không cho nhiều phiên bản được tạo, bạn cũng nên ghi đè initđể ngăn phiên bản được chia sẻ lại được khởi tạo lại.
jscs

Ok, đó là những gì tôi nghĩ, do đó là allocWithZone:phiên bản. Cám ơn.
Olie

2
Điều này hoàn toàn phá vỡ hợp đồng của allocWithZone.
thỉnh thoảng

1
singleton chỉ có nghĩa là "chỉ một đối tượng trong bộ nhớ bất cứ lúc nào", đây là một điều, được khởi tạo lại là một điều khác.
DongXu

5

Đây là phiên bản dành cho ARC và không ARC

Cách sử dụng:

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

Đây là mô hình của tôi dưới ARC. Đáp ứng mẫu mới bằng GCD và cũng thỏa mãn mẫu ngăn chặn khởi tạo cũ của 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
Kết quả này có phải c1là một ví dụ của AAAsiêu lớp không? Bạn cần phải gọi +allocvề self, không phải trên super.
Nick Forge

@NickForge superkhông có nghĩa là đối tượng siêu hạng. Bạn không thể có được đối tượng siêu hạng Nó chỉ có nghĩa là định tuyến các thông điệp đến phiên bản siêu hạng của phương thức. supervẫn còn điểm selflớp. Nếu bạn muốn có được đối tượng siêu hạng, bạn cần có các hàm phản chiếu thời gian chạy.
eonil

@NickForge Và -allocWithZone:phương thức chỉ là một chuỗi đơn giản cho chức năng phân bổ của thời gian chạy để cung cấp điểm ghi đè. Vì vậy, cuối cùng, selfcon trỏ == đối tượng lớp hiện tại sẽ được chuyển đến bộ cấp phát và cuối cùng AAAcá thể sẽ được phân bổ.
eonil

bạn đã đúng, tôi đã quên sự tinh tế trong cách làm superviệc trong các phương thức của lớp.
Nick Forge

Hãy nhớ sử dụng #import <objc / objc-runtime.h>
Ryan Heitner

2

Đọc câu trả lời này và sau đó đi và đọc câu trả lời khác.

Trước tiên, bạn phải biết Singleton nghĩa là gì và yêu cầu của nó là gì, nếu bạn không hiểu nó, hơn là bạn sẽ không hiểu giải pháp - tất cả!

Để tạo Singleton thành công, bạn phải có thể thực hiện 3 thao tác sau:

  • Nếu có một điều kiện cuộc đua , thì chúng tôi không được phép tạo nhiều phiên bản SharedInstance của bạn cùng một lúc!
  • Ghi nhớ và giữ giá trị trong số nhiều lời mời.
  • Tạo nó chỉ một lần. Bằng cách kiểm soát điểm vào.

dispatch_once_tgiúp bạn giải quyết tình trạng cuộc đua bằng cách chỉ cho phép khối của nó được gửi đi một lần.

Staticgiúp bạn ghi nhớ giá trị của nó qua bất kỳ số lượng yêu cầu nào. Làm thế nào để nó nhớ? Nó không cho phép bất kỳ trường hợp mới nào có tên chính xác của sharedInstance của bạn được tạo lại, nó chỉ hoạt động với cái được tạo ban đầu.

Không sử dụng gọi điện alloc init(nghĩa là chúng ta vẫn có alloc initcác phương thức vì chúng ta là lớp con NSObject, mặc dù chúng ta KHÔNG nên sử dụng chúng) trên lớp sharedInstance của mình, chúng ta đạt được điều này bằng cách sử dụng +(instancetype)sharedInstance, được giới hạn chỉ được bắt đầu một lần , bất kể nhiều lần thử từ các luồng khác nhau đồng thời và ghi nhớ giá trị của nó.

Một số Singletons hệ thống phổ biến nhất đi kèm với chính ca cao là:

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

Về cơ bản, bất cứ điều gì cần có hiệu ứng tập trung sẽ cần phải tuân theo một số kiểu mẫu thiết kế Singleton.


1

Ngoài ra, Objective-C cung cấp phương thức khởi tạo + (void) cho NSObject và tất cả các lớp con của nó. Nó luôn được gọi trước bất kỳ phương thức nào của lớp.

Tôi đã đặt một điểm dừng một lần trong iOS 6 và Clark_once xuất hiện trong các khung stack.


0

Lớp đơn: Không ai có thể tạo nhiều hơn một đối tượng của lớp trong mọi trường hợp hoặc thông qua bất kỳ cách nào.

+ (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
Nếu ai đó gọi init, init sẽ gọi sharedInstance, sharedInstance sẽ gọi init, init sẽ gọi sharedInstance lần thứ hai, sau đó gặp sự cố! Đầu tiên, đây là một vòng lặp đệ quy vô hạn. Thứ hai, lần lặp thứ hai của việc gọi Clark_once sẽ bị sập bởi vì nó không thể được gọi lại từ bên trong Clark_once.
Chuck Krutsinger

0

Có hai vấn đề với câu trả lời được chấp nhận, có thể có hoặc không có liên quan cho mục đích của bạn.

  1. Nếu từ phương thức init, bằng cách nào đó, phương thức sharedInstance được gọi lại (ví dụ vì các đối tượng khác được xây dựng từ đó sử dụng singleton), nó sẽ gây ra tràn ngăn xếp.
  2. Đối với hệ thống phân cấp lớp, chỉ có một singleton (cụ thể là: lớp đầu tiên trong hệ thống phân cấp mà phương thức sharedInstance được gọi), thay vì một singleton cho mỗi lớp cụ thể trong cấu trúc phân cấp.

Đoạn mã sau giải quyết cả hai vấn đề sau:

+ (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

Hy vọng mã trên sẽ giúp nó ra.


-2

nếu bạn cần tạo singleton nhanh chóng,

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

hoặc là

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

bạn có thể sử dụng cách này

let sharedClass = LibraryAPI.sharedInstance
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.