มันจะเป็นประโยชน์หรือไม่ที่จะเริ่มต้นใช้อินสแตนซ์แทน id?


226

เสียงดังกราวเพิ่มคำหลักที่instancetypeว่าเท่าที่ผมสามารถมองเห็นแทนที่idเป็นชนิดผลตอบแทนในและ-allocinit

มีประโยชน์ในการใช้instancetypeแทนidหรือไม่?


10
ไม่ .. ไม่ใช่สำหรับการจัดสรรและเริ่มต้นเนื่องจากพวกเขาใช้งานได้แล้ว จุดอินสแตนซ์ประเภทคือเพื่อให้คุณสามารถกำหนดวิธีการจัดสรร / init เช่น behavoir
hooleyhoop

@hooleyhoop พวกเขาไม่ทำงานแบบนี้ พวกเขากลับ id id คือ 'วัตถุ Obj-C' นั้นคือทั้งหมด. คอมไพเลอร์ไม่รู้อะไรเกี่ยวกับค่าส่งคืนนอกเหนือจากนั้น
griotspeak

5
พวกเขาทำงานเช่นนี้การทำสัญญาและการตรวจสอบประเภทเกิดขึ้นแล้วสำหรับ init เพียงคุณต้องการ custructors เท่านั้น nshipster.com/instancetype
kocodude

สิ่งต่าง ๆ มีระดับสูงไปหน่อยใช่แล้ว ประเภทผลลัพธ์ที่เกี่ยวข้องค่อนข้างใหม่และการเปลี่ยนแปลงอื่น ๆ หมายความว่าจะเป็นปัญหาที่ชัดเจนมากขึ้นในไม่ช้า
griotspeak

1
ฉันเพียงแค่ต้องการที่จะแสดงความคิดเห็นว่าตอนนี้บน iOS 8 เป็นจำนวนมากของวิธีการที่ใช้ในการแสดงidได้ถูกแทนที่ด้วย instancetypeแม้จากinit NSObjectหากคุณต้องการให้โค้ดของคุณเข้ากันได้กับสวิฟท์คุณต้องใช้instancetype
Hola Soy Edu Feliz Navidad

คำตอบ:


192

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

ใช้งานเฉพาะที่ซึ่งมันสมเหตุสมผลแน่นอน (เช่นวิธีการที่ส่งคืนอินสแตนซ์ของคลาสนั้น) id ยังคงมีประโยชน์


8
alloc, initฯลฯ ได้รับการส่งเสริมโดยอัตโนมัติinstancetypeโดยรวบรวม ไม่ได้หมายความว่าจะไม่มีประโยชน์ มี แต่มันไม่ใช่อย่างนั้น
สตีเวนฟิชเชอร์

10
มันมีประโยชน์สำหรับตัวสร้างความสะดวกสบายส่วนใหญ่
Catfish_Man

5
มันได้รับการแนะนำให้รู้จักกับ (และเพื่อช่วยสนับสนุน) ARC พร้อมกับการเปิดตัว Lion ในปี 2011 สิ่งหนึ่งที่ทำให้เกิดความยุ่งยากในการนำไปใช้อย่างแพร่หลายในโกโก้คือวิธีการสมัครทั้งหมดต้องได้รับการตรวจสอบเพื่อดูว่าพวกเขาทำ alloc] เพราะมันจะทำให้สับสนอย่างยิ่งที่จะทำ [SomeSubClass ConvenienceConstructor] พร้อมกับ + ConvenienceConstructor ที่ประกาศให้ส่งคืนชนิดของอินสแตนซ์และให้มันไม่ส่งคืนอินสแตนซ์ของ SomeSubClass
Catfish_Man

2
'id' จะถูกแปลงเป็นประเภทอินสแตนซ์เท่านั้นเมื่อคอมไพเลอร์สามารถอนุมานวิธีตระกูล แน่นอนที่สุดมันไม่ได้ทำเพื่อตัวสร้างความสะดวกสบายหรือวิธีการส่งคืนรหัสอื่น ๆ
Catfish_Man

