Tại sao Objective-C không hỗ trợ các phương thức riêng tư?


123

Tôi đã thấy một số chiến lược để khai báo các phương thức bán riêng trong Objective-C , nhưng dường như không có cách nào để tạo ra một phương thức thực sự riêng tư. Tôi chấp nhận điều đó. Nhưng tại sao nó lại như thế? Mọi lời giải thích về cơ bản tôi đều nói, "bạn không thể làm điều đó, nhưng đây là một xấp xỉ gần đúng."

Có một số từ khóa áp dụng cho ivars(thành viên) để kiểm soát phạm vi của họ, ví dụ như @private, @public, @protected. Tại sao điều này không thể được thực hiện cho các phương pháp là tốt? Có vẻ như một cái gì đó thời gian chạy có thể hỗ trợ. Có một triết lý cơ bản mà tôi đang thiếu? Đây có phải là cố ý?


15
Câu hỏi hay, btw.
bbum

5
Theo chỉnh sửa của bạn: có, thực thi thời gian chạy sẽ rất tốn kém vì rất ít lợi ích. Thực thi thời gian biên dịch sẽ là một bổ sung đáng hoan nghênh cho ngôn ngữ và hoàn toàn có thể đạt được. Vâng, nó có thể bị phá vỡ trong thời gian chạy, nhưng điều đó là tốt. Lập trình viên không phải là kẻ thù. Mục tiêu của phạm vi là giúp bảo vệ lập trình viên khỏi lỗi.
Rob Napier

1
Bạn không thể "bỏ qua thời gian chạy" - thời gian chạy là thông điệp gửi đi.
Chuck

"Sẽ không có cách nào để một phương pháp riêng tư làm ngắn mạch kiểm tra này" Ý bạn là "phá vỡ"? ;-)
Constantino Tsarouhas

Câu trả lời:


103

Câu trả lời là ... à ... đơn giản. Đơn giản và nhất quán, trên thực tế.

Objective-C hoàn toàn năng động tại thời điểm gửi phương thức. Cụ thể, mọi công văn phương thức đều đi qua điểm phân giải phương thức động chính xác giống như mọi công văn phương thức khác. Trong thời gian chạy, mọi triển khai phương thức đều có cùng độ phơi sáng và tất cả các API được cung cấp bởi thời gian chạy Objective-C hoạt động với các phương thức và bộ chọn hoạt động như nhau trên tất cả các phương thức.

Như nhiều người đã trả lời (cả ở đây và trong các câu hỏi khác), các phương thức riêng thời gian biên dịch được hỗ trợ; nếu một lớp không khai báo một phương thức trong giao diện có sẵn công khai của nó, thì phương thức đó có thể không tồn tại xa như mã của bạn có liên quan. Nói cách khác, bạn có thể đạt được tất cả các kết hợp khả năng hiển thị khác nhau mong muốn tại thời điểm biên dịch bằng cách tổ chức dự án của bạn một cách thích hợp.

Có rất ít lợi ích để sao chép cùng chức năng vào thời gian chạy. Nó sẽ thêm một số lượng lớn phức tạp và chi phí chung. Và ngay cả với tất cả sự phức tạp đó, nó vẫn không thể ngăn chặn tất cả trừ nhà phát triển bình thường nhất thực hiện các phương thức được cho là "riêng tư" của bạn.

EDIT: Một trong những giả định mà tôi nhận thấy là các tin nhắn riêng tư sẽ phải trải qua thời gian chạy dẫn đến chi phí rất lớn. Điều này hoàn toàn đúng?

Vâng, đúng vậy. Không có lý do nào để giả sử rằng người triển khai một lớp sẽ không muốn sử dụng tất cả các tính năng Objective-C được thiết lập trong triển khai và điều đó có nghĩa là việc gửi động phải xảy ra. Tuy nhiên , không có lý do cụ thể tại sao các phương thức riêng tư không thể được gửi bởi một biến thể đặc biệt objc_msgSend(), vì trình biên dịch sẽ biết rằng chúng là riêng tư; tức là điều này có thể đạt được bằng cách thêm một bảng phương thức chỉ riêng vào Classcấu trúc.

