ฉันจะใช้ Objective-C singleton ที่เข้ากันได้กับ ARC ได้อย่างไร


172

ฉันจะแปลง (หรือสร้าง) คลาสซิงเกิลตันที่รวบรวมและทำงานอย่างถูกต้องเมื่อใช้การนับการอ้างอิงอัตโนมัติ (ARC) ใน Xcode 4.2 ได้อย่างไร


1
เมื่อเร็ว ๆ นี้ฉันได้พบบทความจาก Matt Galloway ในเชิงลึกเกี่ยวกับ Singletons สำหรับทั้ง ARC และสภาพแวดล้อมการจัดการหน่วยความจำด้วยตนเอง galloway.me.uk/tutorials/singleton-classes
cescofry

คำตอบ:


391

ในลักษณะเดียวกับที่คุณ (ควร) ทำมาแล้ว:

+ (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
คุณเพียงแค่ไม่ทำการจัดการหน่วยความจำ hokey pokey Apple ที่เคยแนะนำในdeveloper.apple.com/library/mac/documentation/Cocoa/Conceptual/ …
Christopher Pickslay

1
@MakingScienceFictionFact คุณอาจต้องการดูบทความนี้
kervich

6
staticตัวแปร@David ที่ประกาศภายในเมธอด / ฟังก์ชั่นจะเหมือนกับstaticตัวแปรที่ประกาศภายนอกเมธอด / ฟังก์ชั่นโดยจะใช้ได้ภายในขอบเขตของเมธอด / ฟังก์ชันนั้นเท่านั้น ทุกการทำงานที่แยกกันด้วย+sharedInstanceวิธีการ (แม้จะอยู่ในเธรดที่ต่างกัน) จะ 'เห็น' sharedInstanceตัวแปรเดียวกัน
Nick Forge

9
ถ้ามีใครโทร [[MyClass alloc] init] จะเป็นอย่างไร นั่นจะสร้างวัตถุใหม่ เราจะหลีกเลี่ยงปัญหานี้ได้อย่างไร (นอกเหนือจากการประกาศ MyClass * sharedInstance = nil นอกเมธอด)
Ricardo Sanchez-Saez

2
หากโปรแกรมเมอร์คนอื่นยุ่งเหยิงและเรียก init เมื่อพวกเขาควรจะเรียก sharedInstance หรือคล้ายกันนั่นเป็นข้อผิดพลาดของพวกเขา การทำลายรากฐานและสัญญาขั้นพื้นฐานของภาษาเพื่อหยุดยั้งผู้อื่นที่อาจทำให้เกิดข้อผิดพลาดดูเหมือนว่าผิด มีการสนทนาเพิ่มเติมที่boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus

8

ถ้าคุณต้องการสร้างอินสแตนซ์อื่น ๆ ตามต้องการทำสิ่งนี้:

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

มิฉะนั้นคุณควรทำสิ่งนี้:

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

1
จริง / เท็จ: dispatch_once()บิตหมายความว่าคุณจะไม่ได้รับอินสแตนซ์เพิ่มเติมแม้แต่ในตัวอย่างแรก ... ?
Olie

4
@Olie: เท็จเนื่องจากรหัสลูกค้าสามารถทำได้[[MyClass alloc] init]และข้ามการsharedInstanceเข้าถึง Dongxu, คุณควรดูที่บทความซิงเกิล Peter Hosey ของ หากคุณกำลังจะแทนที่allocWithZone:เพื่อป้องกันการสร้างอินสแตนซ์เพิ่มเติมคุณควรแทนที่initเพื่อป้องกันไม่ให้อินสแตนซ์ที่แชร์ถูกเริ่มต้นใหม่
jscs

ตกลงนั่นคือสิ่งที่ฉันคิดดังนั้นallocWithZone:รุ่น ขอบคุณ.
Olie

2
นี่เป็นการแบ่งสัญญาของ allocWithZone อย่างสมบูรณ์
occulus

1
ซิงเกิลหมายถึง "เพียงวัตถุเดียวในหน่วยความจำในเวลาใดก็ได้" นี่คือสิ่งหนึ่งการเริ่มต้นใหม่เป็นอีกสิ่งหนึ่ง
DongXu


2

นี่คือรูปแบบของฉันภายใต้ ARC ตอบสนองรูปแบบใหม่ด้วย GCD และยังเป็นไปตามรูปแบบการป้องกันอินสแตนซ์เก่าของ 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
ผลลัพธ์นี้จะไม่c1เป็นตัวอย่างของAAAซูเปอร์คลาสหรือไม่? คุณจะต้องเรียก+allocบนไม่ได้อยู่ในself super
Nick Forge

@NickForge superไม่ได้หมายถึงวัตถุระดับซุปเปอร์ คุณไม่สามารถรับออบเจ็กต์ระดับสูงได้มันหมายถึงการกำหนดเส้นทางข้อความไปยังเมธอดเวอร์ชันซุปเปอร์คลาส superยังคงชี้selfระดับ หากคุณต้องการได้รับออบเจ็กต์ระดับสูงคุณจะต้องได้รับฟังก์ชั่นการสะท้อนแบบรันไทม์
eonil

@NickForge และ-allocWithZone:เมธอดเป็นเพียงเชนการทำงานของการจัดสรรรันไทม์เพื่อเสนอจุดที่สำคัญ ดังนั้นในที่สุดselfตัวชี้ == วัตถุคลาสปัจจุบันจะถูกส่งผ่านไปยังตัวจัดสรรและในที่สุดAAAอินสแตนซ์จะถูกจัดสรร
eonil

คุณถูกต้องฉันลืมรายละเอียดปลีกย่อยของวิธีการsuperทำงานในวิธีการเรียน
Nick Forge

อย่าลืมใช้ #import <objc / objc-runtime.h>
Ryan Heitner

2

อ่านคำตอบนี้แล้วไปอ่านคำตอบอื่น

ก่อนอื่นคุณต้องรู้ว่า Singleton มีความหมายว่าอะไรและมีข้อกำหนดอะไรบ้างถ้าคุณไม่เข้าใจมันมากกว่าที่คุณจะไม่เข้าใจวิธีแก้ปัญหา - เลย!

ในการสร้าง Singleton สำเร็จคุณต้องทำสิ่งต่อไปนี้ 3:

  • หากมีสภาพการแข่งขันเราจะต้องไม่อนุญาตให้สร้าง SharedInstance ของคุณหลายอินสแตนซ์ในเวลาเดียวกัน!
  • จำและรักษาคุณค่าไว้ท่ามกลางการเรียกร้องหลายครั้ง
  • สร้างเพียงครั้งเดียว โดยการควบคุมจุดเข้า

dispatch_once_tช่วยคุณแก้ปัญหาสภาพการแข่งขันโดยอนุญาตให้ส่งบล็อกของมันเพียงครั้งเดียว

Staticช่วยให้คุณ“ จดจำ” คุณค่าของมันในจำนวนการเรียกใช้ใด ๆ จำได้อย่างไร ไม่อนุญาตให้มีอินสแตนซ์ใหม่ที่มีชื่อที่แน่นอนของ sharedInstance ของคุณถูกสร้างขึ้นอีกครั้งเพียงแค่ทำงานกับอินสแตนซ์ที่สร้างขึ้น แต่เดิม

ไม่ใช้การโทรalloc init(เช่นเรายังมีalloc initวิธีการเนื่องจากเราเป็น subclass ของ NSObject ถึงแม้ว่าเราไม่ควรใช้มัน) ในคลาส sharedInstance ของเราเราทำได้โดยการใช้+(instancetype)sharedInstanceซึ่งถูก จำกัด ขอบเขตให้เริ่มต้นเพียงครั้งเดียวโดยไม่คำนึงถึงความพยายามจากหลายเธรด ในเวลาเดียวกันและจดจำค่าของมัน

ระบบ Singletons ที่พบมากที่สุดที่มาพร้อมกับโกโก้คือ:

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

โดยทั่วไปอะไรก็ตามที่จะต้องมีผลกระทบจากศูนย์กลางจะต้องเป็นไปตามรูปแบบการออกแบบซิงเกิล


1

อีกวิธีหนึ่งคือ Objective-C จัดเตรียมวิธีการเริ่มต้น + (void) สำหรับ NSObject และคลาสย่อยทั้งหมด มันถูกเรียกเสมอก่อนวิธีการใด ๆ ของชั้นเรียน

ฉันตั้งค่าเบรกพอยต์ในหนึ่งครั้งใน iOS 6 และ dispatch_once ปรากฏขึ้นในเฟรมสแต็ก


0

Singleton Class: ไม่มีใครสามารถสร้างวัตถุมากกว่าหนึ่งคลาสในทุกกรณีหรือทุกทาง

+ (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
ถ้ามีคนโทรหา init, init จะเรียก sharedInstance, sharedInstance จะโทรหา init, init จะโทรไปที่ sharedInstance เป็นครั้งที่สองจากนั้นจะหยุดทำงาน! อย่างแรกนี่คือลูปการวนรอบแบบวนซ้ำ ประการที่สองการทำซ้ำครั้งที่สองของการเรียก dispatch_once จะล้มเหลวเนื่องจากไม่สามารถเรียกได้อีกครั้งจากภายใน dispatch_once
Chuck Krutsinger

0

มีสองประเด็นเกี่ยวกับคำตอบที่ยอมรับซึ่งอาจหรืออาจไม่เกี่ยวข้องกับวัตถุประสงค์ของคุณ

  1. หากจากวิธีการเริ่มต้นอย่างใดวิธีการ sharedInstance เรียกว่าอีกครั้ง (เช่นเพราะวัตถุอื่น ๆ จะถูกสร้างขึ้นจากที่นั่นซึ่งใช้ซิงเกิลตัน) มันจะทำให้กองล้น
  2. สำหรับลำดับชั้นของคลาสมีเพียงหนึ่งเดียว (กล่าวคือ: ชั้นแรกในลำดับชั้นที่เรียกว่าวิธี sharedInstance) แทนที่จะเป็นหนึ่งเดียวต่อชั้นคอนกรีตในลำดับชั้น

รหัสต่อไปนี้จะดูแลปัญหาทั้งสองนี้:

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

หวังว่ารหัสข้างต้นจะช่วยมันออกมา


-2

ถ้าคุณต้องการสร้างซิงเกิลตันอย่างรวดเร็ว

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

หรือ

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

คุณสามารถใช้วิธีนี้

let sharedClass = LibraryAPI.sharedInstance
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.