ฉันจะประกาศคุณสมบัติระดับคลาสใน Objective-C ได้อย่างไร


205

อาจจะชัดเจน แต่ฉันไม่รู้วิธีประกาศคุณสมบัติของคลาสใน Objective-C

ฉันต้องการแคชพจนานุกรมต่อชั้นเรียนและสงสัยว่าจะใส่ไว้ในชั้นเรียนได้อย่างไร

คำตอบ:


190

คุณสมบัติมีความหมายเฉพาะใน Objective-C แต่ฉันคิดว่าคุณหมายถึงสิ่งที่เทียบเท่ากับตัวแปรสแตติก? เช่นมีเพียงอินสแตนซ์เดียวสำหรับ Foo ทุกประเภท

ในการประกาศฟังก์ชันคลาสใน Objective-C คุณใช้เครื่องหมาย + แทน - ดังนั้นการนำไปใช้ของคุณจะมีลักษณะดังนี้:

// Foo.h
@interface Foo {
}

+ (NSDictionary *)dictionary;

// Foo.m
+ (NSDictionary *)dictionary {
  static NSDictionary *fooDict = nil;
  if (fooDict == nil) {
    // create dict
  }
  return fooDict;
}

23
ถูกต้องหรือไม่ จะไม่ตั้งค่า fooDict เป็นศูนย์เพราะบรรทัดแรกของวิธีพจนานุกรมจะส่งผลให้มีการสร้างพจนานุกรมใหม่ทุกครั้งหรือไม่
PapillonUK


59
บรรทัดคง NSDictionary * fooDict = ศูนย์; จะถูกประหารเพียงครั้งเดียว! แม้ในวิธีการที่เรียกว่าหลาย ๆ ครั้งการประกาศ (และในตัวอย่างนี้คือการเริ่มต้น) ด้วยคำหลักคงที่จะถูกละเว้นหากตัวแปรคงที่มีชื่อนี้
Binarian

3
@ BenC.R.Leggiero ใช่แน่นอน .ไวยากรณ์ -accessor ไม่เชื่อมโยงกับคุณสมบัติใน Objective-C, มันเป็นเพียงแค่รวบรวมในทางลัดสำหรับวิธีการใด ๆ ที่ผลตอบแทนบางสิ่งบางอย่างโดยไม่ต้องสละ args ใด ๆ ในกรณีนี้ผมต้องการทีผมเองชอบ.ไวยากรณ์สำหรับการใช้งานใด ๆ ที่รหัสลูกค้าตั้งใจที่จะได้รับสิ่งที่ไม่ดำเนินการ(แม้ว่ารหัสการดำเนินการอาจจะสร้างบางสิ่งบางอย่างในครั้งเดียวหรือดำเนินการผลข้างเคียง) .ไวยากรณ์การใช้งานจำนวนมากยังส่งผลให้รหัสอ่านง่ายขึ้น: การมีอยู่ของ[…]s หมายถึงสิ่งที่สำคัญที่จะทำเมื่อดึงใช้.ไวยากรณ์แทน
Slipp D. Thompson

4
ดูคำตอบของ Alex Nolasco คุณสมบัติของชั้นเรียนมีตั้งแต่ Xcode 8 release: stackoverflow.com/a/37849467/6666611
n3wbie

112

ฉันใช้โซลูชันนี้:

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

และฉันพบว่ามันมีประโยชน์อย่างมากในการแทนที่รูปแบบซิงเกิล

หากต้องการใช้งานเพียงเข้าถึงข้อมูลของคุณด้วยเครื่องหมายจุด:

Model.value = 1;
NSLog(@"%d = value", Model.value);

3
มันเจ๋งจริงๆ แต่บนโลกนี้มีความselfหมายอย่างไรในวิธีการเรียน?
ทอดด์เลห์แมน

8
@ToddLehman selfเป็นวัตถุที่ได้รับข้อความ และเนื่องจากคลาสยังเป็นวัตถุในกรณีนี้selfหมายถึงModel
spooki

6
ทำไมผู้ทะเยอทะยานถึงต้องเป็น@synchronized?
Matt Kantor

