ใน Objective-C เหตุใดฉันจึงควรตรวจสอบว่าตนเอง = [super init] ไม่ใช่ศูนย์หรือไม่?


165

ฉันมีคำถามทั่วไปเกี่ยวกับการเขียนวิธีการเริ่มต้นใน Objective-C

ฉันเห็นมันทุกหนทุกแห่ง (รหัสของ Apple หนังสือรหัสโอเพนซอร์สและอื่น ๆ ) ว่าวิธีการเริ่มต้นควรตรวจสอบว่าตัวเอง = [super init] ไม่ได้เป็นศูนย์ก่อนที่จะดำเนินการเริ่มต้นต่อไป

แม่แบบ Apple เริ่มต้นสำหรับวิธีการเริ่มต้นคือ:

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

ทำไม?

ฉันหมายความว่าเมื่อ init จะกลับมาเป็นศูนย์ได้อย่างไร ถ้าฉันโทรหา init บน NSObject แล้วกลับมาว่างเปล่าบางอย่างก็ต้องเมาแน่ ๆ ใช่มั้ย และในกรณีนี้คุณอาจไม่เขียนโปรแกรม ...

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


1
Wil Shipley โพสต์บทความที่เกี่ยวข้องกับเรื่องนี้ในขณะที่กลับ [self = [stupid init];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) อ่านความคิดเห็นด้วยเช่นกันบางสิ่งที่ดี
Ryan Townshend

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

1
ดูเหมือนว่า Wil จะทำคดีให้มากกว่านี้สำหรับการไม่กำหนดตัวเองใหม่ในช่วงเริ่มต้นโดยไม่รู้ตัวว่า [super init] อาจไม่ส่งคืนผู้รับ
Jasarien

3
Wil ได้เปลี่ยนความคิดของเขาตั้งแต่แรกโพสต์นั้น
bbum

5
ฉันเคยเห็นคำถามนี้มานานแล้วและเพิ่งพบมันอีกครั้ง สมบูรณ์ +1
Dan Rosenstark

คำตอบ:


53

ตัวอย่างเช่น:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... และต่อไป (หมายเหตุ: NSData อาจส่งข้อยกเว้นหากไม่มีไฟล์) มีบางพื้นที่ที่ไม่มีศูนย์ที่กลับมาเป็นพฤติกรรมที่คาดหวังเมื่อมีปัญหาเกิดขึ้นและด้วยเหตุนี้จึงเป็นวิธีปฏิบัติมาตรฐานในการตรวจสอบศูนย์ตลอดเวลาเพื่อความมั่นคง


10
ใช่ แต่นี่ไม่ใช่วิธีการเริ่มต้นของคลาสที่เกี่ยวข้อง NSData สืบทอดมาจาก NSObject NSData ตรวจสอบว่า [super init] ส่งคืนศูนย์หรือไม่ นั่นคือสิ่งที่ฉันถามที่นี่ ขออภัยหากผมไม่ได้ล้าง ...
Jasarien

9
ถ้าคุณจะคลาสย่อยคลาสเหล่านั้นจะมีโอกาสที่ค่อนข้างจริงที่ [super init] จะส่งคืนศูนย์ ไม่ใช่คลาสทั้งหมดที่เป็น subclasses โดยตรงของ NSObject ไม่มีการรับประกันใด ๆ ว่าจะไม่ส่งคืนไม่มี มันเป็นเพียงการฝึกการเข้ารหัสที่ได้รับการสนับสนุนโดยทั่วไป
Chuck

7
ฉันสงสัย NSObject init สามารถคืนค่าศูนย์ได้ในทุกกรณี หากคุณมีหน่วยความจำไม่เพียงพอการจัดสรรจะล้มเหลว แต่ถ้าประสบความสำเร็จฉันสงสัยว่า init จะล้มเหลว - NSObject ไม่มีแม้แต่อินสแตนซ์ของตัวแปรใด ๆ ยกเว้นคลาส ใน GNUStep มันมีการใช้งานเพียงแค่ "คืนตัวเอง" และการแยกส่วนบน Mac ก็เหมือนกัน ทั้งหมดนี้เป็นของหลักสูตรที่ไม่เกี่ยวข้อง - เพียงทำตามสำนวนมาตรฐานและคุณไม่ต้องกังวลว่ามันจะเป็นไปได้หรือไม่
Peter N Lewis

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

4
ถ้า alloc ส่งคืนค่าศูนย์, init จะถูกส่งไปที่ศูนย์ซึ่งจะส่งผลให้ไม่มีศูนย์ซึ่งจะสิ้นสุดลงด้วยตนเองเป็นศูนย์
.

50

สำนวนนี้เป็นมาตรฐานเพราะมันใช้งานได้ในทุกกรณี