Sẽ không có cách nào để một phương thức riêng tư làm ngắn mạch kiểm tra này hoặc bỏ qua thời gian chạy?

Nó không thể bỏ qua thời gian chạy, nhưng thời gian chạy không nhất thiết phải thực hiện bất kỳ kiểm tra nào cho các phương thức riêng tư.

Điều đó nói rằng, không có lý do gì mà bên thứ ba không thể cố tình kêu gọi objc_msgSendPrivate()một đối tượng, ngoài việc thực hiện đối tượng đó và một số điều (ví dụ KVO) sẽ phải làm điều đó. Trên thực tế, nó sẽ chỉ là một quy ước và tốt hơn một chút trong thực tế so với các bộ chọn tiền tố của các phương thức riêng tư hoặc không đề cập đến chúng trong tiêu đề giao diện.

Tuy nhiên, để làm như vậy, sẽ làm suy yếu bản chất năng động thuần túy của ngôn ngữ. Không còn mọi phương thức gửi đi thông qua một cơ chế công văn giống hệt nhau. Thay vào đó, bạn sẽ bị bỏ lại trong một tình huống mà hầu hết các phương thức hành xử theo một cách và một số ít chỉ khác nhau.

Điều này mở rộng ra ngoài thời gian chạy vì có nhiều cơ chế trong Ca cao được xây dựng dựa trên tính năng động nhất quán của Objective-C. Ví dụ, cả Mã hóa giá trị khóa và Quan sát giá trị khóa sẽ phải được sửa đổi rất nhiều để hỗ trợ các phương thức riêng tư - rất có thể bằng cách tạo lỗ hổng có thể khai thác - hoặc các phương thức riêng tư sẽ không tương thích.


49
+1 Mục tiêu-C là (theo một cách nào đó) là ngôn ngữ "ông lớn", trong đó các lập trình viên dự kiến ​​sẽ thể hiện một số kỷ luật nhất định, thay vì bị trình biên dịch / thời gian chạy trên tay mỗi lần. Tôi thấy nó mới mẻ. Đối với tôi, việc hạn chế khả năng hiển thị của phương thức trong quá trình biên dịch / liên kết là đủ và tốt hơn.
Quinn Taylor

11
Nhiều trăn hơn là "obj-c-ic" :). Guido khá chủ động trong việc duy trì Python trên các hệ thống NeXT, bao gồm cả việc tạo phiên bản đầu tiên của PyObjC. Như vậy, ObjC đã làm ảnh hưởng python hơi .
bbum

3
+1 cho câu chuyện lịch sử thú vị của nhà độc tài nhân từ cho cuộc sống và sự giao thoa của anh ta với hệ thống NeXT huyền thoại.
pokstad

5
Cảm ơn bạn. Python, Java và Objective-C đều có các mô hình đối tượng thời gian chạy khá giống nhau [với SmallTalk]. Java có nguồn gốc khá trực tiếp từ Objective-C. Python chạy một khóa học song song nhiều hơn một nguồn cảm hứng, nhưng Guido chắc chắn đã nghiên cứu NeXTSTEP một cách chặt chẽ.
bbum

1
Ngoại trừ tôi không tin tưởng giấy phép của nó và tôi chắc chắn sẽ thích clang hơn gcc. Nhưng đó là bước đầu tiên tốt cho chắc chắn.
Cthutu

18

Thời gian chạy có thể hỗ trợ nó nhưng chi phí sẽ rất lớn. Mỗi bộ chọn được gửi sẽ cần phải được kiểm tra xem nó là riêng tư hay công khai cho lớp đó, hoặc mỗi lớp sẽ cần quản lý hai bảng công văn riêng biệt. Điều này không giống với các biến ví dụ vì mức độ bảo vệ này được thực hiện tại thời điểm biên dịch.

