2011-01-13 25 views
34

Tôi đã học được điều gì đó mới trong khi cố gắng tìm ra lý do tại sao thuộc tính ghi đè của tôi được khai báo trong Danh mục riêng tư không tạo ra trình thiết lập. Nó được vì loại của tôi đã được đặt tên:Minutia về Mục tiêu-C Thể loại và Tiện ích mở rộng

// .m 
@interface MyClass (private) 
@property (readwrite, copy) NSArray* myProperty; 
@end 

Thay đổi nó để:

// .m 
@interface MyClass() 
@property (readwrite, copy) NSArray* myProperty; 
@end 

và setter của tôi được tổng hợp. Bây giờ tôi biết rằng Lớp mở rộng không chỉ là một tên khác cho một Danh mục ẩn danh. Rời khỏi một danh mục chưa được đặt tên khiến nó biến thành một con thú khác: một cái bây giờ cho phép thực thi phương thức thực hiện biên dịch-thời gian và cho phép bạn thêm các con ngà. Bây giờ tôi đã hiểu được những triết lý chung bên dưới mỗi loại: Các danh mục thường được sử dụng để thêm các phương thức vào bất kỳ lớp nào trong thời gian chạy và Lớp mở rộng thường được sử dụng để thực thi triển khai API riêng tư và thêm ivars. Tôi chấp nhận điều này.

Nhưng có những trò lừa đảo làm tôi bối rối. Đầu tiên, ở cấp độ cao: Tại sao lại phân biệt như thế này? Những khái niệm này có vẻ giống như những ý tưởng tương tự mà không thể quyết định xem chúng có giống nhau hay các khái niệm khác nhau hay không. Nếu chúng giống nhau, tôi sẽ mong đợi những điều tương tự chính xác có thể bằng cách sử dụng Danh mục không có tên như với Danh mục có tên (mà chúng không có). Nếu chúng khác nhau, (chúng là) Tôi sẽ mong đợi một sự khác biệt về cú pháp giữa hai điều này. Có vẻ kỳ quặc khi nói, "Ồ, nhân tiện, để thực hiện một lớp mở rộng, chỉ cần viết một thể loại, nhưng để lại tên. Nó kỳ diệu thay đổi."

Thứ hai, về chủ đề thực thi biên dịch thời gian: Nếu bạn không thể thêm thuộc tính vào một Danh mục được đặt tên, tại sao làm như vậy thuyết phục trình biên dịch mà bạn đã làm điều đó? Để làm rõ, tôi sẽ minh họa bằng ví dụ của tôi. Tôi có thể khai báo thuộc tính chỉ đọc trong tệp tiêu đề:

// .h 
@interface MyClass : NSObject 
@property (readonly, copy) NSString* myString; 
@end 

Bây giờ, tôi muốn chuyển sang tệp triển khai và tự mình đọc quyền truy cập tài sản. Nếu tôi làm đúng:

// .m 
@interface MyClass() 
@property (readwrite, copy) NSString* myString; 
@end 

Tôi nhận được cảnh báo khi tôi không tổng hợp và khi tôi làm, tôi có thể đặt thuộc tính và mọi thứ đều có màu đào. Nhưng, kỳ phiền, nếu tôi xảy ra được một chút sai lầm về sự khác biệt giữa các loại và lớp mở rộng và tôi cố gắng:

// .m 
@interface MyClass (private) 
@property (readwrite, copy) NSString* myString; 
@end 

Trình biên dịch là hoàn toàn bình định vào suy nghĩ rằng tài sản là readwrite. Tôi không nhận được cảnh báo, và thậm chí không có lỗi biên dịch tốt đẹp "Object không thể được thiết lập - hoặc thuộc tính chỉ đọc hoặc không có setter tìm thấy" khi thiết lập myString mà tôi đã có tôi không khai báo tài sản readwrite trong thể loại. Tôi chỉ nhận được ngoại lệ "Không phản hồi với bộ chọn" khi chạy. Nếu thêm các thuộc tính và các thuộc tính không được hỗ trợ bởi (được đặt tên) Các thể loại, có quá nhiều thứ để yêu cầu trình biên dịch chơi theo cùng các quy tắc không? Tôi có thiếu một số triết lý thiết kế lớn?

+0

Tôi tin rằng bạn có nghĩa là 'readwrite' cho khai báo thuộc tính myString riêng tư. Tôi đã chỉnh sửa để phản ánh điều đó; hy vọng đó là chính xác. –

+0

Thật vậy, tôi đã làm. Cảm ơn. –

+0

Câu hỏi Fantasic! –

Trả lời

53

mở rộng lớp đã được thêm vào trong Objective-C 2.0 để giải quyết hai vấn đề cụ thể:

  1. Cho phép một đối tượng để có một "tin" giao diện đó được kiểm tra bởi trình biên dịch.
  2. Cho phép các thuộc tính có thể đọc được, có thể ghi công khai.