4
โปรดทราบว่าใน iOS 7 วิธีการมากมายใน Foundation ได้ถูกแปลงเป็นประเภทอินสแตนซ์ ฉันเห็นการจับนี้เป็นการส่วนตัวอย่างน้อย 3 กรณีของรหัสที่มีอยู่ล่วงหน้าที่ไม่ถูกต้อง
Catfish_Man

336

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

ในความเป็นจริงนี่คือสิ่งที่ Apple พูดในเรื่องนี้:

ในรหัสของคุณให้แทนที่ค่าที่เกิดขึ้นidซึ่งเป็นค่าตอบแทนinstancetypeที่เหมาะสม โดยทั่วไปจะเป็นกรณีสำหรับinitวิธีการและวิธีการเรียนโรงงาน แม้ว่าคอมไพเลอร์จะแปลงวิธีการที่ขึ้นต้นด้วย“ alloc,”“ init” หรือ“ ใหม่” โดยอัตโนมัติและมีประเภทidการส่งคืนเพื่อส่งคืนinstancetypeแต่จะไม่แปลงวิธีอื่น ๆ การประชุมเชิงวัตถุประสงค์ -C คือการเขียนinstancetypeอย่างชัดเจนสำหรับวิธีการทั้งหมด

ลองทำต่อไปและอธิบายว่าทำไมจึงเป็นความคิดที่ดี

ก่อนคำจำกัดความบางอย่าง:

 @interface Foo:NSObject
 - (id)initWithBar:(NSInteger)bar; // initializer
 + (id)fooWithBar:(NSInteger)bar;  // class factory
 @end

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

นี่ไม่ใช่ปัญหาทางวิชาการ ยกตัวอย่างเช่น[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]จะสร้างข้อผิดพลาดใน Mac OS X ( เท่านั้น ) วิธีการหลายชื่อ 'writeData:' พบกับผลลัพธ์ที่ไม่ตรงกันชนิดพารามิเตอร์หรือแอตทริบิวต์ เหตุผลก็คือว่าทั้งสอง NSFileHandle และ NSURLHandle writeData:ให้ ตั้งแต่[NSFileHandle fileHandleWithStandardOutput]ส่งคืนidคอมไพเลอร์ไม่แน่ใจว่าคลาสwriteData:ใดที่ถูกเรียกใช้

คุณต้องหลีกเลี่ยงปัญหานี้โดยใช้:

[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];

หรือ:

NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];

แน่นอนว่าทางออกที่ดีกว่าคือการประกาศเป็นส่งคืนfileHandleWithStandardOutput instancetypeจากนั้นนักแสดงหรือการมอบหมายก็ไม่จำเป็น

(โปรดสังเกตว่าบน iOS เช่นนี้จะไม่สร้างข้อผิดพลาดเป็นเพียงNSFileHandleให้writeData:มี. ตัวอย่างอื่น ๆ ที่มีอยู่เช่นlengthที่ส่งกลับCGFloatจากUILayoutSupportแต่NSUIntegerจากNSString.)

หมายเหตุ : เนื่องจากผมเขียนนี้ส่วนหัว MacOS ได้รับการแก้ไขให้กลับมาเป็นแทนNSFileHandleid

สำหรับ initializers มันซับซ้อนกว่า เมื่อคุณพิมพ์สิ่งนี้:

- (id)initWithBar:(NSInteger)bar

…ผู้เรียบเรียงจะแกล้งคุณพิมพ์สิ่งนี้แทน:

- (instancetype)initWithBar:(NSInteger)bar

นี่เป็นสิ่งจำเป็นสำหรับ ARC นี้จะอธิบายในภาษาเสียงดังกราวส่วนขยายประเภทผลที่เกี่ยวข้อง นี่คือเหตุผลที่ผู้คนจะบอกคุณว่าไม่จำเป็นต้องใช้instancetypeแม้ว่าฉันจะโต้แย้งคุณควร ส่วนที่เหลือของคำตอบนี้เกี่ยวข้องกับเรื่องนี้

