วิธีที่ดีที่สุดในการกำหนดวิธีการส่วนตัวสำหรับคลาสใน Objective-C


355

ฉันเพิ่งเริ่มเขียนโปรแกรม Objective-C และมีพื้นหลังใน Java สงสัยว่าคนเขียนโปรแกรม Objective-C จัดการกับวิธีส่วนตัว

ฉันเข้าใจว่าอาจมีอนุสัญญาและนิสัยหลายอย่างและคิดถึงคำถามนี้ในฐานะผู้รวบรวมเทคนิคที่ดีที่สุดที่ผู้คนใช้ในการจัดการกับวิธีการส่วนตัวใน Objective-C

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


สำหรับการค้นพบของฉันจนถึง

เป็นไปได้ที่จะใช้หมวดหมู่ [เช่น MyClass (ส่วนตัว)] ที่กำหนดไว้ในไฟล์ MyClass.m เพื่อจัดกลุ่มวิธีส่วนตัว

วิธีนี้มี 2 ประเด็น:

  1. Xcode (และคอมไพเลอร์?) ไม่ได้ตรวจสอบว่าคุณกำหนดวิธีการทั้งหมดในหมวดหมู่ส่วนตัวในบล็อก @implementation ที่สอดคล้องกัน
  2. คุณต้องใส่ @interface ประกาศหมวดหมู่ส่วนตัวของคุณในจุดเริ่มต้นของไฟล์ MyClass.m มิฉะนั้น Xcode บ่นกับข้อความเช่น "ตัวเองอาจไม่ตอบสนองต่อข้อความ" privateFoo "

ปัญหาแรกสามารถแก้ไขได้ด้วยหมวดหมู่ที่ว่าง [เช่น MyClass ()]
คนที่สองรบกวนฉันมาก ฉันต้องการที่จะเห็นวิธีการส่วนตัวนำไปใช้ (และกำหนด) ใกล้ถึงจุดสิ้นสุดของไฟล์; ฉันไม่ทราบว่าเป็นไปได้


1
ผู้คนอาจพบว่าคำถามนี้น่าสนใจ: stackoverflow.com/questions/2158660/…
bbum

คำตอบ:


436

ไม่มีอย่างที่คนอื่นพูดไปแล้วสิ่งนี้เป็นวิธีส่วนตัวใน Objective-C แต่เริ่มต้นใน Objective-C 2.0 (หมายถึงระบบปฏิบัติการ Mac OS X Leopard, iPhone OS 2.0 และต่อมา) คุณสามารถสร้างหมวดหมู่ที่มีชื่อที่ว่างเปล่า (คือ@interface MyClass ()) เรียกว่าการขยายชั้นเรียน มีอะไรพิเศษเกี่ยวกับการขยายชั้นเรียนคือการใช้งานวิธีการต้องเหมือนกัน@implementation MyClassกับวิธีการสาธารณะ ดังนั้นฉันจึงจัดโครงสร้างคลาสของฉันดังนี้:

ในไฟล์. h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

และในไฟล์. m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

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


8
และจะสร้าง "MYClass อาจไม่ตอบสนองต่อ '-myPrivateMethod-" ไม่ใช่ข้อยกเว้น / ข้อผิดพลาด
Özgür

2
นี่เป็นจริงเริ่มแสดงในรหัสสำเร็จรูปของ Apple ++
Chris Trahey

75
ด้วยคอมไพเลอร์ LLVM 4 เป็นต้นไปคุณไม่จำเป็นต้องทำสิ่งนี้ คุณสามารถกำหนดได้ในการนำไปใช้โดยไม่จำเป็นต้องใส่ไว้ในส่วนขยายของชั้นเรียน
Abizern