Ngoài ra, thời gian chạy sẽ cần xác minh rằng người gửi thư riêng có cùng lớp với người nhận. Bạn cũng có thể bỏ qua các phương thức riêng tư; nếu lớp được sử dụng instanceMethodForSelector:, nó có thể trả lại IMPcho bất kỳ lớp nào khác để chúng gọi phương thức riêng trực tiếp.

Phương pháp riêng tư không thể bỏ qua công văn tin nhắn. Hãy xem xét kịch bản sau đây:

  1. Một lớp AllPubliccó một phương thức công khaidoSomething

  2. Một lớp khác HasPrivatecó một phương thức cá nhân cũng được gọi làdoSomething

  3. Bạn tạo một mảng chứa bất kỳ số lượng phiên bản nào của cả hai AllPublicHasPrivate

  4. Bạn có vòng lặp sau:

    for (id anObject in myArray)
        [anObject doSomething];

    Nếu bạn đã chạy vòng lặp đó từ bên trong AllPublic, thời gian chạy sẽ phải dừng bạn gửi doSomethingcác HasPrivatecá thể, tuy nhiên vòng lặp này sẽ có thể sử dụng được nếu nó ở trong HasPrivatelớp.


1
Tôi đồng ý rằng sẽ có một chi phí liên quan. Có lẽ đó không phải là thứ bạn muốn trên một thiết bị cầm tay. Bạn có thể hỗ trợ vòng loại lớn? Chi phí sẽ thực sự quan trọng trên một hệ thống máy tính để bàn?
Rob Jones

1
Mặc dù vậy nhưng bạn có thể đúng. Objective-C có chức năng gửi tin nhắn thời gian chạy so với lệnh gọi phương thức thời gian biên dịch của C ++, vì vậy bạn sẽ nói về kiểm tra thời gian biên dịch so với kiểm tra thời gian chạy.
Remus Rusanu

Có vẻ như thực sự kỳ lạ rằng lý do chính cho việc không hỗ trợ các phương thức riêng tư là do hiệu suất. Tôi nghĩ rằng Apple đơn giản đã không nghĩ rằng các phương pháp riêng tư là rất cần thiết, cho dù có hiệu quả hay không. Nếu không thì họ nên chọn một ngôn ngữ khác cho công ty trị giá hàng tỷ đô la của họ.
pokstad

1
Thêm các phương thức riêng tư sẽ không làm phức tạp việc thực hiện thời gian chạy, nó sẽ làm phức tạp ngữ nghĩa của ngôn ngữ. Kiểm soát truy cập là một khái niệm đơn giản trong các ngôn ngữ tĩnh, nhưng nó không phù hợp với công văn động đi kèm với nhiều chi phí khái niệm và dẫn đến nhiều người, tại sao điều này không hoạt động? câu hỏi
Jens Ayton

7
Phương pháp riêng tư sẽ mua gì cho bạn? Cuối cùng, Objective-C là ngôn ngữ dựa trên C. Do đó, nếu ai đó có mã đang chạy trong quy trình của bạn, họ có thể dễ dàng p0wnz quy trình của bạn bất kể mức độ riêng tư hoặc bị che khuất của bất kỳ API nào.
bbum

13

Các câu trả lời được đăng cho đến nay làm rất tốt việc trả lời câu hỏi từ góc độ triết học, vì vậy tôi sẽ đưa ra một lý do thực tế hơn: những gì sẽ đạt được bằng cách thay đổi ngữ nghĩa của ngôn ngữ? Nó đủ đơn giản để "che giấu" các phương thức riêng tư một cách hiệu quả. Ví dụ, hãy tưởng tượng bạn có một lớp được khai báo trong tệp tiêu đề, như vậy:

@interface MyObject : NSObject {}
- (void) doSomething;
@end

Nếu bạn có nhu cầu về các phương thức "riêng tư", bạn cũng có thể đặt phương thức này vào tệp thực hiện:

@interface MyObject (Private)
- (void) doSomeHelperThing;
@end

@implementation MyObject

- (void) doSomething
{
    // Do some stuff
    [self doSomeHelperThing];
    // Do some other stuff;
}

