@ class so với #import


709

Theo hiểu biết của tôi, người ta nên sử dụng khai báo lớp chuyển tiếp trong sự kiện ClassA cần bao gồm tiêu đề ClassB và ClassB cần bao gồm tiêu đề ClassA để tránh bất kỳ vùi tròn nào. Tôi cũng hiểu rằng an #importlà một đơn giản ifndefđể bao gồm chỉ xảy ra một lần.

Câu hỏi của tôi là: Khi nào một người sử dụng #importvà khi nào một người sử dụng @class? Đôi khi nếu tôi sử dụng một @classkhai báo, tôi thấy một cảnh báo trình biên dịch phổ biến như sau:

warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.

Tôi thực sự thích hiểu điều này, thay vì chỉ xóa phần @classkhai báo phía trước và ném #importvào để làm im lặng những cảnh báo mà trình biên dịch đưa ra cho tôi.


10
Tuyên bố chuyển tiếp chỉ nói với trình biên dịch, "Này, tôi biết tôi đang khai báo những thứ mà bạn không nhận ra, nhưng khi tôi nói @MyClass, tôi hứa rằng tôi sẽ #import nó trong quá trình thực hiện".
JoeCortopassi

Câu trả lời:


754

Nếu bạn thấy cảnh báo này:

cảnh báo: người nhận 'MyCoolClass' là lớp chuyển tiếp và @interface tương ứng có thể không tồn tại

bạn cần #importtệp, nhưng bạn có thể làm điều đó trong tệp thực hiện (.m) và sử dụng @classkhai báo trong tệp tiêu đề của bạn.

@classkhông (thường) loại bỏ sự cần thiết đối với #importcác tệp, nó chỉ di chuyển yêu cầu xuống gần hơn với nơi thông tin hữu ích.

Ví dụ

Nếu bạn nói @class MyCoolClass, trình biên dịch biết rằng nó có thể thấy một cái gì đó như:

MyCoolClass *myObject;

Nó không phải lo lắng về bất cứ điều gì khác ngoài MyCoolClassmột lớp hợp lệ và nó nên dành chỗ cho một con trỏ tới nó (thực sự, chỉ là một con trỏ). Do đó, trong tiêu đề của bạn, @classđủ 90% thời gian.

Tuy nhiên, nếu bạn cần tạo hoặc truy cập myObjectcác thành viên, bạn sẽ cần cho trình biên dịch biết những phương thức đó là gì. Tại thời điểm này (có lẽ là trong tệp triển khai của bạn), bạn sẽ cần phải #import "MyCoolClass.h"nói cho trình biên dịch biết thêm thông tin ngoài "đây là một lớp".


5
Câu trả lời tuyệt vời, cảm ơn. Để tham khảo trong tương lai: điều này cũng xử lý các tình huống trong đó bạn có @classgì đó trong .htệp của mình , nhưng quên #importnó trong .m, hãy thử truy cập một phương thức trên @classđối tượng ed và nhận các cảnh báo như : warning: no -X method found.
Tim

24
Trường hợp bạn cần #import thay vì @ class là nếu tệp .h bao gồm các loại dữ liệu hoặc các định nghĩa khác cần thiết cho giao diện của lớp.
Ken Aspeslagh

2
Một lợi thế lớn khác không được đề cập ở đây là biên dịch nhanh. Vui lòng tham khảo câu trả lời của Venkateshwar
MartinMoizard

@BenGottlieb Không phải chữ 'm' trong "myCoolClass" có phải là chữ hoa không? Như trong "MyCoolClass"?
Basil Bourque

182

Ba quy tắc đơn giản:

  • Chỉ có #importsiêu lớp và các giao thức được chấp nhận, trong các tệp tiêu đề ( .htệp).
  • #importtất cả các lớp và giao thức, bạn gửi tin nhắn đến khi thực hiện ( .mtệp).
  • Chuyển tiếp khai báo cho mọi thứ khác.

Nếu bạn chuyển tiếp khai báo trong các tệp thực hiện, thì có lẽ bạn đã làm sai.


22
Trong các tệp tiêu đề, bạn cũng có thể phải #import bất cứ điều gì xác định giao thức mà lớp bạn áp dụng.
Tyler

Có sự khác biệt nào khi khai báo #import trong tệp giao diện h hoặc tệp thực hiện m không?
Samuel G

Và #import nếu bạn sử dụng các biến thể hiện từ lớp
user151019