มีสามข้อได้เปรียบ:

  1. ชัดเจน. รหัสของคุณกำลังทำสิ่งที่มันพูดแทนที่จะเป็นอย่างอื่น
  2. แบบแผน คุณกำลังสร้างนิสัยที่ดีสำหรับเวลาที่สำคัญ
  3. ความมั่นคง คุณได้สร้างความมั่นคงให้กับโค้ดของคุณซึ่งทำให้อ่านง่ายขึ้น

ชัดเจน

มันเป็นความจริงว่าไม่มีทางเทคนิคประโยชน์ที่จะกลับมาจากinstancetype initแต่นี้เป็นเพราะคอมไพเลอร์โดยอัตโนมัติแปลงไปid instancetypeคุณพึ่งพาการเล่นโวหารนี้; ในขณะที่คุณกำลังเขียนว่าinitส่งกลับคอมไพเลอร์จะถูกแปลความหมายของมันราวกับว่ามันส่งกลับidinstancetype

สิ่งเหล่านี้เทียบเท่ากับคอมไพเลอร์:

- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;

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

แบบแผน

ในขณะที่มีความแตกต่างใด ๆ กับinitและวิธีการอื่น ๆ ที่มีคือความแตกต่างทันทีที่คุณกำหนดโรงงานชั้นเรียน

ทั้งสองนี้ไม่เทียบเท่า:

+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

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

ความมั่นคง

ในที่สุดลองนึกภาพถ้าคุณรวมมันเข้าด้วยกัน: คุณต้องการinitฟังก์ชั่นและโรงงานคลาส

ถ้าคุณใช้idสำหรับinitคุณท้ายด้วยรหัสเช่นนี้

- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

แต่ถ้าคุณใช้instancetypeคุณจะได้รับสิ่งนี้:

- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

มันสอดคล้องและอ่านได้มากขึ้น พวกเขากลับมาเหมือนเดิมและตอนนี้เห็นได้ชัด

ข้อสรุป

หากคุณไม่ได้ตั้งใจเขียนโค้ดสำหรับคอมไพเลอร์เก่าคุณควรใช้instancetypeตามความเหมาะสม

idคุณควรลังเลก่อนที่จะเขียนข้อความที่ส่งกลับ ถามตัวเองว่า: นี่เป็นตัวอย่างของคลาสนี้หรือไม่? ถ้าเป็นinstancetypeเช่นนั้น

มีหลายกรณีที่คุณต้องส่งคืนidแต่คุณอาจใช้instancetypeบ่อยกว่านี้


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

6
ต้องมีความชัดเจน: ผมเชื่อว่าคำตอบโดย Catfish_Man ถูกต้องเฉพาะในประโยคแรกของ คำตอบโดย hooleyhoop ถูกต้องยกเว้นสำหรับประโยคแรกของ มันเป็นภาวะที่กลืนไม่เข้าคายไม่ออก ฉันต้องการให้สิ่งที่เมื่อเวลาผ่านไปจะเห็นว่ามีประโยชน์มากขึ้นและถูกต้องมากกว่าทั้ง (ด้วยความเคารพอย่างสูงจากทั้งสองสิ่งนี้ชัดเจนยิ่งกว่าตอนที่เขียนคำตอบ)
สตีเฟ่นฟิชเชอร์

1
เมื่อเวลาผ่านไปฉันหวังว่าอย่างนั้น ฉันไม่สามารถคิดเหตุผลใด ๆ ที่จะทำไม่ได้เว้นแต่ว่า Apple รู้สึกว่าการรักษาความเข้ากันได้ของแหล่งข้อมูลด้วย (เช่น) กำหนด NSArray ใหม่ให้กับ NSString โดยไม่จำเป็น
สตีเว่นฟิชเชอ

4
instancetypeเทียบกับidการตัดสินใจสไตล์ไม่ได้จริงๆ การเปลี่ยนแปลงเมื่อเร็ว ๆ นี้instancetypeทำให้เห็นชัดเจนว่าเราควรใช้instancetypeในสถาน-initที่ที่เราหมายถึง 'ตัวอย่างของชั้นเรียนของฉัน'
griotspeak

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

