คุณสมบัติอ่านอย่างเดียวใน Objective-C?


94

ฉันได้ประกาศคุณสมบัติแบบอ่านอย่างเดียวในอินเทอร์เฟซของฉันดังนี้:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

บางทีฉันอาจจะเข้าใจคุณสมบัติผิด แต่ฉันคิดว่าเมื่อคุณประกาศเป็นreadonlyคุณสามารถใช้ตัวตั้งค่าที่สร้างขึ้นภายใน.mไฟล์การนำไปใช้งาน ( ) แต่เอนทิตีภายนอกไม่สามารถเปลี่ยนค่าได้ คำถาม SO นี้บอกว่านั่นคือสิ่งที่ควรเกิดขึ้น นั่นคือพฤติกรรมที่ฉันตามมา อย่างไรก็ตามเมื่อพยายามใช้ standard setter หรือ dot syntax เพื่อตั้งค่าeventDomainภายในเมธอด init ของฉันมันทำให้ฉันมีunrecognized selector sent to instance.ข้อผิดพลาด แน่นอนฉันกำลัง@synthesizeมีทรัพย์สิน พยายามใช้มันดังนี้:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

ฉันเข้าใจผิดเกี่ยวกับการreadonlyประกาศเกี่ยวกับทรัพย์สินหรือไม่? หรือมีอย่างอื่นเกิดขึ้น?

คำตอบ:


118

คุณต้องบอกคอมไพเลอร์ว่าคุณต้องการเซ็ตเตอร์ด้วย วิธีทั่วไปคือใส่ไว้ในนามสกุลคลาสในไฟล์. m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
ไม่ใช่หมวดหมู่ มันเป็นส่วนขยายคลาส (ตามที่ Eiko กล่าวไว้) แตกต่างกันอย่างชัดเจนแม้ว่าจะใช้เพื่อวัตถุประสงค์ที่คล้ายคลึงกัน ตัวอย่างเช่นคุณไม่สามารถดำเนินการดังกล่าวข้างต้นในหมวดหมู่ที่แท้จริง
bbum

2
ฉันสังเกตว่าคลาสที่สืบทอดมาจากคลาสที่มีคุณสมบัติส่วนขยายคลาสจะไม่เห็นคลาสนั้น เช่นการสืบทอดเห็นเฉพาะอินเทอร์เฟซภายนอก ยืนยัน?
Yogev Shelly

5
นั่นไม่ยุติธรรมเลยทีเดียว อาจแตกต่างกัน แต่เรียกว่า "หมวดหมู่ที่ไม่ระบุชื่อ"
MaxGabriel

3
นี่นอกเหนือจากการประกาศครั้งแรกของ op ในอินเทอร์เฟซสาธารณะหรือไม่? ความหมายการประกาศส่วนขยายคลาสนี้จะแทนที่การประกาศเริ่มต้นใน.h? มิฉะนั้นฉันไม่เห็นว่าสิ่งนี้จะเปิดเผยผู้ตั้งค่าสาธารณะได้อย่างไร ขอบคุณ
Madbreaks

4
@Madbreaks มันเพิ่มเติม แต่อยู่ในไฟล์. m ดังนั้นคอมไพเลอร์จึงรู้ที่จะทำให้มันเขียนสำหรับคลาสนั้น แต่การใช้งานภายนอกยังคง จำกัด ให้อ่านได้อย่างเดียวตามที่คาดไว้
Eiko

38

Eiko และคนอื่น ๆ ให้คำตอบที่ถูกต้อง

นี่เป็นวิธีที่ง่ายกว่า: เข้าถึงตัวแปรสมาชิกส่วนตัวโดยตรง

ตัวอย่าง

ในไฟล์. h ส่วนหัว:

@property (strong, nonatomic, readonly) NSString* foo;

ในไฟล์การนำไปใช้. m:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

นั่นคือทั้งหมดที่คุณต้องการ ไม่มีอะไรจุกจิกกวนใจ

รายละเอียด

ตั้งแต่ Xcode 4.4 และ LLVM Compiler 4.0 ( คุณสมบัติใหม่ใน Xcode 4.4.2) ) คุณไม่จำเป็นต้องยุ่งกับงานที่กล่าวถึงในคำตอบอื่น ๆ :

  • synthesizeคำหลัก
  • การประกาศตัวแปร
  • การประกาศคุณสมบัติอีกครั้งในไฟล์. m การนำไปใช้งาน

หลังจากการประกาศคุณสมบัติfooคุณสามารถสันนิษฐาน Xcode _fooได้เพิ่มตัวแปรสมาชิกส่วนตัวที่ชื่อด้วยคำนำหน้าของขีดล่างนี้:

ถ้าคุณสมบัติถูกประกาศreadwrite, Xcode สร้างวิธีทะเยอทะยานที่มีชื่อและหมาชื่อfoo setFooเมธอดเหล่านี้ถูกเรียกโดยปริยายเมื่อคุณใช้สัญกรณ์จุด (Object.myMethod ของฉัน) หากมีการประกาศคุณสมบัติจะreadonlyไม่มีการสร้างตัวตั้งค่า นั่นหมายความว่าตัวแปร backing ที่ตั้งชื่อด้วยเครื่องหมายขีดล่างไม่ใช่อ่านอย่างเดียว readonlyหมายเพียงว่าไม่มีวิธีหมาถูกสังเคราะห์และดังนั้นจึงใช้เครื่องหมายจุดที่จะตั้งค่าล้มเหลวด้วยรวบรวมข้อผิดพลาด สัญกรณ์จุดล้มเหลวเนื่องจากคอมไพลเลอร์หยุดคุณไม่ให้เรียกใช้เมธอด (ตัวตั้งค่า) ที่ไม่มีอยู่

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

