Cách tốt nhất để định nghĩa các phương thức riêng cho một lớp trong Objective-C


355

Tôi mới bắt đầu lập trình Objective-C và, có nền tảng về Java, tự hỏi làm thế nào mọi người viết chương trình Objective-C đối phó với các phương thức riêng tư.

Tôi hiểu có thể có một số quy ước và thói quen và suy nghĩ về câu hỏi này như là một công cụ tổng hợp các kỹ thuật tốt nhất mà mọi người sử dụng để xử lý các phương pháp riêng tư trong Objective-C.

Vui lòng bao gồm một đối số cho cách tiếp cận của bạn khi đăng nó. Tại sao nó lại tốt? Những nhược điểm nào nó có (mà bạn biết) và cách bạn đối phó với chúng?


Đối với những phát hiện của tôi cho đến nay.

Có thể sử dụng các danh mục [ví dụ MyClass (Riêng tư)] được xác định trong tệp MyClass.m để nhóm các phương thức riêng tư.

Cách tiếp cận này có 2 vấn đề:

  1. Xcode (và trình biên dịch?) Không kiểm tra xem bạn có xác định tất cả các phương thức trong danh mục riêng tư trong khối @im THỰCation tương ứng không
  2. Bạn phải đặt @interface khai báo danh mục riêng tư của bạn vào đầu tệp MyClass.m, nếu không, Xcode sẽ phàn nàn với một thông báo như "tự có thể không trả lời tin nhắn" privateFoo ".

Vấn đề đầu tiên có thể được giải quyết với danh mục trống [ví dụ MyClass ()].
Cái thứ hai làm phiền tôi rất nhiều. Tôi muốn xem các phương thức riêng được triển khai (và được xác định) ở gần cuối tệp; Tôi không biết nếu điều đó có thể.


1
Mọi người có thể thấy câu hỏi này thú vị: stackoverflow.com/questions/2158660/ từ
bbum

Câu trả lời:


435

Như những người khác đã nói, không có gì giống như một phương thức riêng tư trong Objective-C. Tuy nhiên, bắt đầu từ Objective-C 2.0 (có nghĩa là Mac OS X Leopard, iPhone OS 2.0 và sau này), bạn có thể tạo một danh mục với một tên trống (nghĩa là @interface MyClass ()) được gọi là Mở rộng lớp . Điều độc đáo về một phần mở rộng lớp là các triển khai phương thức phải đi giống @implementation MyClassnhư các phương thức chung. Vì vậy, tôi cấu trúc các lớp học của mình như thế này:

Trong tệp .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Và trong tệp .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

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

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

@end

Tôi nghĩ rằng ưu điểm lớn nhất của phương pháp này là nó cho phép bạn nhóm các triển khai phương thức của mình theo chức năng, chứ không phải theo sự phân biệt công khai / riêng tư (đôi khi tùy tiện).


8
và nó sẽ tạo ra "MYClass có thể không phản hồi với '-myPrivateMethod-", không phải là ngoại lệ / lỗi.
Özgür

2
Điều này thực sự bắt đầu xuất hiện trong mã soạn sẵn của Apple. ++
Chris Trahey

75
với trình biên dịch LLVM 4 trở đi, bạn thậm chí không cần phải làm điều này. bạn chỉ có thể định nghĩa chúng trong quá trình thực hiện mà không cần đặt chúng trong phần mở rộng lớp.
Abizern

1
Nếu bạn nhận được các cảnh báo @Comptrol đề cập, thì đó là vì bạn đã xác định một phương thức bên dưới thay vì phương thức khác gọi nó (xem câu trả lời của Andy) - và bạn bỏ qua những cảnh báo này trong tình trạng nguy hiểm. Tôi đã mắc lỗi này và trình biên dịch đã nhầm lẫn cho đến khi tôi lồng một cuộc gọi như thế này: if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Sau đó, fWidthCombined luôn diễn ra như 0.
Wienke

6
@Wienke Không còn phải lo lắng về đơn hàng. Các phiên bản gần đây của LLVM sẽ tìm thấy phương thức ngay cả khi nó xuất hiện bên dưới nơi nó được gọi.
Steve Waddicor

52

Thực sự không có một "phương thức riêng tư" nào trong Objective-C, nếu bộ thực thi có thể tìm ra cách thực hiện nào để sử dụng thì nó sẽ thực hiện. Nhưng điều đó không có nghĩa là không có các phương thức không phải là một phần của giao diện tài liệu. Đối với những phương pháp tôi nghĩ rằng một thể loại là tốt. Thay vì đặt @interfaceở đầu tệp .m như điểm 2 của bạn, tôi sẽ đặt nó vào tệp .h của chính nó. Một quy ước mà tôi tuân theo (và đã thấy ở nơi khác, tôi nghĩ đó là quy ước của Apple vì Xcode hiện hỗ trợ tự động cho nó) là đặt tên một tệp như vậy theo tên và loại của nó với dấu + phân tách chúng, vì vậy @interface GLObject (PrivateMethods)có thể được tìm thấy GLObject+PrivateMethods.h. Lý do cung cấp tệp tiêu đề là để bạn có thể nhập tệp đó trong các lớp kiểm tra đơn vị của mình :-).