1
หากคุณได้รับคำเตือน @Comptrol กล่าวถึงนั่นเป็นเพราะคุณได้กำหนดวิธีการด้านล่างมากกว่าวิธีอื่นที่เรียกมันว่า (ดูคำตอบของ Andy) - และคุณไม่สนใจคำเตือนเหล่านี้ที่อันตรายของคุณ ฉันทำผิดพลาดและคอมไพเลอร์ยุ่งเหยิงจนไม่เป็นไรจนกระทั่งฉันซ้อนโทรแบบนี้: if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...จากนั้น fWidthCombined มักจะผ่านมาเป็น 0
Wienke

6
@Wienke ไม่จำเป็นต้องกังวลเกี่ยวกับการสั่งซื้ออีกต่อไป LLVM เวอร์ชันล่าสุดจะค้นหาวิธีแม้ว่าจะปรากฏด้านล่างที่เรียกว่า
Steve Waddicor

52

ไม่มี "วิธีส่วนตัว" ใน Objective-C จริงๆหากรันไทม์สามารถทำงานได้ว่าการใช้งานแบบใดจะใช้งานได้ แต่นั่นไม่ใช่การบอกว่าไม่มีวิธีการใดที่ไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซที่ทำเป็นเอกสาร สำหรับวิธีการเหล่านั้นฉันคิดว่าหมวดหมู่นั้นใช้ได้ แทนที่จะใส่@interfaceที่ด้านบนของไฟล์. m เช่นจุดที่ 2 ของคุณฉันจะใส่มันลงในไฟล์. h ของมันเอง การประชุมที่ฉันติดตาม (และเคยเห็นที่อื่นฉันคิดว่ามันเป็นแบบแผนของ Apple เนื่องจาก Xcode ตอนนี้ให้การสนับสนุนอัตโนมัติ) คือการตั้งชื่อไฟล์ดังกล่าวหลังจากคลาสและหมวดหมู่ด้วยเครื่องหมาย + แยกพวกมันดังนั้นจึง@interface GLObject (PrivateMethods)สามารถพบGLObject+PrivateMethods.hได้ เหตุผลในการให้ไฟล์ส่วนหัวคือเพื่อให้คุณสามารถนำเข้าในคลาสทดสอบหน่วยของคุณ :-)

โดยวิธีการที่เกี่ยวข้องกับการใช้ / การกำหนดวิธีการใกล้กับจุดสิ้นสุดของไฟล์. m คุณสามารถทำได้ด้วยหมวดหมู่โดยใช้หมวดหมู่ที่ด้านล่างของไฟล์. m:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

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

แม้จะมีส่วนขยายชั้นเรียนฉันก็มักจะสร้างส่วนหัวแยกต่างหาก ( GLObject+Extension.h) เพื่อให้ฉันสามารถใช้วิธีการเหล่านั้นได้หากจำเป็นต้องเลียนแบบการมองเห็น "เพื่อน" หรือ "ป้องกัน"

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


37

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


4
โซลูชันนี้มีข้อดีที่จะหลีกเลี่ยงการเพิ่มโครงสร้างโปรแกรมที่ไม่จำเป็นเพียงเพื่อหลีกเลี่ยงคำเตือนคอมไพเลอร์
Jan Hettich

1
ฉันมักจะทำสิ่งนี้เช่นกัน แต่ก็ไม่ใช่ผู้เชี่ยวชาญ Objective-C สำหรับผู้เชี่ยวชาญมีเหตุผลใดที่จะไม่ทำเช่นนี้ (นอกเหนือจากปัญหาการสั่งซื้อวิธี)?
Eric Smith

2
การเรียงลำดับวิธีดูเหมือนจะเป็นปัญหาเล็กน้อย แต่ถ้าคุณแปลเป็นความสามารถในการอ่านรหัสมันอาจกลายเป็นปัญหาสำคัญโดยเฉพาะเมื่อทำงานในทีม
borisdiakur

17
การสั่งซื้อวิธีไม่มีความสำคัญอีกต่อไป LLVM เวอร์ชันล่าสุดไม่สนใจว่าจะใช้วิธีใดในการสั่งซื้อ เพื่อให้คุณสามารถเหมาะสมกับการสั่งซื้อโดยไม่จำเป็นต้องประกาศก่อน
Steve Waddicor