- (void) doSomeHelperThing
{
    // Do some helper stuff
}

@end

Chắc chắn, nó không hoàn toàn giống với các phương thức riêng tư C ++ / Java, nhưng nó đủ gần, vậy tại sao lại thay đổi ngữ nghĩa của ngôn ngữ, cũng như trình biên dịch, thời gian chạy, v.v., để thêm một tính năng đã được mô phỏng trong một chấp nhận được đường? Như đã lưu ý trong các câu trả lời khác, ngữ nghĩa truyền thông điệp - và sự phụ thuộc của chúng vào phản xạ thời gian chạy - sẽ khiến việc xử lý các tin nhắn "riêng tư" trở nên không tầm thường.


1
Vấn đề với cách tiếp cận này là ai đó có thể ghi đè các phương thức riêng tư (thậm chí vô tình) khi họ phân lớp của bạn. Tôi cho rằng, về cơ bản, bạn phải chọn những cái tên không có khả năng bị ghi đè.
dreamlax

3
Cảm giác giống như một bản hack trên đầu bản hack đối với tôi.
Rob Jones

1
@Mike: Apple đã tuyên bố rằng các tên phương thức có dấu gạch dưới hàng đầu chỉ được dành riêng cho Apple: developer.apple.com/mac/l Library / documentation / Coloa / Conception / / . Cách thay thế được đề xuất là tiền tố với hai chữ in hoa và sau đó là dấu gạch dưới.
dreamlax

8
Ngoài ra, hãy gửi SSN của bạn sau phương thức để các lập trình viên khác biết ai đã viết nó. = D Không gian tên, cần chúng !!!
pokstad

2
Nếu bạn lo lắng về các phương pháp mà bạn xác định bị ghi đè, bạn đã không chấp nhận đúng mức độ dài vốn có mà Objective-C khuyến khích ...
Kendall Helmstetter Gelner

7

Giải pháp đơn giản nhất là khai báo một số hàm C tĩnh trong các lớp Objective-C của bạn. Chúng chỉ có phạm vi tệp theo quy tắc C cho từ khóa tĩnh và do đó chúng chỉ có thể được sử dụng bởi các phương thức trong lớp đó.

Không ồn ào chút nào.


Những điều này có phải được xác định bên ngoài phần @im THỰC HÀNH không?
Rob Jones

@Rob - không có họ có thể (và rất có thể nên được) được xác định trong triển khai lớp của bạn.
Cromulent

6

Có, nó có thể được thực hiện mà không ảnh hưởng đến thời gian chạy bằng cách sử dụng một kỹ thuật đã được (các) trình biên dịch sử dụng để xử lý C ++: xáo trộn tên.

Nó đã không được thực hiện bởi vì nó đã được thiết lập rằng nó sẽ giải quyết một số khó khăn đáng kể trong không gian vấn đề mã hóa mà các kỹ thuật khác (ví dụ: tiền tố hoặc gạch dưới) có thể phá vỡ đủ. IOW, bạn cần nhiều nỗi đau hơn để vượt qua thói quen ăn sâu.

Bạn có thể đóng góp các bản vá cho clang hoặc gcc để thêm các phương thức riêng tư vào cú pháp và tạo ra các tên được đọc sai mà nó nhận ra một mình trong quá trình biên dịch (và ngay lập tức bị quên). Sau đó, những người khác trong cộng đồng Objective-C sẽ có thể xác định liệu nó có thực sự đáng giá hay không. Nó có khả năng nhanh hơn theo cách đó hơn là cố gắng thuyết phục các nhà phát triển.


2
Việc biên dịch tên thời gian biên dịch khó hơn nhiều so với người ta có thể giả sử vì bạn phải quản lý tất cả các bộ chọn phương thức, bao gồm cả các bộ chọn có thể xuất hiện trong các tệp XIB và những thứ có thể được truyền cho những thứ như NSSelectorFromString () cũng như KeyValueCoding và bạn bè ...
bbum