1
@Mark - Được bảo vệ bởi quy tắc số 1, chỉ truy cập ivars từ siêu lớp của bạn, ngay cả khi đó.
PeyloW

@Tyler tại sao không chuyển tiếp khai báo giao thức?
JoeCortopassi

110

Xem tài liệu Ngôn ngữ lập trình Objective-C trên ADC

Trong phần Xác định một Lớp học | Giao diện lớp nó mô tả lý do tại sao điều này được thực hiện:

Lệnh @group giảm thiểu số lượng mã mà trình biên dịch và trình liên kết nhìn thấy, và do đó là cách đơn giản nhất để đưa ra khai báo về phía trước của tên lớp. Rất đơn giản, nó tránh được các vấn đề tiềm ẩn có thể xảy ra với việc nhập các tệp vẫn nhập các tệp khác. Ví dụ, nếu một lớp khai báo một biến đối tượng được gõ tĩnh của một lớp khác và hai tệp giao diện của chúng nhập vào nhau, thì không lớp nào có thể biên dịch chính xác.

Tôi hi vọng cái này giúp được.


48

Sử dụng khai báo chuyển tiếp trong tệp tiêu đề nếu cần và #importcác tệp tiêu đề cho bất kỳ lớp nào bạn đang sử dụng khi triển khai. Nói cách khác, bạn luôn luôn #importlà các tệp bạn đang sử dụng khi triển khai và nếu bạn cần tham chiếu một lớp trong tệp tiêu đề của mình, hãy sử dụng khai báo chuyển tiếp.

Các ngoại lệ này là bạn nên #importmột lớp hoặc giao thức chính quy bạn đang kế thừa từ trong tập tin tiêu đề của bạn (trong trường hợp này bạn sẽ không cần phải nhập nó trong việc thực hiện).


24