การใช้self->เป็นวิธีการเข้าถึงตัวแปรสมาชิกของออบเจ็กต์ / อินสแตนซ์ คุณอาจจะข้ามสิ่งนั้นไปได้และใช้แค่ชื่อ var แต่ฉันชอบใช้ลูกศร + ตัวเองมากกว่าเพราะมันทำให้โค้ดของฉันเป็นเอกสาร เมื่อคุณเห็นสิ่งที่self->_fooคุณรู้โดยไม่มีความคลุมเครือซึ่ง_fooเป็นตัวแปรสมาชิกในอินสแตนซ์นี้


อย่างไรก็ตามการอภิปรายข้อดีข้อเสียของผู้เข้าถึงคุณสมบัติเทียบกับการเข้าถึง ivar โดยตรงเป็นวิธีการรักษาที่รอบคอบที่คุณจะอ่านในหนังสือProgramming iOSของDr.Matt Neuberg ฉันพบว่าการอ่านและอ่านซ้ำมีประโยชน์มาก


1
บางทีคุณควรเพิ่มว่าไม่ควรเข้าถึงตัวแปรสมาชิกโดยตรง (จากที่ไม่มีคลาส)! การทำเช่นนั้นถือเป็นการละเมิด OOP (การซ่อนข้อมูล)
HAS

2
@HAS แก้ไขสถานการณ์ที่นี่เป็นส่วนตัวตั้งค่าตัวแปรสมาชิกไม่เห็นจากภายนอกชั้นนี้ นั่นคือประเด็นทั้งหมดของคำถามของผู้โพสต์ต้นฉบับ: การประกาศคุณสมบัติreadonlyเพื่อไม่ให้คลาสอื่นตั้งค่าได้
Basil Bourque

ฉันกำลังดูโพสต์นี้เพื่อดูวิธีสร้างคุณสมบัติแบบอ่านอย่างเดียว สิ่งนี้ไม่ได้ผลสำหรับฉัน มันอนุญาตให้ฉันกำหนดค่าใน init แต่ฉันยังสามารถเปลี่ยน _variable ในภายหลังด้วยการกำหนด มันไม่เพิ่มข้อผิดพลาดหรือคำเตือนของคอมไพเลอร์
netskink

@BasilBourque ขอบคุณมากสำหรับการแก้ไขที่เป็นประโยชน์สำหรับคำถาม "การคงอยู่" นั้น!
GhostCat

36

อีกวิธีหนึ่งที่ฉันพบในการทำงานกับคุณสมบัติแบบอ่านอย่างเดียวคือการใช้ @synthesize เพื่อระบุที่เก็บสำรอง ตัวอย่างเช่น

@interface MyClass

@property (readonly) int whatever;

@end

จากนั้นในการนำไปใช้งาน

@implementation MyClass

@synthesize whatever = m_whatever;

@end

วิธีการของคุณสามารถตั้งค่าm_whateverได้เนื่องจากเป็นตัวแปรสมาชิก


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

(ในไฟล์ส่วนหัว)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

จากนั้นในการนำไปใช้งาน

@synthesize myProperty = m_propertyBackingStore;

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

น่าเสียใจเล็กน้อยในแง่ของการซ่อนและการห่อหุ้มข้อมูล


1
วิธีแรกที่คุณอธิบายนั้นจัดการได้ง่ายกว่าคำตอบที่ยอมรับ แค่ "@synthesize foo1, foo2, foo3;" ใน. m และทั้งหมดเป็นสิ่งที่ดี
sudo

20

ดูการปรับแต่งคลาสที่มีอยู่ในเอกสาร iOS

readonly ระบุว่าคุณสมบัติเป็นแบบอ่านอย่างเดียว หากคุณระบุแบบอ่านอย่างเดียวต้องใช้เมธอด getter ใน @implementation เท่านั้น หากคุณใช้ @synthesize ในบล็อกการนำไปใช้ระบบจะสังเคราะห์เฉพาะเมธอด getter เท่านั้น ยิ่งไปกว่านั้นหากคุณพยายามกำหนดค่าโดยใช้ dot syntax คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์

คุณสมบัติอ่านอย่างเดียวมีเมธอด getter เท่านั้น คุณยังคงสามารถตั้งค่า ivar สำรองได้โดยตรงภายในคลาสของคุณสมบัติหรือใช้การเข้ารหัสค่าคีย์


9

คุณกำลังเข้าใจคำถามอื่นผิด ในคำถามนั้นมีส่วนขยายคลาสประกาศดังนี้:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

นั่นคือสิ่งที่สร้าง setter ที่มองเห็นได้เฉพาะในการนำไปใช้งานของคลาส ดังที่ Eiko กล่าวไว้คุณต้องประกาศส่วนขยายคลาสและลบล้างการประกาศคุณสมบัติเพื่อบอกให้คอมไพเลอร์สร้าง setter เฉพาะในคลาส


5

ทางออกที่สั้นที่สุดคือ:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2

หากคุณสมบัติถูกกำหนดเป็นแบบอ่านอย่างเดียวนั่นหมายความว่าจะไม่มีตัวตั้งค่าที่สามารถใช้ได้ทั้งภายในกับคลาสหรือภายนอกจากคลาสอื่น ๆ (กล่าวคือคุณจะมี "getter" ก็ต่อเมื่อมีเหตุผล)

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

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