1
นี่มันเจ๋งจริงๆที่ใช้งานได้ คุณสามารถทำลายคุณสมบัติระดับชั้นเรียนของคุณเองที่ทำงานเหมือนกับของจริง ฉันเดาว่าการซิงโครไนซ์ตัวเองนั้นเทียบเท่ากับการใช้ 'อะตอมมิก' ในการประกาศคุณสมบัติของคุณและสามารถละเว้นได้หากคุณต้องการเวอร์ชั่น 'nonatomic'? นอกจากนี้ฉันจะพิจารณาการตั้งชื่อตัวแปรสำรองด้วย '_' ตามค่าเริ่มต้นโดย apple และยังปรับปรุงการอ่านได้ตั้งแต่การส่งคืน / ตั้งค่า self.value จาก getter / setter ทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุด ในความคิดของฉันนี่คือคำตอบที่ถูกต้อง
Peter Segerblom

3
มันเจ๋ง แต่ ... รหัส 10 บรรทัดเพียงเพื่อสร้าง 1 สมาชิกคงที่? แฮ็คอะไร Apple ควรทำให้คุณสมบัตินี้เป็นจริง
John Henckel

92

ดังที่เห็นใน WWDC 2016 / XCode 8 (มีอะไรใหม่ในเซสชัน LLVM @ 5: 05) คุณสมบัติคลาสสามารถประกาศได้ดังนี้

@interface MyType : NSObject
@property (class) NSString *someString;
@end

NSLog(@"format string %@", MyType.someString);

โปรดทราบว่าคุณสมบัติของคลาสจะไม่ถูกสังเคราะห์

@implementation
static NSString * _someString;
+ (NSString *)someString { return _someString; }
+ (void)setSomeString:(NSString *)newString { _someString = newString; }
@end

8
มันควรจะชัดเจนว่านี่เป็นเพียงน้ำตาลสำหรับการประกาศ accessor ดังที่คุณบันทึกไว้คุณสมบัติจะไม่ถูกสังเคราะห์: staticตัวแปร( ) ต้องยังคงถูกประกาศและใช้งานและวิธีการใช้งานอย่างชัดเจนเช่นเดียวกับที่เคยเป็นมาก่อนหน้านี้ ไวยากรณ์ Dot นั้นใช้งานได้ก่อนหน้านี้เช่นกัน ในทุกเรื่องนี้ดูเหมือนจะเป็นเรื่องใหญ่กว่าที่เป็นจริง
jscs

6
เรื่องใหญ่ก็หมายความว่าคุณสามารถเข้าถึงซิงเกิลตันจากรหัส Swift โดยไม่ต้องใช้ () และส่วนต่อท้ายประเภทจะถูกลบออกโดยการประชุม เช่น XYZMyClass.shared (Swift 3) แทน XYZMyClass.sharedMyClass ()
Ryan

สิ่งนี้ดูไม่ปลอดภัยนัก หากสิ่งนี้สามารถกลายพันธุ์จากหัวข้อที่แตกต่างในรหัสของคุณฉันจะให้แน่ใจว่าคุณจัดการกับสภาพการแข่งขันที่อาจเกิดขึ้นนี้
smileBot

อินเทอร์เฟซที่สะอาดดีสำหรับการบริโภคคลาส แต่มันก็ยังใช้งานได้มากมาย ลองใช้บล็อกแบบคงที่และมันก็ไม่สนุกเท่าไหร่ ความจริงแล้วการใช้สแตติกนั้นง่ายกว่ามาก
Departamento B

1
การปรับใช้สามารถปรับปรุงให้ง่ายขึ้นสำหรับพฤติกรรม "singleton" แบบปลอดภัยต่อเธรดโดยใช้โทเค็น dispatch_once - แต่ไม่เช่นนั้นโซลูชันจะแสดงคำตอบอย่างถูกต้อง
Motti Shneor

63