ในขณะที่ผิดปกติจะมีกรณีที่ ...

[super init];

... ส่งคืนอินสแตนซ์ที่แตกต่างกันดังนั้นจึงต้องมอบหมายให้เป็นของตนเอง

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

บรรทัดล่างคือว่ามันเป็นรูปแบบที่ถูกต้องในการใช้เอกสารและถ้าคุณไม่ได้ใช้มันแสดงว่าคุณทำผิด


3
สิ่งนี้ยังคงเป็นจริงในแง่ของตัวระบุความผิดพลาดหรือไม่? ถ้า initializer ของ superclass ของฉันไม่เป็นโมฆะแล้วมันยังคงคุ้มค่ากับความยุ่งเหยิงที่เพิ่มเข้ามาในการตรวจสอบหรือไม่? (แม้ว่า NSObject ตัวเองดูเหมือนจะไม่มีอะไรที่เป็นไปได้สำหรับ-init... )
natevw

มีกรณีที่[super init]ส่งกลับศูนย์เมื่อซูเปอร์คลาสโดยตรงคือNSObjectอะไร? นี่ไม่ใช่กรณีที่ "ทุกอย่างพังหรือไม่"
Dan Rosenstark

1
@DanRosenstark ไม่ใช่ว่าNSObjectเป็นซูเปอร์คลาสโดยตรง แต่ ... แม้ว่าคุณจะประกาศNSObjectว่าเป็นซูเปอร์คลาสโดยตรง แต่บางสิ่งอาจถูกแก้ไขในรันไทม์เพื่อให้NSObjectการติดตั้งใช้งานinitไม่ใช่สิ่งที่เรียกว่าจริง
bbum

1
ขอบคุณมาก @bbum สิ่งนี้ช่วยฉันในการแก้ไขข้อผิดพลาด เป็นการดีที่จะออกกฎบางอย่าง!
Dan Rosenstark

26

ฉันคิดว่าในชั้นเรียนส่วนใหญ่ถ้าค่าตอบแทนจาก [super init] เป็นศูนย์และคุณตรวจสอบตามที่แนะนำโดยวิธีปฏิบัติมาตรฐานแล้วส่งคืนก่อนกำหนดหากไม่มีศูนย์โดยทั่วไปแอปของคุณจะยังทำงานไม่ถูกต้อง หากคุณคิดเกี่ยวกับมันแม้ว่าถ้า (ตัวเอง! = ไม่มี) การตรวจสอบจะมีการดำเนินงานที่เหมาะสมของชั้นเรียนของคุณ, 99.99% ของเวลาที่คุณจริงทำความต้องการตัวเองให้เป็นที่ไม่ใช่ศูนย์ ทีนี้สมมติว่าไม่ว่าด้วยเหตุผลใดก็ตาม [super init] ก็คืนค่าศูนย์โดยทั่วไปการตรวจสอบของคุณต่อศูนย์จะส่งผ่านบั๊กไปยังผู้โทรในชั้นเรียนของคุณซึ่งมันอาจจะล้มเหลวอยู่ดี ที่ประสบความสำเร็จ

โดยทั่วไปสิ่งที่ฉันได้รับคือ 99.99% ของเวลาถ้า (ตัวเอง! = ไม่มี) ไม่ซื้ออะไรเลยในแง่ของความแข็งแกร่งมากขึ้นเนื่องจากคุณเพิ่งผ่านเจ้าชู้ไปยังผู้เรียกร้องของคุณ เพื่อให้สามารถจัดการกับสิ่งนี้ได้อย่างสมบูรณ์คุณจะต้องทำการตรวจสอบในลำดับชั้นการโทรทั้งหมดของคุณ และถึงอย่างนั้นสิ่งเดียวที่จะซื้อคุณก็คือแอปของคุณจะล้มเหลวเล็กน้อย / เรียบร้อยดีขึ้น แต่มันก็จะล้มเหลว

หากคลาสห้องสมุดตัดสินใจโดยไม่มีกฎเกณฑ์ที่จะคืนค่าใด ๆ อันเป็นผลมาจาก [super init] คุณก็ค่อนข้างจะดีอยู่ดีและนั่นเป็นข้อบ่งชี้ว่าผู้เขียนของคลาสไลบรารีนั้นผิดพลาดในการนำไปใช้

ฉันคิดว่านี่เป็นคำแนะนำในการเข้ารหัสแบบดั้งเดิมมากกว่าเดิมเมื่อแอพทำงานในหน่วยความจำที่ จำกัด มากขึ้น