Interface Private

Trước Objective-C 2.0, nếu một nhà phát triển muốn có một tập hợp các phương pháp trong Objective-C, họ thường tuyên bố một "cá nhân" loại trong tập tin thực thi của lớp:

@interface MyClass (Private) 
- (id)awesomePrivateMethod; 
@end 

Tuy nhiên, những phương pháp tư nhân thường được trộn vào khối @implementation của lớp (không một @implementation khối riêng biệt cho các loại Private). Và tại sao không? Đây không thực sự là phần mở rộng cho lớp; họ chỉ bù đắp cho việc thiếu các hạn chế công cộng/tư nhân trong các mục tiêu-C.

Vấn đề là trình biên dịch Objective-C giả định rằng các phương thức được khai báo trong một danh mục sẽ được triển khai ở nơi khác, vì vậy chúng không kiểm tra để đảm bảo các phương thức được triển khai. Do đó, nhà phát triển có thể khai báoawesomePrivateMethod nhưng không triển khai được và trình biên dịch sẽ không cảnh báo cho họ về sự cố. Đó là vấn đề bạn nhận thấy: trong một thể loại, bạn có thể khai báo một thuộc tính (hoặc một phương thức) nhưng không nhận được cảnh báo nếu bạn chưa bao giờ thực hiện nó - đó là vì trình biên dịch hy vọng nó sẽ được thực hiện "một nơi nào đó" , trong một đơn vị biên dịch độc lập với cái này).

Nhập tiện ích mở rộng lớp học. Các phương thức được khai báo trong một phần mở rộng lớp được giả định sẽ được thực hiện trong khối @implementation chính; nếu không, trình biên dịch sẽ đưa ra cảnh báo.

công khai-Readable, tư nhân có khả năng ghi Thuộc tính

Nó thường là có lợi để thực hiện một cấu trúc dữ liệu không thay đổi - có nghĩa là, một trong đó mã bên ngoài có thể không sử dụng một setter để sửa đổi trạng thái của đối tượng. Tuy nhiên, nó vẫn có thể được tốt đẹp để có một tài sản ghi cho nội bộ sử dụng. Tiện ích mở rộng lớp cho phép: trong giao diện công khai, nhà phát triển có thể khai báo thuộc tính là chỉ đọc, nhưng sau đó khai báo thuộc tính để có thể ghi trong tiện ích mở rộng lớp. Để mã bên ngoài, thuộc tính sẽ là chỉ đọc, nhưng một setter có thể được sử dụng trong nội bộ.

Vậy tại sao tôi không thể khai báo thuộc tính có thể ghi trong danh mục?

Danh mục không thể thêm biến mẫu.Một setter thường đòi hỏi một số loại lưu trữ sao lưu. Nó đã được quyết định rằng cho phép một thể loại để tuyên bố một tài sản có khả năng yêu cầu một cửa hàng sao lưu là A Bad Thing ™. Do đó, một thể loại không thể khai báo một thuộc tính ghi được.

Họ Nhìn tương tự, nhưng khác

Sự rắc rối nằm trong ý tưởng rằng một phần mở rộng lớp chỉ là một "loại vô danh". Cú pháp tương tự và ngụ ý ý tưởng này; Tôi tưởng tượng nó chỉ được chọn vì nó quen thuộc với các lập trình viên Objective-C và, theo một số cách, các phần mở rộng của lớp giống như các thể loại. Họ giống nhau ở chỗ cả hai tính năng đều cho phép bạn thêm các phương thức (và thuộc tính) vào một lớp hiện có, nhưng chúng phục vụ các mục đích khác nhau và do đó cho phép các hành vi khác nhau.

+0

Giải thích tuyệt vời. Tôi đã quen thuộc với phương pháp danh mục riêng trước 2.0 để khai báo các phương thức riêng được thực hiện trong @implementation của lớp. Tôi cũng dường như đã bỏ lỡ bản ghi nhớ về sự ra đời của Tiện ích mở rộng. Đối với tôi, có vẻ như nhiều nhà phát triển chỉ đơn giản là chọn phương pháp ít rõ ràng hơn khi không đặt tên cho Danh mục riêng của họ mà tôi không thích. Đạo đức tôi cần tìm về cơ bản là không có thứ gì như một Thể loại không tên. Cảm ơn bạn. –

+2

Câu trả lời tuyệt vời .... – bbum

+0

Câu trả lời hay, tôi đọc nó một lần nữa và một lần nữa – guoleii

3

Bạn có thể thêm thuộc tính vào danh mục, bạn không thể tổng hợp nó. Nếu bạn sử dụng một danh mục, bạn sẽ không nhận được cảnh báo biên dịch vì nó dự kiến ​​trình cài đặt sẽ được triển khai trong danh mục.