หากคุณกำลังมองหาระดับเทียบเท่าระดับของ@propertyคำตอบคือ "ไม่มีสิ่งนั้น" แต่จำไว้ว่า@propertyมันเป็นเพียงวากยสัมพันธ์เท่านั้น มันเพิ่งสร้างวิธีการวัตถุที่ตั้งชื่ออย่างเหมาะสม

คุณต้องการสร้างวิธีการเรียนที่เข้าถึงตัวแปรคงที่ซึ่งตามที่คนอื่นได้กล่าวว่ามีเพียงไวยากรณ์ที่แตกต่างกันเล็กน้อย


แม้คุณสมบัติความคิดนั้นจะเป็นประโยคมันก็ยังดีที่สามารถใช้ dot syntax สำหรับสิ่งต่าง ๆ เช่น MyClass.class แทน [MyClass class]
Zaky German

4
@ZakyGerman คุณทำได้! UIDevice.currentDevice.identifierForVendorทำงานได้สำหรับฉัน
tc

1
@tc ขอบคุณ! เติมโง่ตอนนี้ ด้วยเหตุผลบางอย่างฉันมั่นใจว่าฉันลองทำสิ่งนั้นในอดีต แต่มันไม่ได้ผล นั่นเป็นคุณสมบัติใหม่โดยบังเอิญหรือไม่?
Zaky German

1
@ZakyGerman มันได้ทำงานอย่างน้อยปีหรือสองปีสำหรับวิธีการเรียน ฉันเชื่อว่ามันได้ผลเสมอสำหรับวิธีการเช่นถ้าวิธี getter / setter มีประเภทที่คาดหวัง
tc

21

นี่เป็นวิธีที่ปลอดภัยในการทำเธรด:

// Foo.h
@interface Foo {
}

+(NSDictionary*) dictionary;

// Foo.m
+(NSDictionary*) dictionary
{
  static NSDictionary* fooDict = nil;

  static dispatch_once_t oncePredicate;

  dispatch_once(&oncePredicate, ^{
        // create dict
    });

  return fooDict;
}

การแก้ไขเหล่านี้ทำให้มั่นใจได้ว่า fooDict จะสร้างเพียงครั้งเดียว

จากเอกสารของ Apple : "dispatch_once - เรียกใช้งานวัตถุบล็อกเพียงครั้งเดียวเท่านั้นตลอดอายุการใช้งานของแอปพลิเคชัน"


3
ไม่รหัส dispatch_once ไม่เกี่ยวข้องเนื่องจาก NSDictionary แบบคงที่สามารถเริ่มต้นได้ในบรรทัดแรกของ + (NSDictionary *) วิธีการพจนานุกรมและเนื่องจากมันเป็นแบบคงที่มันจะเริ่มต้นได้เพียงครั้งเดียวเท่านั้น?
jcpennypincher

@jcpennypincher Initializer element is not a compile-time constantความพยายามที่จะเริ่มต้นในพจนานุกรมในบรรทัดเดียวกันเป็นการประกาศคงที่อัตราผลตอบแทนรวบรวมข้อผิดพลาดต่อไปนี้:
George WS

@GeorgeWS คุณจะได้รับข้อผิดพลาดนั้นเพราะคุณกำลังพยายามเริ่มต้นกับผลลัพธ์ของฟังก์ชั่น (จัดสรรและ init เป็นฟังก์ชัน) หากคุณเริ่มต้นมันเป็นศูนย์แล้วเพิ่ม if (obj == ศูนย์) และเริ่มต้นในที่นั่นคุณจะไม่เป็นไร
Rob

1
Rob นั่นไม่ปลอดภัยหรอก รหัสที่แสดงที่นี่ดีที่สุด
Ian Ollmann

ฉันชอบคำตอบนี้ดีที่สุดเพราะมันสมบูรณ์และปลอดภัยเธรด - แต่มันเกี่ยวข้องเฉพาะกับการใช้งานที่ดีขึ้นในขณะที่คำถามของ op เกี่ยวกับการประกาศคุณสมบัติของ class - ไม่ใช่การใช้งานของพวกเขา ขอบคุณอยู่ดี
Motti Shneor

11

ในฐานะของ Xcode 8 Objective-C ตอนนี้รองรับคุณสมบัติคลาส:

