ฉันต้องการซ่อน (ทำให้เป็นส่วนตัว) -init
วิธีการเรียนของฉันใน Objective-C
ฉันจะทำสิ่งนั้นได้อย่างไร
NS_UNAVAILABLE
ยังอนุญาตให้ผู้โทรสามารถเรียกinit
ผ่านทางอ้อมnew
ได้ เพียงเอาชนะที่init
จะกลับมาnil
จะจัดการกับทั้งสองกรณี
ฉันต้องการซ่อน (ทำให้เป็นส่วนตัว) -init
วิธีการเรียนของฉันใน Objective-C
ฉันจะทำสิ่งนั้นได้อย่างไร
NS_UNAVAILABLE
ยังอนุญาตให้ผู้โทรสามารถเรียกinit
ผ่านทางอ้อมnew
ได้ เพียงเอาชนะที่init
จะกลับมาnil
จะจัดการกับทั้งสองกรณี
คำตอบ:
Objective-C เช่น Smalltalk ไม่มีแนวคิดของวิธีการ "ส่วนตัว" กับ "สาธารณะ" ข้อความใด ๆ ที่สามารถส่งไปยังวัตถุใด ๆ ได้ตลอดเวลา
คุณสามารถทำอะไรได้NSInternalInconsistencyException
ถ้า-init
วิธีการของคุณถูกเรียกใช้:
- (id)init {
[self release];
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"-init is not a valid initializer for the class Foo"
userInfo:nil];
return nil;
}
ทางเลือกอื่น - ซึ่งน่าจะดีกว่าในทางปฏิบัติ - คือการ-init
ทำสิ่งที่สมเหตุสมผลสำหรับชั้นเรียนของคุณหากเป็นไปได้
หากคุณกำลังพยายามทำเช่นนี้เพราะคุณกำลังพยายาม "ให้ความมั่นใจ" ว่ามีการใช้วัตถุแบบซิงเกิลไม่ต้องกังวล โดยเฉพาะไม่รำคาญกับ "แทนที่+allocWithZone:
, -init
, -retain
, -release
" วิธีการสร้าง singletons มันไม่จำเป็นเสมอไปและกำลังเพิ่มความซับซ้อนโดยไม่มีข้อได้เปรียบที่สำคัญอย่างแท้จริง
เพียงแค่เขียนรหัสของคุณเพื่อให้+sharedWhatever
วิธีการของคุณเป็นวิธีการเข้าถึงซิงเกิลตันและเอกสารที่เป็นวิธีการรับอินสแตนซ์ซิงเกิลในส่วนหัวของคุณ นั่นควรจะเป็นสิ่งที่คุณต้องการในกรณีส่วนใหญ่
alloc
และinit
มีฟังก์ชั่นรหัสของพวกเขาอย่างไม่ถูกต้องเพราะพวกเขามีระดับที่ถูกต้อง แต่กรณีที่ไม่ถูกต้อง นี่คือสาระสำคัญของหลักการห่อหุ้มใน OO คุณซ่อนสิ่งต่าง ๆ ใน API ของคุณที่คลาสอื่นไม่ต้องการหรือรับเข้าถึง คุณไม่เพียง แต่ทำให้ทุกอย่างเป็นสาธารณะและคาดหวังให้มนุษย์ติดตามทุกสิ่ง
NS_UNAVAILABLE
- (instancetype)init NS_UNAVAILABLE;
นี่เป็นแอททริบิวต์ที่ไม่มีในเวอร์ชันย่อ มันปรากฏตัวครั้งแรกใน macOS 10.7และ iOS 5 มันถูกกำหนดไว้ใน NSObjCRuntime.h #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
เป็น
มีรุ่นที่เป็น ปิดใช้งานเมธอดสำหรับไคลเอ็นต์ Swiftเท่านั้นไม่ใช่สำหรับรหัส ObjC:
- (instancetype)init NS_SWIFT_UNAVAILABLE;
unavailable
เพิ่มunavailable
คุณสมบัติให้กับส่วนหัวเพื่อสร้างข้อผิดพลาดคอมไพเลอร์ในการเรียกเพื่อเริ่ม
-(instancetype) init __attribute__((unavailable("init not available")));
หากคุณไม่มีเหตุผลเพียงพิมพ์__attribute__((unavailable))
หรือแม้กระทั่ง__unavailable
:
-(instancetype) __unavailable init;
doesNotRecognizeSelector:
ใช้doesNotRecognizeSelector:
เพื่อเพิ่ม NSInvalidArgumentException “ ระบบรันไทม์จะเรียกใช้เมธอดนี้ทุกครั้งที่วัตถุได้รับข้อความ aSelector ซึ่งไม่สามารถตอบสนองหรือส่งต่อได้
- (instancetype) init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
NSAssert
ใช้ NSAssert
เพื่อโยน NSInternalInconsistencyException และแสดงข้อความ:
- (instancetype) init {
[self release];
NSAssert(false,@"unavailable, use initWithBlah: instead");
return nil;
}
raise:format:
ใช้ raise:format:
เพื่อโยนข้อยกเว้นของคุณเอง:
- (instancetype) init {
[self release];
[NSException raise:NSGenericException
format:@"Disabled. Use +[[%@ alloc] %@] instead",
NSStringFromClass([self class]),
NSStringFromSelector(@selector(initWithStateDictionary:))];
return nil;
}
[self release]
เป็นสิ่งจำเป็นเพราะวัตถุที่ถูกแล้วalloc
ated เมื่อใช้ ARC คอมไพเลอร์จะโทรหาคุณ ไม่ว่าในกรณีใดก็ตามอย่ากังวลเมื่อคุณกำลังจะหยุดทำการโดยเจตนา
objc_designated_initializer
ในกรณีที่คุณตั้งใจจะปิดการใช้งาน init
เพื่อบังคับใช้ initializer ที่กำหนดมีคุณสมบัติสำหรับที่:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
สิ่งนี้จะสร้างคำเตือนเว้นแต่ว่าวิธีการเริ่มต้นอื่นจะเรียกmyOwnInit
ภายใน รายละเอียดจะถูกเผยแพร่ในAdopting Modern Objective-Cหลังจาก Xcode รุ่นถัดไป (ฉันเดา)
init
กว่า เนื่องจากถ้าวิธีนี้ไม่ถูกต้องคุณจะเริ่มต้นวัตถุทำไม นอกจากนั้นเมื่อขว้างปายกเว้นคุณจะสามารถที่จะระบุข้อความที่กำหนดเองบางสื่อสารที่ถูกต้องวิธีการที่นักพัฒนาในขณะที่คุณไม่ได้มีตัวเลือกดังกล่าวในกรณีของinit*
doesNotRecognizeSelector
Apple เริ่มใช้สิ่งต่อไปนี้ในไฟล์ส่วนหัวเพื่อปิดใช้งานตัวสร้าง init:
- (instancetype)init NS_UNAVAILABLE;
สิ่งนี้แสดงอย่างถูกต้องว่าเป็นข้อผิดพลาดของคอมไพเลอร์ใน Xcode โดยเฉพาะจะถูกตั้งค่าในไฟล์ส่วนหัว HealthKit หลายไฟล์ (HKUnit เป็นหนึ่งในนั้น)
หากคุณกำลังพูดถึงวิธีการเริ่มต้น - เริ่มต้นแล้วคุณไม่สามารถ มันสืบทอดมาจาก NSObject และทุก ๆ คลาสจะตอบกลับโดยไม่มีคำเตือน
คุณสามารถสร้างวิธีการใหม่พูด -initMyClass และวางไว้ในหมวดหมู่ส่วนตัวอย่างที่ Matt แนะนำ จากนั้นกำหนดเมธอด -init ดีฟอลต์เพื่อเพิ่มข้อยกเว้นถ้าถูกเรียกหรือ (ดีกว่า) เรียกไพรเวต -initMyClass ของคุณด้วยค่าเริ่มต้นบางอย่าง
หนึ่งในเหตุผลหลักที่คนดูเหมือนจะต้องการที่จะซ่อน init สำหรับวัตถุเดี่ยว หากเป็นกรณีนี้คุณไม่จำเป็นต้องซ่อน -init เพียงส่งคืนวัตถุ singleton แทน (หรือสร้างหากยังไม่มีอยู่)
ใส่ไว้ในไฟล์ส่วนหัว
- (id)init UNAVAILABLE_ATTRIBUTE;
คุณสามารถประกาศวิธีการใด ๆ ที่จะไม่สามารถใช้ได้โดยใช้ NS_UNAVAILABLE
ที่จะไม่ได้มีการใช้
ดังนั้นคุณสามารถใส่บรรทัดเหล่านี้ใต้ @interface ของคุณ
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
ยิ่งกว่านั้นกำหนดแมโครในส่วนหัวของคำนำหน้า
#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;
และ
@interface YourClass : NSObject
NO_INIT
// Your properties and messages
@end
ขึ้นอยู่กับสิ่งที่คุณหมายถึงโดย "ทำให้เป็นส่วนตัว" ใน Objective-C การเรียกใช้เมธอดบนวัตถุอาจอธิบายได้ดีกว่าว่าการส่งข้อความไปยังวัตถุนั้น ไม่มีอะไรในภาษาที่ห้ามไม่ให้ลูกค้าเรียกวิธีการใด ๆ ที่ระบุบนวัตถุ สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือไม่ประกาศเมธอดในไฟล์ส่วนหัว หากลูกค้ายังคงเรียกวิธี "ส่วนตัว" ที่มีลายเซ็นที่ถูกต้องก็จะยังคงดำเนินการที่รันไทม์
วิธีที่พบมากที่สุดในการสร้างวิธีการส่วนตัวใน Objective-C คือการสร้างหมวดหมู่ในไฟล์การนำไปใช้และประกาศวิธีการ "ซ่อน" ทั้งหมดในนั้น โปรดจำไว้ว่านี่จะไม่เป็นการป้องกันการโทรinit
แต่คอมไพเลอร์จะคายคำเตือนหากมีคนพยายามทำเช่นนี้
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
มีหัวข้อที่เหมาะสมใน MacRumors.com เกี่ยวกับหัวข้อนี้
ดีปัญหาที่คุณไม่สามารถทำให้ "ส่วนตัว / มองไม่เห็น" เป็นสาเหตุให้วิธีการ init ได้รับการส่งไปยัง id (เป็น alloc คืนรหัส) ไม่ให้ YourClass
โปรดทราบว่าจากจุดของคอมไพเลอร์ (ตัวตรวจสอบ) id สามารถ potencialy ตอบสนองต่อสิ่งที่เคยพิมพ์ (มันไม่สามารถตรวจสอบสิ่งที่จะเข้าไปใน id ที่ runtime) ดังนั้นคุณสามารถซ่อน init เมื่อไม่มีอะไรจะ (สาธารณะ = ใน ส่วนหัว) ใช้วิธีการเริ่มต้นกว่าการรวบรวมจะรู้ว่าไม่มีวิธีสำหรับ ID ที่จะตอบสนองต่อการเริ่มต้นเนื่องจากไม่มีที่ใดก็ได้ (ในแหล่งที่มาของคุณ libs ทั้งหมด ฯลฯ ... )
ดังนั้นคุณจึงไม่สามารถห้ามผู้ใช้ให้ส่งผ่าน init และคอมไพเลอร์ซึ่งได้ถูกทุบ ... แต่สิ่งที่คุณทำได้คือการป้องกันไม่ให้ผู้ใช้รับอินสแตนซ์จริงโดยการเรียก init
ง่ายๆโดยการใช้ init ซึ่งส่งกลับศูนย์และมี initializer (ส่วนตัว / ที่มองไม่เห็น) ที่ชื่อคนอื่นจะไม่ได้รับ (เช่น initOnce, initWithSpecial ... )
static SomeClass * SInstance = nil;
- (id)init
{
// possibly throw smth. here
return nil;
}
- (id)initOnce
{
self = [super init];
if (self) {
return self;
}
return nil;
}
+ (SomeClass *) shared
{
if (nil == SInstance) {
SInstance = [[SomeClass alloc] initOnce];
}
return SInstance;
}
หมายเหตุ: ใครบางคนสามารถทำได้
SomeClass * c = [[SomeClass alloc] initOnce];
และในความเป็นจริงจะส่งคืนอินสแตนซ์ใหม่ แต่ถ้า initOnce ไม่ปรากฏในโครงการของเราต่อสาธารณะ (ในส่วนหัว) ประกาศจะสร้างคำเตือน (id อาจไม่ตอบสนอง ... ) และบุคคลที่ใช้สิ่งนี้จะต้อง เพื่อทราบอย่างชัดเจนว่า initializer ที่แท้จริงคือ initOnce
เราสามารถป้องกันปัญหานี้ได้อีก แต่ไม่จำเป็น
ฉันต้องพูดถึงว่าการวางการยืนยันและการยกระดับข้อยกเว้นเพื่อซ่อนวิธีการในคลาสย่อยนั้นมีกับดักที่น่ารังเกียจสำหรับเจตนาดี
ฉันอยากจะแนะนำให้ใช้__unavailable
เป็นJano อธิบายสำหรับตัวอย่างแรกของเขาอธิบายตัวอย่างแรกของเขา
สามารถแทนที่เมธอดในคลาสย่อยได้ ซึ่งหมายความว่าหากวิธีการในซูเปอร์คลาสใช้วิธีที่เพิ่งยกข้อยกเว้นในคลาสย่อยมันอาจจะไม่ทำงานตามที่ตั้งใจไว้ พูดง่ายๆก็คือคุณเคยทำสิ่งที่ไม่ดีมาก่อน นี่เป็นความจริงด้วยวิธีการเริ่มต้นเช่นกัน นี่คือตัวอย่างของการใช้งานทั่วไปที่ค่อนข้างดังกล่าว:
- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
...bla bla...
return self;
}
- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
return self;
}
ลองนึกภาพสิ่งที่เกิดขึ้นกับ -initWithLessParameters ถ้าฉันทำสิ่งนี้ในคลาสย่อย:
- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
นี่ก็หมายความว่าคุณควรจะใช้วิธีการส่วนตัว (ซ่อน) โดยเฉพาะอย่างยิ่งในวิธีการเริ่มต้นเว้นแต่ว่าคุณวางแผนที่จะมีวิธีการแทนที่ แต่นี่เป็นหัวข้ออื่นเนื่องจากคุณไม่สามารถควบคุมการใช้ superclass ได้อย่างเต็มที่ (นี่ทำให้ฉันตั้งคำถามกับการใช้ __attribute ((objc_designated_initializer)) ว่าเป็นแนวปฏิบัติที่ไม่ดีแม้ว่าฉันจะไม่ได้ใช้ในเชิงลึกก็ตาม)
นอกจากนี้ยังหมายความว่าคุณสามารถใช้การยืนยันและข้อยกเว้นในวิธีการที่จะต้องแทนที่ในคลาสย่อย (วิธีการ "นามธรรม" เช่นเดียวกับในการสร้างระดับนามธรรมใน Objective-C )
และอย่าลืมเกี่ยวกับวิธีการ + new class
NS_UNAVAILABLE
. โดยทั่วไปฉันอยากให้คุณใช้วิธีนี้ OP จะพิจารณาทบทวนคำตอบที่ยอมรับหรือไม่ คำตอบอื่น ๆ ที่นี่ให้รายละเอียดที่เป็นประโยชน์มากมาย แต่ไม่ใช่วิธีที่ต้องการในการบรรลุเป้าหมายนี้