Nhân tiện, khi có liên quan đến việc thực hiện / xác định các phương thức gần cuối tệp .m, bạn có thể làm điều đó với một danh mục bằng cách triển khai danh mục ở cuối tệp .m:

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

hoặc với phần mở rộng lớp (thứ bạn gọi là "danh mục trống"), chỉ cần xác định các phương thức đó sau cùng. Các phương thức Objective-C có thể được định nghĩa và sử dụng theo bất kỳ thứ tự nào trong quá trình triển khai, vì vậy không có gì ngăn bạn đặt các phương thức "riêng tư" ở cuối tệp.

Ngay cả với các tiện ích mở rộng lớp, tôi sẽ thường tạo một tiêu đề riêng ( GLObject+Extension.h) để tôi có thể sử dụng các phương thức đó nếu được yêu cầu, bắt chước khả năng hiển thị của "bạn bè" hoặc "được bảo vệ".

Vì câu trả lời này ban đầu được viết, trình biên dịch clang đã bắt đầu thực hiện hai lần cho các phương thức Objective-C. Điều này có nghĩa là bạn có thể tránh khai báo hoàn toàn các phương thức "riêng tư" của mình và cho dù chúng ở trên hay bên dưới trang web gọi chúng sẽ được trình biên dịch tìm thấy.


37

Mặc dù tôi không phải là chuyên gia về Objective-C, nhưng cá nhân tôi chỉ định nghĩa phương thức trong việc thực hiện lớp của mình. Cấp, nó phải được xác định trước (ở trên) bất kỳ phương thức nào gọi nó, nhưng chắc chắn phải mất ít công việc nhất để làm.


4
Giải pháp này có ưu điểm là nó tránh thêm cấu trúc chương trình không cần thiết chỉ để tránh cảnh báo trình biên dịch.
Jan Hettich

1
Tôi có xu hướng làm điều này là tốt, nhưng cũng không phải là một chuyên gia Objective-C. Đối với các chuyên gia, có lý do nào để không làm theo cách này (ngoài vấn đề đặt hàng phương pháp)?
Eric Smith

2
Thứ tự phương thức dường như là một vấn đề nhỏ, nhưng nếu bạn dịch nó thành khả năng đọc mã thì nó có thể trở thành một vấn đề khá quan trọng, đặc biệt là khi làm việc trong một nhóm.
boronomiakur

17
Phương pháp đặt hàng không còn đáng kể. Các phiên bản gần đây của LLVM không quan tâm thứ tự các phương thức được triển khai. Vì vậy, bạn có thể phù hợp với mình về việc đặt hàng, mà không cần phải khai báo trước.
Steve Waddicor

Xem thêm phản hồi này từ @justin
lagweezle

19

Xác định các phương thức riêng tư của bạn trong @implementationkhối là lý tưởng cho hầu hết các mục đích. Clang sẽ thấy những điều này trong @implementation, bất kể thứ tự khai báo. Không cần phải khai báo chúng trong phần tiếp theo lớp (hay còn gọi là mở rộng lớp) hoặc thể loại được đặt tên.

Trong một số trường hợp, bạn sẽ cần khai báo phương thức trong phần tiếp tục lớp (ví dụ: nếu sử dụng bộ chọn giữa phần tiếp tục lớp và phần @implementation).

static các chức năng rất tốt cho các phương pháp riêng tư đặc biệt nhạy cảm hoặc tốc độ.

Một quy ước để đặt tên tiền tố có thể giúp bạn tránh vô tình ghi đè các phương thức riêng tư (tôi thấy tên lớp là tiền tố an toàn).

Các danh mục được đặt tên (ví dụ @interface MONObject (PrivateStuff)) không phải là một ý tưởng đặc biệt tốt vì các xung đột đặt tên tiềm năng khi tải. Chúng thực sự chỉ hữu ích cho bạn bè hoặc các phương pháp được bảo vệ (rất hiếm khi là một lựa chọn tốt). Để đảm bảo bạn được cảnh báo về việc triển khai danh mục không đầy đủ, bạn thực sự nên thực hiện nó:

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

Dưới đây là một bảng cheat chú thích nhỏ:

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

Một cách tiếp cận khác có thể không rõ ràng: một loại C ++ có thể rất nhanh và cung cấp mức độ kiểm soát cao hơn nhiều, trong khi giảm thiểu số lượng phương thức objc được xuất và tải.


1
+1 để sử dụng tên lớp đầy đủ làm tiền tố tên phương thức! Nó an toàn hơn nhiều so với chỉ một gạch dưới hoặc thậm chí TLA của riêng bạn. (Nếu các phương pháp tư nhân là trong một thư viện mà bạn sử dụng trong một dự án của bạn và bạn quên rằng bạn đã sử dụng tên, đôi khi một hoặc hai năm trước đây ...?)
big_m

14

Bạn có thể thử xác định một hàm tĩnh bên dưới hoặc bên trên triển khai của bạn để đưa một con trỏ đến thể hiện của bạn. Nó sẽ có thể truy cập bất kỳ biến thể hiện của bạn.