แต่สำหรับรหัสระดับ C ฉันยังคงมักจะตรวจสอบค่าส่งคืนของ malloc () กับตัวชี้ NULL ในขณะที่สำหรับ Objective-C จนกระทั่งฉันพบหลักฐานไปในทางตรงกันข้ามฉันคิดว่าฉันมักจะข้ามการตรวจสอบ if (self! = nil) ทำไมความแตกต่าง?

เพราะที่ระดับ C และ malloc ในบางกรณีคุณสามารถกู้คืนได้จริงบางส่วน ในขณะที่ฉันคิดว่าใน Objective-C ใน 99.99% ของกรณีถ้า [super init] กลับมาเป็นศูนย์คุณจะ f *** ed แม้ว่าคุณจะพยายามจัดการมัน คุณอาจปล่อยให้แอปพังและจัดการกับผลที่ตามมา


6
พูดได้ดี. ฉันสองที่
Roger CS Wernersson

3
+1 ฉันเห็นด้วยอย่างยิ่ง เพียงเล็กน้อย: ฉันไม่เชื่อว่ารูปแบบนั้นเป็นผลมาจากเวลาที่การจัดสรรล้มเหลวบ่อยขึ้น โดยปกติแล้วการจัดสรรจะเสร็จสิ้นในเวลาที่เรียกว่า init หากการจัดสรรล้มเหลวจะไม่เรียกใช้ init
Nikolai Ruhe

8

นี่เป็นบทสรุปของความคิดเห็นด้านบน

nilสมมติว่าผลตอบแทนซับคลาส จะเกิดอะไรขึ้น

หากคุณไม่ปฏิบัติตามอนุสัญญา

รหัสของคุณจะพังระหว่างinitวิธีการของคุณ (ยกเว้นกรณีที่initไม่มีนัยสำคัญ)

ถ้าคุณทำตามอนุสัญญาไม่รู้ว่าซุปเปอร์คลาสอาจกลับมาไม่มีศูนย์ (คนส่วนใหญ่ท้ายที่นี่)

รหัสของคุณเป็นแบบ probyby จะพังในภายหลังในบางกรณีเนื่องจากอินสแตนซ์ของคุณคือnilที่ที่คุณคาดหวังสิ่งที่แตกต่าง หรือโปรแกรมของคุณจะทำงานโดยไม่คาดคิดโดยไม่หยุดทำ โอ้ที่รัก! คุณต้องการสิ่งนี้หรือไม่ ฉันไม่รู้ ...

หากคุณทำตามอนุสัญญาให้อนุญาตคลาสย่อยของคุณเพื่อส่งกลับศูนย์

เอกสารรหัสของคุณ (!) ควรระบุอย่างชัดเจนว่า: "ส่งคืน ... หรือไม่มี" และส่วนที่เหลือของรหัสของคุณจะต้องเตรียมพร้อมสำหรับการจัดการสิ่งนี้ ตอนนี้มันสมเหตุสมผลแล้ว


5
ฉันคิดว่าประเด็นที่น่าสนใจตรงนี้คือตัวเลือก # 1 นั้นดีกว่าตัวเลือก # 2 อย่างชัดเจน หากมีสถานการณ์ที่เกิดขึ้นจริงซึ่งคุณอาจต้องการให้ init ของคลาสย่อยคืนค่าศูนย์ให้เลือก # 3 หากเหตุผลเดียวที่จะเกิดขึ้นเป็นเพราะข้อผิดพลาดในรหัสของคุณแล้วใช้ # 1 การใช้ตัวเลือก # 2 เป็นเพียงการทำให้แอปพลิเคชันของคุณล่าช้าจนกว่าจะถึงเวลาอันควรและทำให้งานของคุณเมื่อคุณแก้ไขข้อผิดพลาดที่ยากขึ้นมาก มันเหมือนจับข้อยกเว้นอย่างเงียบ ๆ และดำเนินการต่อโดยไม่ต้องจัดการ
Mark Amery

หรือคุณสลับไปที่รวดเร็วและใช้ตัวเลือก
AndrewSB

7

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

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


1
ด้วยสิ่งนี้ในใจมันจะเป็นการดีหรือไม่ที่จะตรวจสอบศูนย์หลังจากเริ่มต้นตัวแปรและก่อนเรียกใช้ฟังก์ชัน เช่นFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software

การสืบทอดNSObjectไม่รับประกันว่า-initจะให้คุณNSObjectเช่นกันหากนับจำนวนรันไทม์ที่แปลกใหม่เช่น GNUstep รุ่นเก่า (ซึ่งส่งคืนGSObject) ดังนั้นไม่ว่าจะมีอะไรให้ตรวจสอบและมอบหมาย
Maxthon Chan

3