1
Có, nó sẽ được tham gia nhiều hơn nếu bạn muốn cung cấp hỗ trợ cho các phương thức riêng tư trên toàn bộ công cụ. Bảng biểu tượng của trình biên dịch sẽ cần được lưu trữ và có thể truy cập thông qua một số api toolchain. Đây không phải là lần đầu tiên Apple phải tăng dần khả năng cho các nhà phát triển đủ quan trọng như thời gian và sự ổn định cho phép. Tuy nhiên, nơi mà các phương pháp riêng tư có liên quan: người ta đặt câu hỏi liệu đó có phải là một chiến thắng đủ để thực hiện nỗ lực này hay không. Thậm chí còn nghi ngờ rằng họ nên truy cập bên ngoài lớp bằng chính các cơ chế khác này.
Huperniketes

1
Làm thế nào một thực thi chỉ biên dịch theo thời gian biên dịch + xáo trộn tên sẽ ngăn ai đó đi theo danh sách phương thức của một lớp và tìm thấy nó trong đó?
dreamlax

Nó sẽ không. Câu trả lời của tôi chỉ giải quyết câu hỏi được đặt ra bởi OP.
Huperniketes

Tôi đã nghĩ về việc cố gắng thêm các phương thức riêng tư vào Clang, một lý do tôi nghĩ các phương thức riêng sẽ tốt là chúng có thể được gọi trực tiếp như các hàm c đơn giản và sau đó bạn có thể có tất cả các tối ưu hóa chức năng C thông thường xảy ra. Tôi chưa bao giờ bận tâm vì thời gian và bạn đã có thể làm điều này bằng cách chỉ sử dụng c.
Ngày của Nathan ngày

4

Về cơ bản, nó phải thực hiện với hình thức gọi thông điệp của Objective-C. Bất kỳ tin nhắn nào cũng có thể được gửi đến bất kỳ đối tượng nào và đối tượng chọn cách trả lời tin nhắn. Thông thường nó sẽ trả lời bằng cách thực thi phương thức được đặt tên theo thông điệp, nhưng nó cũng có thể trả lời theo một số cách khác. Điều này không làm cho các phương thức riêng tư hoàn toàn không thể - Ruby thực hiện nó với một hệ thống chuyển tin nhắn tương tự - nhưng nó làm cho chúng hơi khó xử.

Ngay cả việc triển khai các phương thức riêng tư của Ruby cũng hơi khó hiểu với mọi người vì sự lạ lùng (bạn có thể gửi cho đối tượng bất kỳ tin nhắn nào bạn thích, ngoại trừ những thông báo trong danh sách này !). Về cơ bản, Ruby làm cho nó hoạt động bằng cách cấm các phương thức riêng tư được gọi với một người nhận rõ ràng. Trong Objective-C, nó sẽ đòi hỏi nhiều công việc hơn vì Objective-C không có tùy chọn đó.


1

Đó là một vấn đề với môi trường thời gian chạy của Objective-C. Trong khi C / C ++ biên dịch thành mã máy không thể đọc được, Objective-C vẫn duy trì một số thuộc tính có thể đọc được của con người như tên phương thức dưới dạng chuỗi . Điều này mang lại cho Objective-C khả năng thực hiện các tính năng phản chiếu .

EDIT: Là một ngôn ngữ phản chiếu mà không có phương thức riêng tư nghiêm ngặt làm cho Objective-C trở nên "pythonic" hơn ở chỗ bạn tin tưởng người khác sử dụng mã của bạn thay vì hạn chế phương thức họ có thể gọi. Sử dụng các quy ước đặt tên như dấu gạch dưới kép có nghĩa là để ẩn mã của bạn khỏi một bộ mã hóa máy khách thông thường, nhưng sẽ không ngăn các lập trình viên cần thực hiện công việc nghiêm túc hơn.


Và nếu người tiêu dùng lib của bạn có thể phản ánh các phương thức riêng tư của bạn, họ cũng không thể gọi chúng?
Jared Updike

Nếu họ biết nơi để tìm, đó không phải là cách mọi người đã sử dụng các thư viện không có giấy tờ trên iPhone? Vấn đề với iPhone là bạn có nguy cơ không chấp nhận ứng dụng của mình khi sử dụng bất kỳ API riêng tư nào.
pokstad

