ทำไมคุณถึงต้องใช้ ivar?


153

ฉันมักจะเห็นคำถามนี้ถามวิธีอื่นเช่นต้องมี ivar ทุกตัวหรือไม่ (และฉันชอบคำตอบของบีบีมต่อคำถามนี้)

ฉันใช้คุณสมบัติเกือบทั้งหมดในรหัสของฉัน อย่างไรก็ตามบ่อยครั้งที่ฉันทำงานกับผู้รับเหมาที่พัฒนาบน iOS มาเป็นเวลานานและเป็นโปรแกรมเมอร์เกมแบบดั้งเดิม เขาเขียนโค้ดที่ประกาศเกือบจะไม่มีคุณสมบัติใด ๆ และโน้มตัวลงบน ivars ฉันคิดว่าเขาทำสิ่งนี้เพราะ 1. ) เขาคุ้นเคยกับมันเนื่องจากคุณสมบัติไม่ได้มีอยู่เสมอจนกว่า Objective C 2.0 (Oct '07) และ 2) สำหรับการเพิ่มประสิทธิภาพขั้นต่ำที่จะไม่ทะลุผ่านตัวตั้งค่า

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

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

นอกจากนี้เพื่อความกระจ่างฉันแทนที่ผู้ตั้งค่าและผู้เรียกใช้ตามความจำเป็นและใช้ ivar ที่สัมพันธ์กับคุณสมบัตินั้นภายในตัวรับ / ตัวตั้งค่า อย่างไรก็ตามนอก getter / setter หรือ init ฉันมักจะใช้self.myPropertyไวยากรณ์


แก้ไข 1

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

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

และมีอยู่ในชั้นเรียนต่อเนื่อง:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

หากต้องการให้มัน "สมบูรณ์แบบ" อย่างสมบูรณ์เพียงแค่ประกาศในชั้นเรียนต่อเนื่อง


2
ถอนรากถอนโคนสำหรับคำถามที่น่าสนใจ - ใส่อย่างดีและหนึ่งที่ฉันต้องการที่จะได้ยินกรณีสำหรับ ivars เพราะดูเหมือนว่าฉันได้รับการสอนให้ทำในแบบของแซม
Damo

2
โปรดทราบว่าการนับการอ้างอิงอัตโนมัติ (ARC) ใช้ประโยชน์การจัดการหน่วยความจำแบบเดียวกันกับ ivars เป็นคุณสมบัติดังนั้นในรหัส ARC ความแตกต่างนั้นเกี่ยวกับการห่อหุ้ม
benzado

1
คำถามของคุณและโดยเฉพาะอย่างยิ่งการแก้ไข 1 ส่วนที่ให้ข้อมูลมากกว่าจริง ๆ แล้วคำตอบที่เลือก
user523234

1
หากต้องการแก้ไข 1: ฉันคิดว่าเป็นไปได้ที่จะอ่านและเขียนทุกคุณสมบัติแม้ว่าจะมีการประกาศเพียงครั้งเดียวในรูปแบบ. h โดยใช้ Key-Value-Coding เช่น: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian

1
@Sam ต่อการแก้ไข 1 ของคุณ: หากคุณใช้คุณสมบัติส่วนตัวและใช้ส่วนขยายคลาส / ความต่อเนื่องในไฟล์. m จะไม่สามารถมองเห็นได้สำหรับคลาสย่อย คุณต้องเขียนรหัสอีกครั้งหรือใช้อีก. h กับนามสกุลชั้น ง่ายขึ้นด้วย @ ป้องกัน / ค่าเริ่มต้น
Binarian

คำตอบ:


100

encapsulation

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

ประสิทธิภาพ

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

ประเภทที่ไม่เกี่ยวข้อง

ตัวอย่าง: หากคุณมีประเภท C ++ การเข้าถึงโดยตรงเป็นเพียงวิธีที่ดีกว่าในบางครั้ง ประเภทอาจไม่สามารถคัดลอกได้หรืออาจไม่สามารถคัดลอกได้

multithreading

ivars ของคุณหลายคนมีรหัสเป็นของตัวเอง คุณต้องตรวจสอบความถูกต้องของข้อมูลในบริบทแบบมัลติเธรด ดังนั้นคุณอาจสนับสนุนการเข้าถึงสมาชิกหลายคนโดยตรงในส่วนที่สำคัญ หากคุณยึดติดกับ accessors สำหรับข้อมูลที่เป็นรหัสล็อคของคุณต้องเป็น reentrant และคุณมักจะจบลงด้วยการเข้าซื้อกิจการอื่น ๆ อีกมากมาย (มากขึ้นอย่างมากในบางครั้ง)

ความถูกต้องของโปรแกรม

เนื่องจากคลาสย่อยสามารถแทนที่เมธอดใด ๆ ในที่สุดคุณอาจเห็นว่ามีความแตกต่างทางความหมายระหว่างการเขียนอินเทอร์เฟซกับการจัดการสถานะของคุณอย่างเหมาะสม การเข้าถึงโดยตรงสำหรับความถูกต้องของโปรแกรมนั้นเป็นเรื่องปกติโดยเฉพาะอย่างยิ่งในรัฐที่สร้างขึ้นบางส่วน - ใน initializers ของคุณและในที่deallocดีที่สุดคือการใช้การเข้าถึงโดยตรง นอกจากนี้คุณยังอาจพบนี้ร่วมกันในการใช้งานของตัวเข้าถึงเป็นตัวสร้างความสะดวกสบายcopy, mutableCopyและการเก็บ / การใช้งานเป็นอันดับ

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

ขนาดไบนารี

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

ลดความซับซ้อน

ในบางกรณีมันก็ไม่จำเป็นอย่างสมบูรณ์ที่จะเพิ่ม + type + ดูแลสิ่งที่เพิ่มพิเศษสำหรับนั่งร้านสำหรับตัวแปรอย่างง่ายเช่นบูลส่วนตัวที่เขียนในวิธีการหนึ่งและอ่านในอีกวิธีหนึ่ง


ไม่ได้หมายความว่าการใช้คุณสมบัติหรืออุปกรณ์เสริมนั้นไม่ดี - แต่ละเครื่องมีข้อดีและข้อ จำกัด ที่สำคัญ เช่นเดียวกับภาษาและวิธีการออกแบบ OO มากมายคุณควรสนับสนุน accessors ที่มีทัศนวิสัยที่เหมาะสมใน ObjC จะมีบางครั้งที่คุณต้องเบี่ยงเบน ด้วยเหตุนี้ฉันคิดว่ามันเป็นการดีที่สุดที่จะ จำกัด การเข้าถึงโดยตรงไปยังการนำไปปฏิบัติซึ่งประกาศให้ใช้ ivar (เช่นประกาศ@private)


กำลังแก้ไข 1:

พวกเราส่วนใหญ่จำวิธีการเรียกตัวเข้าถึงที่ซ่อนอยู่แบบไดนามิก (ตราบใดที่เรารู้ชื่อ ... ) ในขณะเดียวกันพวกเราส่วนใหญ่ยังไม่ได้จดจำวิธีการเข้าถึง ivars ที่ไม่สามารถมองเห็นได้อย่างถูกต้อง (เกิน KVC) ความต่อเนื่องในชั้นเรียนช่วยได้ แต่จะแนะนำช่องโหว่

วิธีแก้ปัญหานี้ชัดเจน:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

ตอนนี้ลองใช้กับ ivar เท่านั้นและไม่มี KVC


@Sam ขอบคุณและเป็นคำถามที่ดี! ความซับซ้อน: แน่นอนมันไปทั้งสองวิธี การห่อหุ้มใหม่ - อัปเดต
justin

@bbum RE: ตัวอย่างที่กว้างขวางแม้ว่าฉันจะเห็นด้วยกับคุณว่ามันเป็นทางออกที่ผิด แต่ฉันไม่สามารถจินตนาการได้ว่ามีผู้พัฒนา objc จำนวนมากที่เชื่อว่ามันจะไม่เกิดขึ้น ฉันเคยเห็นมาแล้วในโปรแกรมของผู้อื่นและ App Store ได้ไปไกลถึงการห้ามการใช้ Apple API ส่วนตัว
justin

1
คุณไม่สามารถเข้าถึง ivar ส่วนตัวด้วย object-> foo ได้หรือไม่? ไม่ยากที่จะจำ
Nick Lockwood

1
ฉันหมายถึงคุณสามารถเข้าถึงได้โดยใช้การกำหนดตัวชี้จากวัตถุโดยใช้ไวยากรณ์ C -> คลาส Objective-C นั้นเป็นโครงสร้างเพียงแค่ภายใต้ประทุนและให้ตัวชี้ไปยังโครงสร้างไวยากรณ์ของ C สำหรับการเข้าถึงสมาชิกคือ -> ซึ่งทำงานสำหรับ ivars ในคลาส C วัตถุประสงค์ด้วย
Nick Lockwood

1
@NickLockwood ถ้า ivar คือ@privateคอมไพเลอร์ควรห้ามการเข้าถึงของสมาชิกนอกคลาสและวิธีการเช่น - นั่นไม่ใช่สิ่งที่คุณเห็น?
justin

76

สำหรับฉันมันมักจะมีประสิทธิภาพ การเข้าถึง ivar ของวัตถุนั้นเร็วเท่ากับการเข้าถึงสมาชิก struct ใน C โดยใช้ตัวชี้ไปยังหน่วยความจำที่มี struct ดังกล่าว ในความเป็นจริงวัตถุ Object-C นั้นเป็น C โครงสร้างตั้งอยู่ในหน่วยความจำที่จัดสรรแบบไดนามิก สิ่งนี้มักจะเร็วที่สุดเท่าที่โค้ดของคุณจะได้รับแม้แต่รหัสแอสเซมบลีที่เพิ่มประสิทธิภาพด้วยมือก็อาจเร็วกว่า

การเข้าถึง ivar ผ่าน getter / settings เกี่ยวข้องกับการเรียกใช้เมธอด Objective-C ซึ่งช้ากว่ามาก (อย่างน้อย 3-4 ครั้ง) กว่าการเรียกใช้ฟังก์ชัน "ปกติ" C และแม้แต่การเรียกใช้ฟังก์ชัน C ปกติจะช้ากว่าหลายเท่า การเข้าถึงสมาชิก struct ขึ้นอยู่กับคุณสมบัติของคุณสมบัติของคุณการใช้ setter / getter ที่สร้างโดยคอมไพเลอร์อาจเกี่ยวข้องกับการเรียกใช้ฟังก์ชัน C อีกครั้งเพื่อฟังก์ชั่นobjc_getProperty/ objc_setPropertyเนื่องจากสิ่งเหล่านี้จะต้องretain/ copy/ autoreleaseวัตถุตามความจำเป็นและดำเนินการ spinlocking สำหรับคุณสมบัติอะตอมเพิ่มเติม สิ่งนี้อาจมีราคาแพงมากและฉันไม่ได้พูดถึงช้าลง 50%

ลองทำสิ่งนี้:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

เอาท์พุท:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

นี้เป็น 4.28 ครั้งช้าและนี่คือไม่ใช่อะตอม int ดั้งเดิมสวยมากกรณีที่ดีที่สุด ; กรณีอื่น ๆ ส่วนใหญ่ยิ่งแย่ลง (ลองใช้NSString *ทรัพย์สินอะตอม!) ดังนั้นหากคุณสามารถใช้ชีวิตด้วยความจริงที่ว่าการเข้าถึง ivar แต่ละครั้งช้ากว่าที่ควรจะเป็น 4-5 เท่าการใช้คุณสมบัติจะดี (อย่างน้อยเมื่อมันมาถึงประสิทธิภาพ) อย่างไรก็ตามมีสถานการณ์มากมายที่การลดลงของประสิทธิภาพนั้น ยอมรับไม่ได้อย่างสมบูรณ์

อัปเดต 2015-10-20

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

รหัสด้านล่างกำหนดAccountวัตถุ บัญชีมีคุณสมบัติที่อธิบายชื่อ ( NSString *) เพศ ( enum) และอายุ ( unsigned) ของเจ้าของรวมถึงยอดคงเหลือ ( int64_t) วัตถุบัญชีมีinitวิธีการและcompare:วิธีการ compare:วิธีหมายถึง: คำสั่งซื้อก่อนที่หญิงชายชื่อสั่งซื้อตามลำดับตัวอักษรคำสั่งหนุ่มสาวก่อนเก่าคำสั่งซื้อยอดเงินต่ำไปสูง

อันที่จริงมีอยู่สองชั้นบัญชีและAccountA AccountBหากคุณดูการใช้งานของพวกเขาคุณจะสังเกตเห็นว่าพวกเขาเกือบจะเหมือนกันทั้งหมดยกเว้นหนึ่งcompare:วิธี: AccountAวัตถุเข้าถึงคุณสมบัติของตัวเองโดยวิธี (ทะเยอทะยาน) ในขณะที่AccountBวัตถุเข้าถึงคุณสมบัติของตนเองโดย ivar นั่นคือความแตกต่างเท่านั้น! พวกเขาทั้งคู่เข้าถึงคุณสมบัติของวัตถุอื่นเพื่อเปรียบเทียบโดยทะเยอทะยาน (การเข้าถึงโดย ivar จะไม่ปลอดภัย! จะเกิดอะไรขึ้นถ้าวัตถุอื่นเป็น subclass และแทนที่ getter แล้ว) นอกจากนี้โปรดทราบว่าการเข้าถึงคุณสมบัติของคุณเองเนื่องจาก ivars ไม่ทำลายการห่อหุ้ม (ivars ยังไม่เปิดเผยต่อสาธารณะ)

การตั้งค่าการทดสอบนั้นง่ายมาก: สร้างบัญชีสุ่ม 1 Mio เพิ่มลงในอาร์เรย์และจัดเรียงอาร์เรย์นั้น แค่นั้นแหละ. แน่นอนว่ามีสองอาร์เรย์หนึ่งสำหรับAccountAวัตถุและหนึ่งสำหรับAccountBวัตถุและทั้งสองอาร์เรย์จะเต็มไปด้วยบัญชีที่เหมือนกัน (แหล่งข้อมูลเดียวกัน) เราใช้เวลานานแค่ไหนในการจัดเรียงอาร์เรย์

นี่คือผลลัพธ์ของการวิ่งหลายครั้งที่ฉันทำเมื่อวานนี้:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

อย่างที่คุณเห็นการเรียงลำดับอาเรย์ของAccountBวัตถุนั้นสำคัญกว่าการเรียงอาเรย์ของอAccountAอบเจ็กต์เสมอ

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

นอกจากนี้ในกรณีนี้โค้ดตัวอย่างเป็นงานจริงเพียงอย่างเดียวที่ดำเนินการที่นี่ แต่รหัสของคุณเป็นเพียงนาฬิกาเล็ก ๆ ที่ซับซ้อนเพียงใด และถ้าทุกเกียร์ช้าลงในกระบวนการทั้งหมดเช่นนี้มันจะมีความหมายอย่างไรกับความเร็วของนาฬิกาทั้งหมดในที่สุด? โดยเฉพาะอย่างยิ่งหากขั้นตอนการทำงานหนึ่งอย่างขึ้นอยู่กับผลลัพธ์ของอีกขั้นตอนหนึ่งซึ่งหมายถึงความไร้ประสิทธิภาพทั้งหมดจะสรุป ความไร้ประสิทธิภาพส่วนใหญ่ไม่ใช่ปัญหาของตัวเอง แต่เป็นผลรวมที่แท้จริงของพวกเขาที่กลายเป็นปัญหาสำหรับกระบวนการทั้งหมด และปัญหาดังกล่าวก็คือไม่มีสิ่งใดที่ผู้สร้างโปรไฟล์จะแสดงได้อย่างง่ายดายเพราะผู้สร้างโปรไฟล์เกี่ยวกับการค้นหาฮอตสปอตที่สำคัญ แต่ไม่มีความไร้ประสิทธิภาพเหล่านี้เป็นจุดร้อนด้วยตัวเอง เวลาซีพียูนั้นแพร่กระจายโดยเฉลี่ยในหมู่พวกเขา แต่แต่ละคนมีเพียงเศษเสี้ยวเล็กน้อยของมันดูเหมือนว่าเป็นการเสียเวลาโดยรวมในการปรับให้เหมาะสม และมันเป็นเรื่องจริง

และแม้ว่าคุณจะไม่คิดในแง่ของเวลา CPU เพราะคุณเชื่อว่าการเสียเวลาของ CPU นั้นเป็นที่ยอมรับโดยสิ้นเชิงหลังจากทั้งหมด "ได้ฟรี" แล้วค่าใช้จ่ายในการโฮสต์เซิร์ฟเวอร์ที่เกิดจากการใช้พลังงานเป็นเท่าใด สิ่งที่เกี่ยวกับแบตเตอรีของอุปกรณ์มือถือ? หากคุณจะเขียนแอพมือถือเดียวกันสองครั้ง (เช่นเว็บเบราว์เซอร์มือถือของตัวเอง) เมื่อรุ่นที่ทุกคลาสเข้าถึงคุณสมบัติของตัวเองเท่านั้นโดย getters และครั้งที่ทุกคลาสเข้าถึงพวกเขาโดยใช้ ivars เท่านั้นการใช้อันแรกอย่างต่อเนื่อง แบตเตอรี่เร็วกว่าการใช้แบตเตอรี่ก้อนที่สองถึงแม้ว่ามันจะเทียบเท่ากับการใช้งานและสำหรับผู้ใช้แบตเตอรี่ก้อนที่สองก็อาจจะรู้สึกว่าบิตเร็วขึ้น

ตอนนี้นี่คือรหัสสำหรับmain.mไฟล์ของคุณ(รหัสขึ้นอยู่กับการเปิดใช้งาน ARC และให้แน่ใจว่าใช้การเพิ่มประสิทธิภาพเมื่อรวบรวมเพื่อดูผลเต็มรูปแบบ):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end

3
คำอธิบายที่เป็นประโยชน์อย่างมากและไม่เป็นไปตามโลก
โหวตขึ้น

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

1
@ViktorLexington ในรหัสของฉันฉันตั้งค่าunsigned intที่ไม่เคยถูกเก็บไว้ / เผยแพร่ไม่ว่าคุณจะใช้ ARC หรือไม่ การเก็บรักษา / การปล่อยเองนั้นมีราคาแพงดังนั้นความแตกต่างจะน้อยลงเมื่อการจัดการการเก็บรักษาเพิ่มค่าใช้จ่ายคงที่ที่มีอยู่เสมอโดยใช้ setter / getter หรือ ivar โดยตรง แต่คุณจะยังคงบันทึกค่าใช้จ่ายของการโทรวิธีพิเศษหนึ่งรายการหากคุณเข้าถึง ivar โดยตรง ไม่ใช่เรื่องใหญ่ในกรณีส่วนใหญ่เว้นแต่คุณจะทำอย่างนั้นหลายพันครั้งต่อวินาที Apple แจ้งว่าใช้ getters / setters โดยค่าเริ่มต้นยกเว้นว่าคุณอยู่ในวิธีการเริ่มต้น / dealloc หรือมีจุดคอขวด
Mecki

1
@Fogmeister เพิ่มตัวอย่างโค้ดที่แสดงให้เห็นว่าสิ่งนี้สามารถสร้างความแตกต่างอย่างมากในตัวอย่างของโลกแห่งความจริงที่เรียบง่าย และตัวอย่างนี้ไม่มีอะไรเกี่ยวข้องกับคอมพิวเตอร์สุดยอดที่มีการคำนวณนับล้านล้านครั้งมันเป็นเรื่องของการจัดเรียงตารางข้อมูลที่ง่ายมาก ๆ (เป็นกรณีที่พบได้บ่อยในแอพนับล้าน)
Mecki

2
@malhal คุณสมบัติที่ทำเครื่องหมายว่าcopyจะไม่ทำสำเนาของค่าทุกครั้งที่คุณเข้าถึง ทะเยอทะยานของcopyทรัพย์สินเป็นเหมือนทะเยอทะยานของทรัพย์สินstrong/ รหัสมันเป็นพื้นretain return [[self->value retain] autorelease];เฉพาะผู้ตั้งค่าคัดลอกมูลค่าและมันจะมีลักษณะเช่นนี้[self->value autorelease]; self->value = [newValue copy];ในขณะที่strong/ ผู้retainตั้งค่ามีลักษณะเช่นนี้:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

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

การได้รับ "ประสิทธิภาพขั้นต่ำ" สามารถสรุปได้อย่างรวดเร็วและกลายเป็นปัญหา ฉันรู้จากประสบการณ์ ฉันทำงานในแอพที่ใช้ iDevices ถึงขีด จำกัด ของพวกเขาและเราต้องหลีกเลี่ยงการโทรด้วยวิธีที่ไม่จำเป็น เพื่อช่วยให้บรรลุเป้าหมายนี้เรายังหลีกเลี่ยงจุดซินแท็กซ์เนื่องจากทำให้ยากที่จะเห็นจำนวนการเรียกใช้เมธอดตั้งแต่แรกเห็น: ตัวอย่างเช่นการเรียกเมธอดการเรียกใช้เมธอดจำนวนself.image.size.widthเท่าใด [[self image] size].widthในทางตรงกันข้ามคุณสามารถบอกได้ทันทีด้วย

นอกจากนี้ด้วยการตั้งชื่อ ivar ที่ถูกต้อง KVO ยังเป็นไปได้โดยไม่มีคุณสมบัติ (IIRC ฉันไม่ใช่ผู้เชี่ยวชาญของ KVO)


3
+1 การตอบสนองที่ดีเกี่ยวกับ "ประสิทธิภาพขั้นต่ำ" ได้รับการเพิ่มขึ้นและต้องการเห็นวิธีการโทรทั้งหมดอย่างชัดเจน การใช้จุดไวยากรณ์กับคุณสมบัติจะปิดบังการทำงานจำนวนมากที่เกิดขึ้นใน getters / setters ที่กำหนดเอง (โดยเฉพาะถ้า getter นั้นส่งคืนสำเนาของบางสิ่งทุกครั้งที่ถูกเรียก)
Sam

1
KVO ไม่ทำงานสำหรับฉันโดยไม่ใช้ setter การเปลี่ยน ivar โดยตรงไม่ได้เรียกผู้สังเกตการณ์ว่าค่ามีการเปลี่ยนแปลง!
ไบนารี่

2
KVC สามารถเข้าถึง ivars ได้ KVO ไม่สามารถตรวจพบการเปลี่ยนแปลงกับ ivars (และแทนที่จะอาศัยการเข้าถึงที่จะเรียก)
Nikolai Ruhe

9

อรรถศาสตร์

  • สิ่งที่@propertyสามารถแสดงว่า ivars ไม่สามารถ: และnonatomiccopy
  • สิ่งใดที่ไอวอรี่สามารถบอกได้ว่า@propertyไม่สามารถ:
    • @protected: สาธารณะในคลาสย่อยส่วนตัวนอก
    • @packageสาธารณะในกรอบบน 64 บิตส่วนตัวนอก เหมือนกับ@public32 บิต ดูแอปเปิ้ล64 บิตระดับและอินสแตนซ์ตัวแปรควบคุมการเข้าถึง
    • รอบคัดเลือก ตัวอย่างเช่นอาร์เรย์ของการอ้างอิงวัตถุที่คาดเดายาก: id __strong *_objs.

ประสิทธิภาพ

เรื่องสั้น: ivars เร็วกว่า แต่ก็ไม่สำคัญสำหรับการใช้งานส่วนใหญ่ nonatomicคุณสมบัติไม่ใช้การล็อก แต่ ivar โดยตรงนั้นเร็วกว่าเพราะข้ามการเรียก accessors สำหรับรายละเอียดโปรดอ่านอีเมลต่อไปนี้จาก lists.apple.com

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

คุณสมบัติส่งผลต่อประสิทธิภาพในหลายวิธี:

  1. ดังที่ได้กล่าวไปแล้วการส่งข้อความเพื่อทำโหลด / สโตร์นั้นช้ากว่าการทำโหลด / สโตร์แบบอินไลน์อินไลน์การจัดเก็บ

  2. การส่งข้อความเพื่อทำการโหลด / จัดเก็บก็เป็นรหัสอีกเล็กน้อยที่ต้องเก็บไว้ใน i-cache: แม้ว่า getter / setter จะเพิ่มคำแนะนำพิเศษเป็นศูนย์มากกว่าแค่โหลด / เก็บ แต่ก็มีครึ่งหนึ่งที่มั่นคง - โหลคำแนะนำพิเศษในการโทรเพื่อตั้งค่าการส่งข้อความและจัดการกับผล

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

  4. การส่งข้อความกองกำลังค่าทั้งหมดในการทำงานจะได้รับการหกรั่วไหลไปยังสแต็ค (หรือเก็บไว้ใน callee-Save ลงทะเบียนซึ่งก็หมายถึงหกในเวลาที่แตกต่างกัน)

  5. การส่งข้อความอาจมีผลข้างเคียงโดยพลการดังนั้น

    • บังคับให้คอมไพเลอร์รีเซ็ตสมมติฐานทั้งหมดเกี่ยวกับหน่วยความจำนอกพื้นที่
    • ไม่สามารถยก, จม, สั่งใหม่, รวมตัวกันหรือกำจัดได้

  6. ใน ARC ผลลัพธ์ของการส่งข้อความจะได้รับการเก็บรักษาไว้เสมอไม่ว่าโดย callee หรือ caller ถึง +0 จะส่งกลับ: แม้ว่าวิธีการจะไม่เก็บ / autorelease ผลของมันผู้โทรจะไม่ทราบและมี พยายามดำเนินการเพื่อป้องกันไม่ให้ผลลัพธ์ถูกลบอัตโนมัติ สิ่งนี้ไม่สามารถถูกกำจัดได้เนื่องจากการส่งข้อความนั้นไม่สามารถวิเคราะห์ได้แบบคงที่

  7. ใน ARC เนื่องจากเมธอด setter มักใช้อาร์กิวเมนต์ที่ +0 จึงไม่มีวิธีที่จะ "ถ่ายโอน" การเก็บรักษาวัตถุนั้น (ซึ่งตามที่กล่าวไว้ข้างต้น ARC มักจะมี) ลงใน ivar ดังนั้นค่าโดยทั่วไปจะต้องได้รับ รักษา / ปล่อยออกมาเป็นครั้งที่สอง

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


จอห์น.


6

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

การห่อหุ้มข้อมูล / การซ่อนข้อมูลนี่คือสิ่งที่ดี (TM) จากมุมมองการออกแบบอินเตอร์เฟสที่แคบและการเชื่อมโยงที่น้อยที่สุดคือสิ่งที่ทำให้ซอฟต์แวร์สามารถดูแลรักษาและเข้าใจได้ Obj-C ค่อนข้างยากที่จะซ่อนอะไร แต่ตัวแปรอินสแตนซ์ที่ประกาศไว้ในการนำไปใช้นั้นใกล้เคียงกับที่คุณจะได้รับ

ประสิทธิภาพในขณะที่ "การปรับให้เหมาะสมก่อนกำหนด" เป็นสิ่งที่ไม่ดี (TM) การเขียนโค้ดที่มีประสิทธิภาพไม่ดีเพียงเพราะอย่างน้อยคุณก็ทำได้ไม่ดี มันยากที่จะเถียงกับการเรียกเมธอดที่มีราคาแพงกว่าโหลดหรือที่จัดเก็บและในโค้ดที่ใช้คำนวณได้ค่าใช้จ่ายจะเพิ่มขึ้นในไม่ช้า

ในภาษาแบบคงที่ที่มีคุณสมบัติเช่น C # การเรียกไปที่ setters / getters สามารถปรับให้เหมาะสมโดยคอมไพเลอร์ อย่างไรก็ตาม Obj-C เป็นแบบไดนามิกและการลบการโทรออกนั้นทำได้ยากกว่ามาก

Abstractionการโต้แย้งกับตัวแปรอินสแตนซ์ใน Obj-C เป็นการจัดการหน่วยความจำแบบดั้งเดิม ด้วยตัวแปรอินสแตนซ์ของ MRC จำเป็นต้องมีการเรียกเพื่อรักษา / ปล่อย / การเลิกอัตโนมัติที่จะแพร่กระจายไปทั่วรหัสคุณสมบัติ (สังเคราะห์หรือไม่) เก็บรหัส MRC ไว้ในที่เดียวนั่นคือหลักการของสิ่งที่เป็นนามธรรม (Good Thing (TM)) อย่างไรก็ตามด้วย GC หรือ ARC อาร์กิวเมนต์นี้จะหายไปดังนั้นสิ่งที่เป็นนามธรรมสำหรับการจัดการหน่วยความจำจึงไม่เป็นข้อโต้แย้งกับตัวแปรอินสแตนซ์อีกต่อไป


5

คุณสมบัติเปิดเผยตัวแปรของคุณไปยังคลาสอื่น หากคุณต้องการตัวแปรที่สัมพันธ์กับคลาสที่คุณกำลังสร้างให้ใช้ตัวแปรอินสแตนซ์ นี่คือตัวอย่างเล็ก ๆ : คลาส XML สำหรับการแยกวิเคราะห์ RSS และวงจรที่คล้ายกันผ่านวิธีการมอบหมายมากมายและอื่น ๆ เป็นจริงที่มีอินสแตนซ์ของ NSMutableString เพื่อจัดเก็บผลลัพธ์ของการแยกวิเคราะห์แต่ละอันที่แตกต่างกัน ไม่มีเหตุผลที่คลาสภายนอกจะต้องเข้าถึงหรือจัดการสตริงนั้น ดังนั้นคุณเพียงแค่ประกาศในส่วนหัวหรือแบบส่วนตัวและเข้าถึงได้ตลอดทั้งชั้นเรียน การตั้งค่าคุณสมบัติสำหรับมันอาจเป็นประโยชน์เพื่อให้แน่ใจว่าไม่มีปัญหาหน่วยความจำโดยใช้ self.mutableString เพื่อเรียกใช้ getter / setters


5

ความเข้ากันได้ย้อนหลังเป็นปัจจัยสำหรับฉัน ฉันไม่สามารถใช้คุณสมบัติ Objective-C 2.0 ได้เพราะฉันกำลังพัฒนาซอฟต์แวร์และไดรเวอร์เครื่องพิมพ์ที่ต้องทำงานกับ Mac OS X 10.3 เป็นส่วนหนึ่งของข้อกำหนด ฉันรู้ว่าคำถามของคุณดูเหมือนจะมีเป้าหมายอยู่ที่ iOS แต่ฉันคิดว่าฉันยังคงแบ่งปันเหตุผลของฉันที่ไม่ได้ใช้คุณสมบัติ

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