+0

Tất nhiên. Tôi cần phải có suy nghĩ về điều đó. Tôi dành quá nhiều thời gian để khai báo các phương thức riêng trong các Danh mục được triển khai trong lớp '@implementation Tôi bỏ qua rằng các Category thường có riêng, riêng biệt, thực hiện. –

8

Bạn đang bối rối bởi sự giống nhau về cú pháp. Tiện ích mở rộng lớp học là không phải là chỉ là danh mục chưa được đặt tên. Một mở rộng lớp là một cách để làm cho một phần của giao diện của bạn riêng tư và một phần công khai - cả hai đều được coi là một phần của khai báo giao diện của lớp. Là một phần của giao diện của lớp, phần mở rộng phải được định nghĩa là một phần của lớp.

Một thể loại, mặt khác, là một cách để thêm các phương thức vào một lớp hiện có khi chạy.Điều này có thể, ví dụ, trong một gói riêng biệt chỉ được tải vào thứ Năm.

Đối với hầu hết lịch sử của Objective-C, không thể thêm biến mẫu vào lớp khi chạy, khi danh mục được tải. Điều này đã được làm việc xung quanh rất gần đây trong thời gian chạy mới, nhưng ngôn ngữ vẫn cho thấy những vết sẹo của các lớp cơ sở mỏng manh của nó. Một trong số đó là ngôn ngữ không hỗ trợ các danh mục thêm các biến mẫu. Bạn sẽ phải viết ra các getters và setters mình, phong cách trường học cũ.

Biến thể hiện trong các danh mục cũng hơi phức tạp. Vì chúng không nhất thiết có mặt khi cá thể được tạo ra và trình khởi tạo có thể không biết gì về chúng, khởi tạo chúng là một vấn đề không tồn tại với các biến cá thể thông thường.

+4

+1 cho "... chỉ được tải vào Thứ Năm." – vikingosegundo

+0

Có, tôi bị nhầm lẫn bởi cú pháp tương tự =). Tôi có thể thấy rằng Tiện ích mở rộng là một khái niệm cơ bản khác với Danh mục. Tôi đã tự hỏi tại sao một sự khác biệt nhỏ trong sytax dẫn đến những hành vi khác nhau như vậy. Đối với những người không quen thuộc với Extenstions (tôi, vào khoảng ngày hôm qua), một Extension IS cú pháp một Category chưa được đặt tên. –

+0

@Matt Wilding: Chúng tôi sẽ phải hy vọng một trong những nhóm Cocoa của Apple dừng lại để có câu trả lời chính thức cho "lý do". Danh mục bí mật là cách cũ để thêm hành vi "riêng tư" vào một lớp, vì vậy giả định của tôi là dường như thuận tiện khi sử dụng cú pháp tương tự cho thay thế cao cấp. Tôi nghi ngờ họ nhận ra làm thế nào khó hiểu nó sẽ xuất hiện cho những người đã không được sử dụng Cocoa tất cả cùng. – Chuck

0

Chỉ cần làm rõ một chút về REASON cho hành vi khác nhau của các danh mục chưa được đặt tên (bây giờ được gọi là Tiện ích mở rộng lớp) và các danh mục bình thường (được đặt tên).

Điều này rất đơn giản. Bạn có thể có nhiều loại mở rộng cùng một lớp, được nạp vào thời gian chạy mà không cần trình biên dịch và trình liên kết biết. (hãy xem xét nhiều phần mở rộng đẹp mà mọi người đã viết cho NSObject, thêm vào đó chức năng post-hoc).

Bây giờ Objective-C không có khái niệm về NAME SPACE. Do đó, có các iVars được định nghĩa trong một thể loại được đặt tên có thể tạo ra một xung đột biểu tượng trong thời gian chạy. Nếu hai danh mục khác nhau có thể xác định cùng một

@interface myObject (extensionA) { 
NSString *myPrivateName; 
} 
@end 
@interface myObject (extensionB) { 
NSString *myPrivateName; 
} 
@end 

thì ít nhất, sẽ có bộ nhớ tràn ngập khi chạy.

Trong mâu thuẫn, tiện ích mở rộng Lớp có NO NAME và do đó chỉ có thể ONE. Đó là lý do tại sao bạn có thể định nghĩa iVars ở đó. Họ được đảm bảo là duy nhất. Đối với các lỗi trình biên dịch và cảnh báo liên quan đến các loại và mở rộng lớp + ivars và định nghĩa tài sản, tôi phải đồng ý rằng chúng không hữu ích, và tôi đã dành quá nhiều thời gian để hiểu tại sao mọi thứ biên dịch hay không, và làm thế nào họ làm việc (nếu họ làm việc) sau khi họ biên dịch.

Các vấn đề liên quan