คุณพูดถูกคุณสามารถเขียนได้บ่อยครั้ง[super init]แต่นั่นไม่เหมาะกับคลาสย่อยของอะไรเลย ผู้คนมักจะจำรหัสบรรทัดมาตรฐานหนึ่งบรรทัดและใช้งานได้ตลอดเวลาแม้ในบางครั้งก็จำเป็นเท่านั้นดังนั้นเราจึงได้มาตรฐานif (self = [super init])ซึ่งใช้ทั้งความเป็นไปได้ที่จะถูกส่งคืนและความเป็นไปได้ของวัตถุอื่นที่ไม่ใช่selfการส่งคืน เข้าบัญชี.


3

ข้อผิดพลาดทั่วไปคือการเขียน

self = [[super alloc] init];

ซึ่งส่งคืนอินสแตนซ์ของซูเปอร์คลาสซึ่งไม่ใช่สิ่งที่คุณต้องการในตัวสร้างคลาสย่อย / init คุณได้รับวัตถุที่ไม่ตอบสนองต่อวิธีการ subclass ซึ่งอาจทำให้เกิดความสับสนและทำให้เกิดข้อผิดพลาดที่สับสนเกี่ยวกับการไม่แก้ไขวิธีการหรือตัวระบุที่ไม่พบ ฯลฯ

self = [super init]; 

เป็นสิ่งจำเป็นถ้าชั้นซุปเปอร์มีสมาชิก (ตัวแปรหรือวัตถุอื่น ๆ ) ในการเริ่มต้นครั้งแรกก่อนที่จะตั้งค่าสมาชิก subclasses' มิฉะนั้นรันไทม์ ObjC เริ่มต้นพวกเขาทั้งหมดเพื่อ0หรือไม่มี ( ไม่เหมือนกับ ANSI C ซึ่งมักจัดสรรหน่วยความจำโดยไม่ต้องล้างข้อมูลเลย )

และใช่การเริ่มต้นคลาสพื้นฐานสามารถล้มเหลวได้เนื่องจากข้อผิดพลาดหน่วยความจำไม่เพียงพอส่วนประกอบที่ขาดหายไปความล้มเหลวในการจัดหาทรัพยากร ฯลฯ ดังนั้นการตรวจสอบศูนย์ไม่มีความฉลาดและใช้เวลาน้อยกว่าสองสามมิลลิวินาที


2

นี่คือการตรวจสอบว่า intialazation ทำงาน, คำสั่ง if ส่งกลับจริงถ้าเมธอด init ไม่ได้ส่งคืนศูนย์ดังนั้นจึงเป็นวิธีการตรวจสอบการสร้างวัตถุที่ทำงานอย่างถูกต้อง ไม่กี่เหตุผลที่ฉันคิดได้ว่า init อาจล้มเหลวอาจเป็นวิธี init แบบ overriden ที่ super class ไม่รู้หรือบางอย่างฉันไม่คิดว่ามันเป็นเรื่องธรรมดา แต่ถ้ามันเกิดขึ้นมันจะไม่มีอะไรดีไปกว่าที่จะเกิดความผิดพลาดฉันจึงตรวจสอบมันเสมอ ...


มันคืออะไร แต่จะเรียกมันด้วยกันจะเกิดอะไรขึ้นถ้าจัดสรร ails f?
แดเนียล

ฉันจินตนาการว่าการจัดสรรล้มเหลวแล้ว init จะถูกส่งไปที่ศูนย์มากกว่าตัวอย่างของคลาสที่คุณกำลังเรียกใช้ init ในกรณีนั้นจะไม่มีอะไรเกิดขึ้นและจะไม่มีการใช้รหัสใด ๆ ในการทดสอบว่า [super init] ส่งคืนศูนย์หรือไม่
Jasarien

2
การจัดสรรหน่วยความจำไม่ได้ทำใน + alloc เสมอ พิจารณากรณีของกลุ่มระดับ; NSString ไม่ทราบว่าจะใช้คลาสย่อยใดจนกว่าจะมีการเรียกใช้งานเริ่มต้น
bbum

1

ใน OS X ไม่น่า-[NSObject init]จะล้มเหลวเนื่องจากเหตุผลด้านหน่วยความจำ ไม่สามารถพูดแบบเดียวกันสำหรับ iOS

นอกจากนี้ยังเป็นวิธีปฏิบัติที่ดีสำหรับการเขียนเมื่อซับคลาสคลาสที่อาจส่งคืนnilด้วยเหตุผลใดก็ตาม


2
ทั้งบน iOS และ Mac OS -[NSObject init]เป็นมากไม่น่าจะล้มเหลวเนื่องจากเหตุผลหน่วยความจำที่จะไม่จัดสรรหน่วยความจำใด ๆ
นิโคไล Ruhe

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