@interface MyClass : NSObject
@property (class, nonatomic, assign, readonly) NSUUID* identifier;
@end

เนื่องจากคุณสมบัติของคลาสจะไม่ถูกสังเคราะห์คุณต้องเขียนการใช้งานของคุณเอง

@implementation MyClass
static NSUUID*_identifier = nil;

+ (NSUUID *)identifier {
  if (_identifier == nil) {
    _identifier = [[NSUUID alloc] init];
  }
  return _identifier;
}
@end

คุณเข้าถึงคุณสมบัติคลาสโดยใช้ไวยากรณ์จุดปกติบนชื่อคลาส:

MyClass.identifier;

7

คุณสมบัติมีค่าเฉพาะในวัตถุไม่ใช่คลาส

ถ้าคุณต้องการเก็บบางสิ่งบางอย่างสำหรับวัตถุทั้งหมดของคลาสคุณต้องใช้ตัวแปรส่วนกลาง คุณสามารถซ่อนมันได้โดยประกาศstaticในไฟล์การนำไปใช้งาน

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

อีกทางเลือกหนึ่งคือการสร้างวัตถุของคลาสเฉพาะที่ประกอบด้วยทั้งพจนานุกรม 'คลาส' ของคุณและชุดของวัตถุทั้งหมดที่เกี่ยวข้องกับพจนานุกรมนี้ นี่คือสิ่งที่NSAutoreleasePoolในโกโก้


7

เริ่มต้นจาก Xcode 8 คุณสามารถใช้คุณสมบัติคลาสตามที่ Berbie ตอบ

อย่างไรก็ตามในการนำไปใช้งานคุณต้องกำหนดทั้ง class getter และ setter สำหรับคุณสมบัติ class โดยใช้ตัวแปรแบบสแตติกแทน iVar

Sample.h

@interface Sample: NSObject
@property (class, retain) Sample *sharedSample;
@end

Sample.m

@implementation Sample
static Sample *_sharedSample;
+ ( Sample *)sharedSample {
   if (_sharedSample==nil) {
      [Sample setSharedSample:_sharedSample];
   }
   return _sharedSample;
}

+ (void)setSharedSample:(Sample *)sample {
   _sharedSample = [[Sample alloc]init];
}
@end

2

หากคุณมีคุณสมบัติระดับชั้นเรียนจำนวนมากรูปแบบซิงเกิลอาจเป็นไปตามลำดับ บางสิ่งเช่นนี้

// Foo.h
@interface Foo

+ (Foo *)singleton;

@property 1 ...
@property 2 ...
@property 3 ...

@end

และ

// Foo.m

#import "Foo.h"

@implementation Foo

static Foo *_singleton = nil;

+ (Foo *)singleton {
    if (_singleton == nil) _singleton = [[Foo alloc] init];

    return _singleton;
}

@synthesize property1;
@synthesize property2;
@synthesise property3;

@end

ตอนนี้เข้าถึงคุณสมบัติระดับชั้นของคุณดังนี้:

[Foo singleton].property1 = value;
value = [Foo singleton].property2;

4
การใช้งานซิงเกิลตันนี้ไม่ปลอดภัยสำหรับเธรดอย่าใช้มัน
klefevre

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

มันจะค่อนข้างง่ายต่อการใช้dispatch_onceที่นี่
Ian MacDonald

ใบสั่งซื้อต้องการคำตอบในด้านการประกาศไม่ใช่การนำไปใช้และแม้แต่การนำไปปฏิบัติก็ไม่สมบูรณ์ (ไม่ปลอดภัยต่อเธรด)
Motti Shneor

-3

[ลองใช้วิธีนี้ง่าย ๆ ] คุณสามารถสร้างตัวแปรแบบสแตติกในคลาส Swift จากนั้นเรียกมันจากคลาส Objective-C


1
OP ไม่ได้ถามวิธีการสร้างคุณสมบัติคงที่ใน Swift สิ่งนี้ไม่ได้แก้ปัญหาของเขา
นาธานเอฟ

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