10

คำตอบข้างต้นมีมากเกินพอที่จะอธิบายคำถามนี้ ฉันแค่อยากจะเพิ่มตัวอย่างสำหรับผู้อ่านที่จะเข้าใจมันในแง่ของการเข้ารหัส

ClassA

@interface ClassA : NSObject

- (id)methodA;
- (instancetype)methodB;

@end

คลาส B

@interface ClassB : NSObject

- (id)methodX;

@end

TestViewController.m

#import "ClassA.h"
#import "ClassB.h"

- (void)viewDidLoad {

    [[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime

    [[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}

สิ่งนี้จะสร้างข้อผิดพลาดคอมไพเลอร์เท่านั้นหากคุณไม่ใช้ #import "ClassB.h" มิฉะนั้นคอมไพเลอร์จะสมมติว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่ ตัวอย่างเช่นคอมไพล์ต่อไปนี้ แต่ล้มเหลวขณะรันไทม์: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey: @ "key"];
OlDor

1

คุณยังสามารถดูรายละเอียดได้ที่The Designated Initializer

**

INSTANCETYPE

** คำหลักนี้สามารถใช้ได้กับประเภทการส่งคืนเท่านั้นซึ่งตรงกับประเภทผู้รับที่ส่งคืน ประกาศวิธีการเสมอที่จะส่งกลับประเภทอินสแตนซ์ ทำไมไม่สร้าง Return type Party ให้เป็นอินสแตนซ์ปาร์ตี้ล่ะ? นั่นจะทำให้เกิดปัญหาหากคลาสปาร์ตี้นั้นคลาสย่อย คลาสย่อยจะสืบทอดเมธอดทั้งหมดจากปาร์ตี้รวมถึง initializer และชนิดส่งคืน หากอินสแตนซ์ของคลาสย่อยถูกส่งข้อความ initializer นี้นั่นจะส่งคืนหรือไม่ ไม่ใช่ตัวชี้ไปยังอินสแตนซ์ปาร์ตี้ แต่เป็นตัวชี้ไปยังอินสแตนซ์ของคลาสย่อย คุณอาจคิดว่าไม่มีปัญหาฉันจะแทนที่ initializer ในคลาสย่อยเพื่อเปลี่ยนประเภทการส่งคืน แต่ใน Objective-C คุณไม่สามารถมีสองวิธีที่มีตัวเลือกเดียวกันและชนิดส่งคืนที่แตกต่างกัน โดยระบุว่าวิธีการเริ่มต้นส่งคืน "

ID

** ก่อนที่อินสแตนซ์ประเภทได้รับการแนะนำใน Objective-C initializers จะส่งคืน id (eye-dee) ประเภทนี้ถูกกำหนดเป็น "ตัวชี้ไปยังวัตถุใด ๆ " (id เหมือนโมฆะ * ใน C มาก) จากการเขียนนี้เท็มเพลตคลาส XCode ยังคงใช้ id เป็นชนิดส่งคืนของ initializers ที่เพิ่มในโค้ดสำเร็จรูป ซึ่งแตกต่างจากอินสแตนซ์ประเภท id สามารถใช้เป็นมากกว่าประเภทการส่งคืน คุณสามารถประกาศตัวแปรหรือพารามิเตอร์วิธีการของ id ชนิดเมื่อคุณไม่แน่ใจว่าวัตถุชนิดใดที่ตัวแปรจะจบลงด้วยการชี้ไปที่ คุณสามารถใช้ id เมื่อใช้การแจงนับอย่างรวดเร็วเพื่อวนซ้ำของอาร์เรย์ของวัตถุหลายชนิดหรือไม่ทราบชนิด โปรดทราบว่าเนื่องจาก id ไม่ได้กำหนดเป็น "ตัวชี้ไปยังวัตถุใด ๆ " คุณจะไม่รวม * เมื่อประกาศตัวแปรหรือพารามิเตอร์วัตถุประเภทนี้

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