Cách thực hành phổ biến là sử dụng @ class trong các tệp tiêu đề (nhưng bạn vẫn cần #import siêu lớp) và #import trong các tệp thực hiện. Điều này sẽ tránh bất kỳ vùi tròn, và nó chỉ hoạt động.


2
Tôi nghĩ rằng #import tốt hơn #Include ở chỗ nó chỉ nhập một thể hiện?
Matthew Schinckel

2
Thật. Không biết đó là về các vùi tròn hay đặt hàng không chính xác, nhưng tôi đã thoát khỏi quy tắc đó (với một lần nhập trong một tiêu đề, nhập khẩu không còn cần thiết trong việc thực hiện subclasse), và chẳng mấy chốc nó đã trở nên lộn xộn. Dòng dưới cùng, theo quy tắc đó, và trình biên dịch sẽ được hạnh phúc.
Steph Thirion

1
Các tài liệu hiện tại nói rằng #import"giống như chỉ thị #include của C, ngoại trừ việc nó đảm bảo rằng cùng một tệp không bao giờ được bao gồm nhiều hơn một lần." Vì vậy, theo điều này #importquan tâm đến các vùi tròn, các @classchỉ thị không đặc biệt giúp với điều đó.
Eric

24

Một ưu điểm khác: biên dịch nhanh

Nếu bạn bao gồm một tệp tiêu đề, bất kỳ thay đổi nào trong đó cũng khiến tệp hiện tại cũng biên dịch nhưng đây không phải là trường hợp nếu tên lớp được bao gồm là @class name. Tất nhiên bạn sẽ cần bao gồm tiêu đề trong tệp nguồn


18

Yêu cầu của tôi là thế này. Khi nào một người sử dụng #import và khi nào một người sử dụng @ class?

Câu trả lời đơn giản: Bạn #importhoặc #includekhi có một sự phụ thuộc vật lý. Nếu không, bạn sử dụng tờ khai chuyển tiếp ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Dưới đây là một số ví dụ phổ biến về sự phụ thuộc vật lý:

  • Bất kỳ giá trị C hoặc C ++ (một con trỏ hoặc tham chiếu không phải là một phụ thuộc vật lý). Nếu bạn có một CGPointivar hoặc thuộc tính, trình biên dịch sẽ cần phải xem khai báo CGPoint.
  • Siêu lớp của bạn.
  • Một phương pháp bạn sử dụng.

Đôi khi, nếu tôi sử dụng khai báo @ class, tôi thấy một cảnh báo trình biên dịch phổ biến như sau: "warning: receive 'FooContoder' là lớp chuyển tiếp và @interface tương ứng có thể không tồn tại."

Các nhà soạn nhạc thực sự rất khoan dung trong vấn đề này. Nó sẽ bỏ các gợi ý (chẳng hạn như ở trên), nhưng bạn có thể bỏ rác ngăn xếp của mình một cách dễ dàng nếu bạn bỏ qua chúng và không #importđúng cách. Mặc dù nó nên (IMO), trình biên dịch không thực thi điều này. Trong ARC, trình biên dịch chặt chẽ hơn vì nó chịu trách nhiệm đếm tham chiếu. Điều gì xảy ra là trình biên dịch rơi vào mặc định khi nó gặp một phương thức không xác định mà bạn gọi. Mỗi giá trị trả về và tham số được giả định là id. Do đó, bạn nên xóa mọi cảnh báo khỏi cơ sở mã của mình vì điều này nên được coi là sự phụ thuộc vật lý. Điều này tương tự với việc gọi một hàm C không được khai báo. Với C, các tham số được giả định là int.

Lý do bạn muốn ưu tiên khai báo là bạn có thể giảm thời gian xây dựng của mình theo các yếu tố vì có sự phụ thuộc tối thiểu. Với các khai báo chuyển tiếp, trình biên dịch thấy có một tên, và có thể phân tích và biên dịch chính xác chương trình mà không cần xem khai báo lớp hoặc tất cả các phụ thuộc của nó khi không có phụ thuộc vật lý. Xây dựng sạch mất ít thời gian hơn. Xây dựng gia tăng mất ít thời gian hơn. Chắc chắn, cuối cùng bạn sẽ dành thêm một chút thời gian để đảm bảo rằng tất cả các tiêu đề bạn cần đều hiển thị cho mọi bản dịch, nhưng điều này sẽ giúp giảm thời gian xây dựng một cách nhanh chóng (giả sử dự án của bạn không nhỏ).

Nếu bạn sử dụng #importhoặc #includethay vào đó, bạn đang ném nhiều công việc vào trình biên dịch hơn mức cần thiết. Bạn cũng đang giới thiệu các phụ thuộc tiêu đề phức tạp. Bạn có thể ví nó như một thuật toán brute-force. Khi bạn #import, bạn đang kéo hàng tấn thông tin không cần thiết, đòi hỏi nhiều bộ nhớ, I / O đĩa và CPU để phân tích và biên dịch các nguồn.

ObjC khá gần với lý tưởng cho ngôn ngữ dựa trên C liên quan đến sự phụ thuộc vì NSObjectcác loại không bao giờ là giá trị - NSObjectcác loại luôn được tham chiếu con trỏ đếm. Vì vậy, bạn có thể thoát khỏi thời gian biên dịch cực kỳ nhanh nếu bạn cấu trúc các phần phụ thuộc của chương trình một cách phù hợp và chuyển tiếp nếu có thể vì có rất ít sự phụ thuộc vật lý cần thiết. Bạn cũng có thể khai báo các thuộc tính trong phần mở rộng lớp để giảm thiểu sự phụ thuộc hơn nữa. Đó là một phần thưởng lớn cho các hệ thống lớn - bạn sẽ biết sự khác biệt mà nó tạo ra nếu bạn đã từng phát triển một cơ sở mã C ++ lớn.

Do đó, khuyến nghị của tôi là sử dụng chuyển tiếp khi có thể, và sau đó đến #importnơi có sự phụ thuộc vật lý. Nếu bạn thấy cảnh báo hoặc cảnh báo khác ngụ ý sự phụ thuộc vật lý - hãy khắc phục tất cả. Cách khắc phục là #importtrong tập tin thực hiện của bạn.

Khi bạn xây dựng thư viện, bạn có thể sẽ phân loại một số giao diện thành một nhóm, trong trường hợp đó bạn sẽ #importthư viện đó nơi giới thiệu sự phụ thuộc vật lý (ví dụ #import <AppKit/AppKit.h>). Điều này có thể giới thiệu sự phụ thuộc, nhưng những người bảo trì thư viện thường có thể xử lý các phụ thuộc vật lý cho bạn khi cần - nếu họ giới thiệu một tính năng, họ có thể giảm thiểu tác động của nó đối với các bản dựng của bạn.


BTW nỗ lực tốt đẹp để giải thích những điều. Nhưng họ dường như khá phức tạp.
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.không hoàn toàn đúng Khối ném một kẽ hở trong câu trả lời của bạn, chỉ cần nói.
Richard J. Ross III

@ RichardJ.RossIII Biên và GCC cho phép một người khai báo và sử dụng các giá trị, trong khi clang cấm nó. và tất nhiên, phải có một giá trị đằng sau con trỏ.
justin

11

Tôi thấy rất nhiều "Làm theo cách này" nhưng tôi không thấy bất kỳ câu trả lời nào cho "Tại sao?"

Vì vậy: Tại sao bạn nên @group trong tiêu đề của bạn và #import chỉ trong triển khai của bạn? Bạn đang nhân đôi công việc của mình bằng cách phải luôn luôn @group #import. Trừ khi bạn sử dụng thừa kế. Trong trường hợp đó, bạn sẽ #importing nhiều lần cho một @ class. Sau đó, bạn phải nhớ xóa khỏi nhiều tệp khác nhau nếu bạn đột nhiên quyết định bạn không cần truy cập vào một tuyên bố nữa.

Nhập cùng một tệp nhiều lần không phải là vấn đề vì bản chất của #import. Hiệu suất biên dịch cũng không thực sự là một vấn đề. Nếu đúng như vậy, chúng tôi sẽ không #importing Ca cao / Ca cao.h hoặc tương tự như trong hầu hết các tệp tiêu đề chúng tôi có.


1
xem câu trả lời của Abizem ở trên để biết ví dụ từ tài liệu về lý do tại sao bạn nên làm điều này. Lập trình phòng thủ của nó khi bạn có hai tiêu đề lớp nhập lẫn nhau với các biến thể hiện của lớp khác.
jackslash

7

nếu chúng ta làm điều này

@interface Class_B : Class_A

có nghĩa là chúng ta đang kế thừa Class_A thành Class_B, trong Class_B chúng ta có thể truy cập tất cả các biến của class_A.

nếu chúng ta đang làm điều này

#import ....
@class Class_A
@interface Class_B

ở đây chúng tôi nói rằng chúng tôi đang sử dụng Class_A trong chương trình của mình, nhưng nếu chúng tôi muốn sử dụng các biến Class_A trong Class_B, chúng tôi phải #import Class_A trong tệp .m (tạo một đối tượng và sử dụng hàm và biến của nó).


5

để biết thêm thông tin về các phụ thuộc tệp & #import & @group hãy kiểm tra điều này:

http://qualitycoding.org/file-dependencies/ đó là bài viết hay

tóm tắt bài viết

nhập vào tệp tiêu đề:

  • #import siêu lớp bạn đang kế thừa và các giao thức bạn đang thực hiện.
  • Chuyển tiếp - khai báo mọi thứ khác (trừ khi nó đến từ một khung có tiêu đề chính).
  • Cố gắng loại bỏ tất cả các #imports khác.
  • Khai báo các giao thức trong các tiêu đề của riêng họ để giảm sự phụ thuộc.
  • Quá nhiều tuyên bố chuyển tiếp? Bạn có một lớp học lớn.

nhập khẩu trong các tập tin thực hiện:

  • Loại bỏ cruft #imports không được sử dụng.
  • Nếu một phương thức ủy quyền cho một đối tượng khác và trả về những gì nó nhận được, hãy thử chuyển tiếp khai báo đối tượng đó thay vì #importing nó.
  • Nếu bao gồm một mô-đun buộc bạn phải bao gồm cấp độ sau cấp độ phụ thuộc kế tiếp, bạn có thể có một nhóm các lớp muốn trở thành một thư viện. Xây dựng nó như một thư viện riêng biệt với một tiêu đề chính, vì vậy mọi thứ có thể được đưa vào dưới dạng một đoạn dựng sẵn.
  • Quá nhiều # nhập khẩu? Bạn có một lớp học lớn.

3

Khi tôi phát triển, tôi chỉ có ba điều không bao giờ gây ra cho tôi bất kỳ vấn đề nào.

  1. Nhập siêu lớp
  2. Nhập các lớp cha mẹ (khi bạn có con và cha mẹ)
  3. Nhập các lớp bên ngoài dự án của bạn (như trong khung và thư viện)

Đối với tất cả các lớp khác (lớp con và lớp con trong bản thân dự án của tôi), tôi khai báo chúng thông qua lớp chuyển tiếp.


3

Nếu bạn cố gắng khai báo một biến hoặc một thuộc tính trong tệp tiêu đề mà bạn chưa nhập, bạn sẽ gặp lỗi khi trình biên dịch không biết lớp này.

Suy nghĩ đầu tiên của bạn có lẽ là #importnó.
Điều này có thể gây ra vấn đề trong một số trường hợp.

Ví dụ: nếu bạn triển khai một loạt các phương thức C trong tệp tiêu đề hoặc cấu trúc hoặc một cái gì đó tương tự, vì chúng không nên được nhập nhiều lần.

Do đó, bạn có thể nói với trình biên dịch với @class:

Tôi biết bạn không biết lớp đó, nhưng nó tồn tại. Nó sẽ được nhập khẩu hoặc thực hiện ở nơi khác

Về cơ bản, nó bảo trình biên dịch tắt và biên dịch, mặc dù không chắc lớp này có được thực hiện hay không.

Bạn thường sẽ sử dụng #importtrong .m@classtrong các tệp .h .


0

Chuyển tiếp khai báo chỉ để trình biên dịch ngăn hiển thị lỗi.

trình biên dịch sẽ biết rằng có lớp với tên bạn đã sử dụng trong tệp tiêu đề của mình để khai báo.


bạn vui lòng cụ thể hơn một chút được không?
Sam Spencer

0

Trình biên dịch sẽ chỉ khiếu nại nếu bạn định sử dụng lớp đó theo cách mà trình biên dịch cần biết để thực hiện nó.

Ví dụ:

  1. Điều này có thể giống như nếu bạn định lấy lớp của mình từ nó hoặc
  2. Nếu bạn sẽ có một đối tượng của lớp đó là một biến thành viên (mặc dù hiếm).

Nó sẽ không phàn nàn nếu bạn chỉ sử dụng nó như một con trỏ. Tất nhiên, bạn sẽ phải #import nó trong tệp triển khai (nếu bạn đang khởi tạo một đối tượng của lớp đó) vì nó cần biết nội dung lớp để khởi tạo một đối tượng.

LƯU Ý: #import không giống như #include. Điều này có nghĩa là không có gì gọi là nhập khẩu tròn. nhập khẩu là một loại yêu cầu cho trình biên dịch xem xét một tệp cụ thể cho một số thông tin. Nếu thông tin đó đã có sẵn, trình biên dịch sẽ bỏ qua nó.

Chỉ cần thử điều này, nhập Ah trong Bh và Bh trong Ah Sẽ không có vấn đề hoặc khiếu nại và nó cũng sẽ hoạt động tốt.

Khi nào nên sử dụng @group

Bạn chỉ sử dụng @group nếu bạn thậm chí không muốn nhập tiêu đề trong tiêu đề của mình. Đây có thể là một trường hợp mà bạn thậm chí không quan tâm để biết lớp đó sẽ là gì. Các trường hợp mà bạn thậm chí có thể không có tiêu đề cho lớp đó.

Một ví dụ về điều này có thể là bạn đang viết hai thư viện. Một lớp, hãy gọi nó là A, tồn tại trong một thư viện. Thư viện này bao gồm một tiêu đề từ thư viện thứ hai. Tiêu đề đó có thể có con trỏ là A nhưng một lần nữa có thể không cần sử dụng nó. Nếu thư viện 1 chưa có sẵn, thư viện B sẽ không bị chặn nếu bạn sử dụng @ class. Nhưng nếu bạn đang muốn nhập Ah, thì tiến trình của thư viện 2 sẽ bị chặn.


0

Hãy nghĩ về @group như nói với trình biên dịch "hãy tin tôi, điều này tồn tại".

Hãy nghĩ về #import như là sao chép-dán.

Bạn muốn giảm thiểu số lượng nhập khẩu bạn có vì một số lý do. Không có bất kỳ nghiên cứu nào, điều đầu tiên xuất hiện trong đầu là nó làm giảm thời gian biên dịch.

Lưu ý rằng khi bạn kế thừa từ một lớp, bạn không thể sử dụng khai báo chuyển tiếp. Bạn cần nhập tệp, để lớp bạn khai báo biết cách xác định.


0

Đây là một kịch bản ví dụ, nơi chúng ta cần @ class.

Xem xét nếu bạn muốn tạo một giao thức trong tệp tiêu đề, có tham số với kiểu dữ liệu của cùng một lớp, thì bạn có thể sử dụng @ class. Xin nhớ rằng bạn cũng có thể khai báo các giao thức riêng biệt, đây chỉ là một ví dụ.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@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.