Nếu họ truy cập các phương thức riêng tư thông qua sự phản ánh, họ sẽ có được những gì họ xứng đáng. Bất kỳ lỗi nào không phải là lỗi của bạn.
gnasher729

1

Có hai câu trả lời tùy thuộc vào cách giải thích của câu hỏi.

Đầu tiên là bằng cách ẩn phương thức thực hiện khỏi giao diện. Điều này được sử dụng, thường là với một thể loại không có tên (ví dụ @interface Foo()). Điều này cho phép đối tượng gửi những tin nhắn đó nhưng không phải cho người khác - mặc dù người ta vẫn có thể vô tình ghi đè (hoặc nếu không).

Câu trả lời thứ hai, với giả định rằng đây là về hiệu suất và nội tuyến, được thực hiện nhưng thay vào đó là một hàm C cục bộ. Nếu bạn muốn một NSString *argphương thức 'private foo ( )', bạn sẽ làm void MyClass_foo(MyClass *self, NSString *arg)và gọi nó là một hàm C như thế nào MyClass_foo(self,arg). Cú pháp là khác nhau, nhưng nó hoạt động với loại đặc tính hiệu năng lành mạnh của các phương thức riêng của C ++.

Mặc dù điều này trả lời câu hỏi, tôi nên chỉ ra rằng loại không tên là cho đến nay cách thức Objective-C phổ biến hơn để làm điều này.


0

Objective-C không hỗ trợ các phương thức riêng tư vì nó không cần chúng.

Trong C ++, mọi phương thức phải được hiển thị trong khai báo của lớp. Bạn không thể có các phương thức mà ai đó bao gồm tệp tiêu đề có thể nhìn thấy. Vì vậy, nếu bạn muốn các phương thức mà mã bên ngoài triển khai của bạn không nên sử dụng, bạn không có lựa chọn nào khác, trình biên dịch phải cung cấp cho bạn một số công cụ để bạn có thể nói với nó rằng phương thức này không được sử dụng, đó là từ khóa "riêng tư".

Trong Objective-C, bạn có thể có các phương thức không có trong tệp tiêu đề. Vì vậy, bạn đạt được cùng một mục đích rất dễ dàng bằng cách không thêm phương thức vào tệp tiêu đề. Không cần phương pháp riêng tư. Objective-C cũng có lợi thế là bạn không cần biên dịch lại mọi người dùng của một lớp vì bạn đã thay đổi các phương thức riêng tư.

Đối với các biến thể hiện, mà bạn đã từng phải khai báo trong tệp tiêu đề (không còn nữa), @private, @public và @protected đều khả dụng.


0

Một câu trả lời còn thiếu ở đây là: bởi vì các phương thức riêng tư là một ý tưởng tồi từ quan điểm khả thi. Có vẻ là một ý tưởng tốt để làm cho một phương thức riêng tư khi viết nó, nhưng nó là một hình thức ràng buộc sớm. Bối cảnh có thể thay đổi và người dùng sau này có thể muốn sử dụng một triển khai khác. Một chút khiêu khích: "Các nhà phát triển nhanh nhẹn không sử dụng các phương thức riêng tư"

Theo một cách nào đó, giống như Smalltalk, Objective-C dành cho các lập trình viên trưởng thành. Chúng tôi coi trọng việc biết nhà phát triển ban đầu giả định giao diện nên là gì và chịu trách nhiệm giải quyết hậu quả nếu chúng tôi cần thay đổi triển khai. Vì vậy, có, đó là triết lý, không thực hiện.


Tôi đã bỏ phiếu này vì nó không xứng đáng để bỏ phiếu. Như Stephan nói, có một lập luận rằng các phương thức "riêng tư" là không cần thiết và nó tương tự như các đối số về cách gõ động và gõ tĩnh, trong đó, bất kể kết luận ưa thích của bạn là gì, cả hai bên đều có một điểm.
alastair
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.