สร้างซิงเกิลตันโดยใช้ dispatch_once ของ GCD ใน Objective-C


341

หากคุณสามารถกำหนดเป้าหมาย iOS 4.0 ขึ้นไป

การใช้ GCD เป็นวิธีที่ดีที่สุดในการสร้างซิงเกิลตันใน Objective-C (ปลอดภัยไหม)

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

2
มีวิธีป้องกันผู้ใช้ของคลาสจากการเรียก alloc / copy หรือไม่?
Nicolas Miari

3
dispatch_once_t และ dispatch_once ดูเหมือนจะมีการนำมาใช้ใน 4.0 ไม่ใช่ 4.1 (ดู: developer.apple.com/library/ios/#documentation/Performance/ ...... )
Ben Flynn

1
วิธีนี้กลายเป็นปัญหาหาก init ต้องการใช้วัตถุ singleton รหัสของ Matt Gallagher ทำงานให้ฉันมากกว่าสองสามครั้ง cocoawithlove.com/2008/11/...
เกร็ก

1
ฉันรู้ว่ามันไม่สำคัญในตัวอย่างนี้ แต่ทำไมคนไม่ใช้ 'ใหม่' มากกว่านี้ dispatch_once (& ครั้งเดียว ^ {sharedInstance = [ใหม่ด้วยตนเอง];} แค่ดู bit neater มันก็เท่ากับการจัดสรร + init
Chris Hatton

3
ตรวจสอบให้แน่ใจว่าได้เริ่มใช้ประเภทการคืนสินค้าinstancetypeแล้ว idเสร็จรหัสจะดีกว่ามากเมื่อใช้ที่แทน
Mr Rogers

คำตอบ:


215

นี่เป็นวิธีที่ยอมรับได้อย่างสมบูรณ์และปลอดภัยสำหรับการสร้างตัวอย่างของคลาสของคุณ ในทางเทคนิคอาจไม่ใช่ "ซิงเกิลตัน" (ซึ่งในนั้นมีเพียง 1 วัตถุเท่านั้น) แต่ตราบใดที่คุณใช้[Foo sharedFoo]วิธีการเข้าถึงวัตถุเพียงอย่างเดียวนี่ก็เพียงพอแล้ว


4
คุณจะปล่อยมันอย่างไร
samvermette

65
@samvermette คุณทำไม่ได้ จุดเดียวคือมันจะมีอยู่เสมอ ดังนั้นคุณจะไม่ปล่อยมันและหน่วยความจำจะถูกเรียกคืนด้วยกระบวนการที่ออก
Dave DeLong

6
@Dave DeLong: ในความคิดของฉันมีจุดประสงค์เดียวไม่ได้เป็นอมตะแน่นอน แต่ความมั่นใจว่าเรามีหนึ่งตัวอย่าง เกิดอะไรขึ้นถ้าสิ่งนั้นลดลงเซมาฟอร์เดี่ยว? คุณไม่สามารถบอกได้ว่ามันจะมีอยู่เสมอ
jacekmigacz

4
@hooleyhoop ใช่ในเอกสาร "หากถูกเรียกพร้อมกันจากหลายเธรดฟังก์ชันนี้จะรอพร้อมกันจนกว่าบล็อกจะเสร็จสิ้น"
เควิน

3
@ WalterMartinVargas-Pena การอ้างอิงที่แข็งแกร่งจัดขึ้นโดยตัวแปรคงที่
Dave DeLong

36

instancetype

instancetypeเป็นเพียงหนึ่งในส่วนขยายภาษาจำนวนมากที่จะObjective-Cมีการเพิ่มมากขึ้นในแต่ละรุ่นใหม่

รู้ว่ามันรักมัน

และนำมาเป็นตัวอย่างว่าการใส่ใจในรายละเอียดในระดับต่ำสามารถให้ข้อมูลเชิงลึกเกี่ยวกับวิธีการที่มีประสิทธิภาพในการแปลง Objective-C

อ้างอิงที่นี่: 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
เคล็ดลับที่น่าทึ่งขอบคุณ! instancetype เป็นคีย์เวิร์ดเชิงบริบทที่สามารถใช้เป็นชนิดผลลัพธ์เพื่อส่งสัญญาณว่าเมธอดส่งคืนชนิดผลลัพธ์ที่เกี่ยวข้อง ... ด้วยอินสแตนซ์ประเภทคอมไพเลอร์จะอนุมานชนิดได้อย่างถูกต้อง
Fattie

1
ไม่ชัดเจนสำหรับฉันความหมายของตัวอย่างทั้งสองนี้ที่นี่มีค่าเท่ากันหรือไม่ หนึ่งเป็นที่นิยมกว่าคนอื่น ๆ ? จะดีถ้าผู้เขียนสามารถเพิ่มคำอธิบายเล็กน้อยสำหรับเรื่องนี้
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

init ไม่สามารถใช้ได้อย่างไร อย่างน้อยมันก็ใช้ได้สำหรับหนึ่งเดียวinitหรือไม่?
ฮันนี่

2
Singleton ควรมีจุดเชื่อมต่อเพียงจุดเดียว และจุดนี้มีการแชร์เนื้อหา หากเรามีวิธีการเริ่มต้นในไฟล์ * .h มากกว่าที่คุณสามารถสร้างอินสแตนซ์ซิงเกิลอื่นได้ สิ่งนี้ขัดแย้งกับคำจำกัดความของซิงเกิล
Sergey Petruk

1
@ asma22 __attribute __ ((ไม่พร้อมใช้งาน ()) ทำให้ไม่สามารถใช้วิธีการเหล่านี้หากโปรแกรมเมอร์อื่นต้องการใช้วิธีการทำเครื่องหมายว่าใช้งานไม่ได้เขาจะได้รับข้อผิดพลาด
Sergey Petruk

1
ฉันสมบูรณ์ได้รับและฉันมีความสุขผมได้เรียนรู้สิ่งใหม่ ๆ ที่ไม่ถูกต้องอะไรกับคำตอบของคุณอาจจะเป็นแค่บิตสับสนสำหรับมือใหม่ ...
น้ำผึ้ง

1
ตัวอย่างนี้ใช้ได้เฉพาะMySingletonกับตัวอย่างที่MySingleton.mฉันโทรหา[super alloc]
Sergey Petruk

6

คุณสามารถหลีกเลี่ยงคลาสที่ได้รับการจัดสรรด้วยการเขียนทับวิธีการจัดสรร

@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
นี่ตอบคำถามของฉันในความคิดเห็นด้านบน ไม่ใช่ว่าฉันเป็นคนป้องกันการเขียนโปรแกรม แต่ ...
นิโคลัส Miari

5

เดฟนั้นถูกต้องแล้ว คุณอาจต้องการตรวจสอบเอกสารของ Apple เกี่ยวกับการสร้างซิงเกิลตันสำหรับเคล็ดลับในการนำวิธีการอื่นมาใช้เพื่อให้แน่ใจว่าสามารถสร้างได้เพียงหนึ่งรายการเท่านั้นหากคลาสเลือกที่จะไม่ใช้เมธอด sharedFoo


8
เอ๊ะ ... นั่นไม่ใช่ตัวอย่างที่ยอดเยี่ยมที่สุดในการสร้างซิงเกิลตัน ไม่จำเป็นต้องยกเลิกวิธีการจัดการหน่วยความจำ
Dave DeLong

19
สิ่งนี้ไม่ถูกต้องอย่างสมบูรณ์โดยใช้ ARC
logancautrell

เอกสารที่อ้างถึงถูกเลิกใช้งานแล้ว นอกจากนี้คำตอบที่เชื่อมโยงไปยังเนื้อหาภายนอกโดยทั่วไปมักเป็นคำตอบที่ไม่ดี อย่างน้อยส่วนที่เกี่ยวข้องตัดตอนในคำตอบของคุณ อย่ากังวลที่นี่เว้นแต่คุณต้องการให้คนรุ่นหลังได้รับความรอด
Toolbear

4

หากคุณต้องการให้แน่ใจว่า [[MyClass จัดสรร] init] ส่งคืนวัตถุเดียวกันกับ sharedInstance (ไม่จำเป็นในความคิดของฉัน แต่บางคนต้องการมัน) ที่สามารถทำได้อย่างง่ายดายและปลอดภัยโดยใช้ dispatch_once ที่สอง:

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

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

    return sharedInstance;
}

สิ่งนี้อนุญาตให้มีการรวมกันของ [[MyClass alloc] init] และ [MyClass sharedInstance] เพื่อส่งคืนวัตถุเดียวกัน; [MyClass sharedInstance] จะมีประสิทธิภาพมากกว่านี้เล็กน้อย วิธีการทำงาน: [MyClass sharedInstance] จะโทร [[MyClass alloc] init] ครั้งเดียว รหัสอื่นสามารถเรียกได้เช่นกันจำนวนครั้งใด ๆ ผู้เรียกคนแรกที่เริ่มต้นจะทำการเริ่มต้น "ปกติ" และเก็บวัตถุเดี่ยวไว้ในวิธีการเริ่มต้น การเรียกใช้เพื่อเริ่มต้นในภายหลังใด ๆ จะไม่สนใจสิ่งที่จัดสรรคืนและส่งกลับ sharedInstance เดียวกัน ผลลัพธ์ของการจัดสรรจะถูกจัดสรรคืน

วิธี + sharedInstance จะทำงานเหมือนที่เคยทำมา หากไม่ใช่ผู้โทรรายแรกที่โทร [[MyClass alloc] init] แสดงว่าผลลัพธ์ของ init ไม่ใช่ผลลัพธ์ของการเรียกจัดสรร แต่เป็น OK


2

คุณถามว่านี่เป็น "วิธีที่ดีที่สุดในการสร้างซิงเกิลตัน"

ความคิดเล็กน้อย:

  1. อันดับแรกใช่นี่เป็นโซลูชันที่ปลอดภัยสำหรับเธรด dispatch_onceรูปแบบนี้เป็นวิธีที่ทันสมัยและปลอดภัยในการสร้างซิงเกิลตันใน Objective-C ไม่ต้องกังวล

  2. คุณถามว่านี่เป็นวิธีที่ "ดีที่สุด" หรือไม่ หนึ่งควรยอมรับว่าinstancetypeและ[[self alloc] init]อาจทำให้เข้าใจผิดเมื่อใช้ร่วมกับซิงเกิล

    ประโยชน์ของการinstancetypeเป็นว่ามันเป็นวิธีที่ชัดเจนในการประกาศว่าชั้นสามารถ subclassed โดยไม่ต้องหันไปประเภทของidเช่นที่เราต้องทำในปีกลาย

    แต่staticวิธีการนี้นำเสนอความท้าทายระดับคลาสย่อย เกิดอะไรขึ้นถ้าImageCacheและBlobCachesingletons ทั้งสอง subclasses จากCachesuperclass โดยไม่ต้องดำเนินการของตัวเองsharedCacheวิธีการ?

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

    เพื่อให้ทำงานได้คุณจะต้องตรวจสอบให้แน่ใจว่าคลาสย่อยใช้วิธีการของตนเองsharedInstance(หรืออะไรก็ตามที่คุณเรียกว่าสำหรับคลาสเฉพาะของคุณ)

    บรรทัดล่างต้นฉบับของคุณsharedInstance ดูเหมือนจะรองรับคลาสย่อย แต่ไม่รองรับ หากคุณต้องการสนับสนุนคลาสย่อยอย่างน้อยที่สุดก็มีเอกสารประกอบที่เตือนผู้พัฒนาในอนาคตว่าพวกเขาจะต้องแทนที่วิธีนี้

  3. เพื่อการทำงานร่วมกันที่ดีที่สุดกับ Swift คุณอาจต้องการกำหนดให้เป็นคุณสมบัติไม่ใช่วิธีการเรียนเช่น:

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

    จากนั้นคุณสามารถไปข้างหน้าและเขียนทะเยอทะยานสำหรับคุณสมบัตินี้ (การใช้งานจะใช้dispatch_onceรูปแบบที่คุณแนะนำ):

    + (Foo *)sharedFoo { ... }

    ประโยชน์ของสิ่งนี้คือถ้าผู้ใช้ Swift ใช้งานพวกเขาจะทำสิ่งที่ชอบ:

    let foo = Foo.shared

    หมายเหตุไม่มี()เนื่องจากเราใช้มันเป็นทรัพย์สิน เริ่มต้น Swift 3 นี่คือวิธีการเข้าถึงซิงเกิลโดยทั่วไป ดังนั้นการกำหนดเป็นคุณสมบัติช่วยอำนวยความสะดวกในการทำงานร่วมกัน

    นอกจากนี้ถ้าคุณดูว่า Apple นิยามซิงเกิลตันของพวกเขาอย่างไรนี่คือรูปแบบที่พวกเขานำมาใช้เช่นการNSURLSessionกำหนดซิงเกิลดังต่อไปนี้:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. การพิจารณาการใช้งานร่วมกันของ Swift ที่น้อยมากคือชื่อของซิงเกิล sharedInstanceมันเป็นเรื่องที่ดีที่สุดถ้าคุณสามารถรวมชื่อประเภทที่มากกว่า ตัวอย่างเช่นถ้าชั้นเป็นคุณอาจกำหนดเดี่ยวทรัพย์สินFoo sharedFooหรือถ้าชั้นเป็นคุณอาจเรียกทรัพย์สินDatabaseManager sharedManagerจากนั้นผู้ใช้ Swift สามารถทำได้:

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

    เห็นได้ชัดว่าถ้าคุณต้องการใช้จริงๆsharedInstanceคุณสามารถประกาศชื่อ Swift ได้ทุกเมื่อที่คุณต้องการ:

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

    เห็นได้ชัดว่าเมื่อเขียนรหัส Objective-C เราไม่ควรปล่อยให้การทำงานร่วมกันของ Swift มีมากกว่าข้อควรพิจารณาด้านการออกแบบอื่น ๆ แต่ถึงกระนั้นถ้าเราสามารถเขียนโค้ดที่รองรับทั้งสองภาษาได้อย่างสวยงาม

  5. ผมเห็นด้วยกับคนอื่น ๆ ที่ชี้ให้เห็นว่าถ้าคุณอยากให้เรื่องนี้เป็นซิงเกิลจริงที่นักพัฒนาไม่สามารถ / ไม่ควร (ตั้งใจ) ยกตัวอย่างกรณีของตัวเองunavailableรอบคัดเลือกในinitและnewระมัดระวัง


0

ในการสร้างเธรดเดี่ยวที่ปลอดภัยคุณสามารถทำสิ่งนี้ได้:

@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

และบล็อกนี้อธิบายซิงเกิลตันที่ดีมากใน objc / cocoa


คุณกำลังเชื่อมโยงไปยังบทความที่เก่ามากในขณะที่ OP ขอให้มีลักษณะเกี่ยวกับการใช้งานที่ทันสมัยที่สุด
vikingosegundo

1
คำถามเกี่ยวกับการใช้งานเฉพาะ คุณเพิ่งโพสต์การติดตั้งอื่น เพราะคุณไม่ได้พยายามตอบคำถามด้วยซ้ำ
vikingosegundo

1
@vikingosegundo ผู้ถามถามสภาพอากาศว่า GCD เป็นวิธีที่ดีที่สุดในการสร้างเธรดเดี่ยวที่ปลอดภัยคำตอบของฉันให้ทางเลือกอื่นผิดหรือเปล่า?
Hancock_Xu

ผู้ถามถามว่าการใช้งานบางอย่างปลอดภัยหรือไม่ เขาไม่ได้ถามหาทางเลือก
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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.