19

การกำหนดวิธีการส่วนตัวใน@implementationบล็อกนั้นเหมาะสำหรับวัตถุประสงค์ส่วนใหญ่ เสียงดังกราวจะเห็นสิ่งเหล่านี้ภายใน@implementation, โดยไม่คำนึงถึงคำสั่งประกาศ ไม่จำเป็นต้องประกาศพวกเขาในการต่อเนื่องในชั้นเรียน (aka ส่วนขยายชั้นเรียน) หรือหมวดหมู่ที่มีชื่อ

ในบางกรณีคุณจะต้องประกาศวิธีในการเรียนต่อเนื่องของชั้นเรียน (เช่นหากใช้ตัวเลือกระหว่างการต่อชั้นเรียนกับ@implementation)

static ฟังก์ชั่นดีมากโดยเฉพาะอย่างยิ่งความไวหรือความเร็วของวิธีการส่วนตัวที่สำคัญ

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

หมวดหมู่ที่มีชื่อ (เช่น@interface MONObject (PrivateStuff)) ไม่ใช่แนวคิดที่ดีเป็นพิเศษเนื่องจากอาจมีการชนกันของชื่อเมื่อโหลด พวกเขามีประโยชน์จริง ๆ สำหรับเพื่อนหรือวิธีการป้องกัน (ซึ่งไม่ค่อยเป็นตัวเลือกที่ดี) เพื่อให้แน่ใจว่าคุณได้รับคำเตือนเกี่ยวกับการใช้งานหมวดหมู่ที่ไม่สมบูรณ์คุณควรนำไปใช้จริง:

@implementation MONObject (PrivateStuff)
...HERE...
@end

นี่คือแผ่นโกงที่มีคำอธิบายประกอบเล็กน้อย:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

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


1
+1 สำหรับการใช้ชื่อคลาสแบบเต็มเป็นส่วนนำหน้าชื่อเมธอด! มันปลอดภัยกว่าแค่ขีดเส้นใต้หรือ TLA ของคุณเอง (จะเกิดอะไรขึ้นถ้าเมธอดส่วนตัวอยู่ในไลบรารีที่คุณใช้ในโครงการอื่นและคุณลืมว่าคุณได้ใช้ชื่อไปแล้วเมื่อประมาณหนึ่งหรือสองปีที่แล้ว ... ?)
big_m

14

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

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end

1
Apple สงวนชื่อด้วยเครื่องหมายขีดเส้นใต้สำหรับการใช้งานของตัวเอง
Georg Schölly

1
และถ้าคุณไม่ใช้กรอบของ Apple ล่ะ ฉันมักจะพัฒนารหัส Objective-C โดยไม่มีกรอบของแอปเปิ้ลอันที่จริงฉันสร้างบน Linux, Windows และ Mac OS X ฉันลบมันต่อไปพิจารณาคนส่วนใหญ่ที่รหัสใน Objective-C อาจใช้บน Mac OS X ได้
dreamlax

3
ฉันคิดว่านี่เป็นวิธีการส่วนตัวอย่างแท้จริงในไฟล์. m วิธีการประเภทอื่น ๆ ในชั้นเรียนนั้นไม่ได้เป็นแบบส่วนตัวเพราะคุณไม่สามารถตั้งค่าความเป็นส่วนตัวสำหรับวิธีการใน @interface ... @ end block
David.Chu.ca

ทำไมคุณจะทำเช่นนั้น? หากคุณเพิ่ม "-" ที่จุดเริ่มต้นของการกำหนดวิธีการคุณจะสามารถเข้าถึง "ตัวเอง" โดยไม่ต้องผ่านพารามิเตอร์
Guy

1
@ ผู้ซื้อ: เพราะจากนั้นวิธีการตรวจพบโดยการสะท้อนและดังนั้นจึงไม่เป็นส่วนตัวเลย
dreamlax

3