//.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 bảo lưu tên với một gạch dưới hàng đầu cho sử dụng riêng của mình.
Georg Schölly

1
Và nếu bạn không sử dụng các khuôn khổ của Apple thì sao? Tôi thường phát triển mã Objective-C mà không có khung của Apple, thực tế tôi xây dựng trên Linux, Windows và Mac OS X. Tôi đã loại bỏ nó bằng cách xem xét hầu hết những người viết mã trong Objective-C có thể sử dụng nó trên Mac OS X.
dreamlax

3
Tôi nghĩ rằng đây là phương pháp thực sự riêng tư trong tập tin .m. Các phương thức thể loại lớp khác thực sự không riêng tư vì bạn không thể đặt riêng tư cho các phương thức trong khối @interface ... @ end.
David.Chu.ca

Tại sao bạn lại làm vậy? nếu bạn chỉ cần thêm "-" vào đầu định nghĩa phương thức, bạn sẽ truy cập "tự" mà không chuyển qua làm tham số.
Guy

1
@Guy: bởi vì sau đó phương thức có thể được phát hiện bằng phản xạ, và do đó không riêng tư chút nào.
dreamlax

3

Bạn có thể sử dụng các khối?

@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

Tôi biết đây là một câu hỏi cũ, nhưng đây là một trong những câu hỏi đầu tiên tôi tìm thấy khi tôi đang tìm câu trả lời cho chính câu hỏi này. Tôi chưa thấy giải pháp này được thảo luận ở bất cứ nơi nào khác, vì vậy hãy cho tôi biết nếu có điều gì dại dột khi làm điều này.


1
Những gì bạn đã làm ở đây là tạo một biến loại khối toàn cầu, thực sự không tốt hơn một hàm (và thậm chí không thực sự riêng tư, vì nó không được khai báo static). Nhưng tôi đã thử nghiệm việc gán các khối cho các ivar riêng (từ phương thức init) - kiểu JavaScript - cũng cho phép truy cập vào các ivar riêng, một điều không thể từ các hàm tĩnh. Chưa chắc chắn tôi thích cái nào hơn.
big_m

3

mọi đối tượng trong Objective C đều tuân thủ giao thức NSObject, giữ phương thức biểu diễn : Trình quản lý . Trước đây tôi cũng đang tìm cách tạo ra một số phương pháp "trợ giúp hoặc riêng tư" mà tôi không cần phải tiếp xúc ở cấp độ công cộng. Nếu bạn muốn tạo một phương thức riêng tư không có chi phí hoạt động và không phải xác định nó trong tệp tiêu đề của bạn thì hãy thử cách này ...

xác định phương thức của bạn với một chữ ký tương tự như mã bên dưới ...

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

sau đó khi bạn cần tham chiếu phương thức, chỉ cần gọi nó là bộ chọn ...

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

dòng mã này sẽ gọi phương thức bạn đã tạo và không có cảnh báo khó chịu về việc không xác định nó trong tệp tiêu đề.


6
Theo cách này, bạn không có cách nào để vượt qua tham số thứ ba.
Li Fumin

2

Nếu bạn muốn tránh @interfacekhối ở trên cùng, bạn luôn có thể đặt các khai báo riêng tư trong một tệp khác MyClassPrivate.hkhông lý tưởng nhưng nó không làm lộn xộn việc thực hiện.

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

Một điều nữa mà tôi chưa thấy được đề cập ở đây - Xcode hỗ trợ các tệp .h có tên "_private". Giả sử bạn có một lớp MyClass - bạn có MyClass.m và MyClass.h và bây giờ bạn cũng có thể có MyClass_private.h. Xcode sẽ nhận ra điều này và đưa nó vào danh sách "Đối tác" trong Trình chỉnh sửa trợ lý.

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

1

Không có cách nào để giải quyết vấn đề # 2. Đó chỉ là cách trình biên dịch C (và do đó trình biên dịch Objective-C) hoạt động. Nếu bạn sử dụng trình soạn thảo XCode, cửa sổ bật lên chức năng sẽ giúp bạn dễ dàng điều hướng @interface@implementationcác khối trong tệp.


1

Có một lợi ích của sự vắng mặt phương pháp tư nhân. Bạn có thể di chuyển logic mà bạn dự định ẩn sang lớp riêng và sử dụng nó làm đại biểu. Trong trường hợp này, bạn có thể đánh dấu đối tượng ủy nhiệm là riêng tư và nó sẽ không hiển thị từ bên ngoài. Di chuyển logic đến lớp riêng biệt (có thể một số) giúp thiết kế dự án của bạn tốt hơn. Nguyên nhân là các lớp của bạn trở nên đơn giản hơn và các phương thức của bạn được nhóm trong các lớp với tên thích hợp.


0

Như những người khác nói việc xác định các phương thức riêng tư trong @implementationkhối là ổn đối với hầu hết các mục đích.

Về chủ đề tổ chức mã - Tôi muốn giữ chúng cùng nhau pragma mark privateđể điều hướng dễ dàng hơn trong Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.