คุณสามารถใช้บล็อกได้หรือไม่

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

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


1
สิ่งที่คุณทำที่นี่คือการสร้างตัวแปรประเภทบล็อกทั่วโลกซึ่งไม่ได้ดีไปกว่าฟังก์ชั่น (และไม่ได้เป็นส่วนตัวอย่างแท้จริงเพราะมันไม่ได้ประกาศstatic) แต่ฉันกำลังทดลองใช้การกำหนดบล็อกให้กับ ivars ส่วนตัว (จากวิธีการเริ่มต้น) - สไตล์ JavaScript - ซึ่งยังอนุญาตให้เข้าถึง ivars ส่วนตัวสิ่งที่ไม่สามารถทำได้จากฟังก์ชั่นคงที่ ยังไม่แน่ใจว่าฉันต้องการอะไร
big_m

3

วัตถุทุกชิ้นใน Objective C สอดคล้องกับโปรโตคอล NSObject ซึ่งเก็บไว้บนเมธอดperformSelector: ก่อนหน้านี้ฉันยังกำลังมองหาวิธีในการสร้างวิธีการบางอย่าง "ผู้ช่วยเหลือหรือส่วนตัว" ที่ฉันไม่ต้องการเปิดเผยในระดับสาธารณะ หากคุณต้องการสร้างวิธีการแบบส่วนตัวโดยไม่มีค่าใช้จ่ายและไม่จำเป็นต้องกำหนดไว้ในไฟล์ส่วนหัวของคุณให้ยิงแบบนี้ ...

กำหนดวิธีการของคุณด้วยลายเซ็นที่คล้ายกันเป็นรหัสด้านล่าง ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

จากนั้นเมื่อคุณต้องการอ้างอิงวิธีการเพียงเรียกมันว่าเป็นตัวเลือก ...

[self performSelector:@selector(myHelperMethod:)];

บรรทัดของรหัสนี้จะเรียกใช้วิธีการที่คุณสร้างขึ้นและไม่มีคำเตือนที่น่ารำคาญว่าจะไม่มีการกำหนดไว้ในไฟล์ส่วนหัว


6
ด้วยวิธีนี้คุณไม่มีวิธีส่งพารามิเตอร์ตัวที่สาม
Li Fumin

2

หากคุณต้องการหลีกเลี่ยงการ@interfaceบล็อกที่ด้านบนคุณสามารถใส่การประกาศส่วนตัวในไฟล์อื่นที่MyClassPrivate.hไม่เหมาะ แต่มันไม่เกะกะการใช้งาน

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end

2

อีกสิ่งหนึ่งที่ฉันไม่ได้เห็นได้กล่าวถึงที่นี่ - Xcode รองรับไฟล์. h ด้วย "_private" ในชื่อ สมมติว่าคุณมีคลาส MyClass - คุณมี MyClass.m และ MyClass.h และตอนนี้คุณสามารถมี MyClass_private.h Xcode จะจดจำสิ่งนี้และรวมไว้ในรายการ "ส่วน" ในผู้ช่วยแก้ไข

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"

1

ไม่มีวิธีแก้ไขปัญหา # 2 นั่นเป็นเพียงวิธีการที่คอมไพเลอร์ C (และด้วยเหตุนี้คอมไพเลอร์ Objective-C) ทำงาน หากคุณใช้ตัวแก้ไข XCode ป๊อปอัพฟังก์ชั่นควรทำให้ง่ายต่อการสำรวจ@interfaceและ@implementationบล็อกในไฟล์


1

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


0

ตามที่คนอื่น ๆ กล่าวว่าการกำหนดวิธีการส่วนตัวใน@implementationบล็อกนั้นก็โอเคสำหรับวัตถุประสงค์ส่วนใหญ่

ในหัวข้อของการจัดระเบียบรหัส - ฉันต้องการให้พวกเขาอยู่ด้วยกันภายใต้pragma mark privateเพื่อความสะดวกในการนำทางใน Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

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