Đặt iVars vào Objective-C “hiện đại” ở đâu?


81

Cuốn sách "iOS6 by Tutorials" của Ray Wenderlich có một chương rất hay về cách viết mã Objective-C "hiện đại" hơn. Trong một phần, sách mô tả cách di chuyển iVars từ tiêu đề của lớp vào tệp thực thi. Vì tất cả các iVars phải ở chế độ riêng tư, đây có vẻ là điều đúng đắn nên làm.

Nhưng cho đến nay tôi đã tìm thấy 3 cách để làm như vậy. Mọi người đang làm điều đó theo cách khác nhau.

1.) Đặt iVars dưới @implementantion bên trong một khối dấu ngoặc nhọn (Đây là cách nó được thực hiện trong sách).

2.) Đặt iVars dưới @implementantion mà không có khối dấu ngoặc nhọn

3.) Đặt iVars bên trong Giao diện riêng tư phía trên @implementantion (một phần mở rộng của lớp)

Tất cả các giải pháp này dường như hoạt động tốt và cho đến nay tôi không nhận thấy bất kỳ sự khác biệt nào trong hoạt động của ứng dụng của mình. Tôi đoán không có cách nào "đúng" để làm điều đó nhưng tôi cần viết một số hướng dẫn và tôi chỉ muốn chọn một cách duy nhất cho mã của mình.

Tôi nên đi con đường nào?

Chỉnh sửa: Tôi chỉ nói về iVars ở đây. Không phải thuộc tính. Chỉ các biến bổ sung mà đối tượng chỉ cần cho chính nó và không được để lộ ra bên ngoài.

Mẫu mã

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

4
Không có bình luận nào về "đúng" nhưng một tùy chọn khác - mà tôi dường như đang hướng tới - là hủy hoàn toàn iVars và biến mọi thứ thành thuộc tính (chủ yếu là trong phần mở rộng lớp trong tệp .m). Tính nhất quán giúp tôi không phải suy nghĩ về các chi tiết triển khai của trạng thái.
Phillip Mills

1
Câu hỏi này thực sự sẽ mang lại lợi ích cho những người khác nếu bạn cập nhật câu hỏi để hiển thị các ví dụ mã thực chứng minh từng lựa chọn trong số 3 tùy chọn. Cá nhân tôi chỉ sử dụng tùy chọn 1, đã thấy phương án 3, nhưng tôi không quen thuộc với tùy chọn 2.
rmaddy

1
Cảm ơn bạn. Tôi đã thêm một số mã mẫu.
TalkingCode

4
Xin lỗi, chỉ nhận thấy bản cập nhật. Tùy chọn 2 không hợp lệ. Điều đó không tạo ra ivars. Điều đó tạo ra các biến toàn cục của tệp. Mỗi cá thể của lớp đó sẽ chia sẻ một bộ biến đó. Bạn có thể cho chúng là các biến lớp chứ không phải các biến mẫu.
rmaddy

Câu trả lời:


162

Khả năng đặt các biến phiên bản trong @implementationkhối hoặc trong phần mở rộng lớp, là một tính năng của “thời gian chạy Objective-C hiện đại”, được sử dụng bởi mọi phiên bản iOS và các chương trình Mac OS X 64 bit.

Nếu bạn muốn viết các ứng dụng Mac OS X 32 bit, bạn phải đặt các biến phiên bản của mình trong phần @interfacekhai báo. Tuy nhiên, rất có thể bạn không cần hỗ trợ phiên bản 32-bit của ứng dụng. OS X đã hỗ trợ các ứng dụng 64-bit kể từ phiên bản 10.5 (Leopard), được phát hành hơn 5 năm trước.

Vì vậy, giả sử bạn chỉ viết ứng dụng sẽ sử dụng thời gian chạy hiện đại. Bạn nên đặt ivars của mình ở đâu?

Tùy chọn 0: Trong @interface(Không làm điều đó)

Đầu tiên, hãy xem xét lý do tại sao chúng ta không muốn đặt các biến cá thể trong một @interfacekhai báo.

  1. Việc đưa các biến cá thể vào một trình @interfacebày chi tiết về việc triển khai cho người dùng của lớp. Điều này có thể khiến những người dùng đó (ngay cả chính bạn khi sử dụng các lớp của riêng bạn!) Dựa vào các chi tiết triển khai mà họ không nên làm. (Điều này không phụ thuộc vào việc chúng tôi có khai báo ivars hay không @private.)

  2. Việc đưa các biến cá thể vào trong @interfacelàm cho quá trình biên dịch mất nhiều thời gian hơn, bởi vì bất cứ khi nào chúng tôi thêm, thay đổi hoặc xóa khai báo ivar, chúng tôi phải biên dịch lại mọi .mtệp nhập giao diện.

Vì vậy, chúng tôi không muốn đặt các biến cá thể trong @interface. Chúng ta nên đặt chúng ở đâu?

Tùy chọn 2: Không @implementationcó dấu ngoặc nhọn (Không làm điều đó)

Tiếp theo, hãy thảo luận về tùy chọn 2 của bạn, “Đặt iVars dưới @implementantion mà không có khối dấu ngoặc nhọn”. Điều này không khai báo các biến cá thể! Bạn đang nói về điều này:

@implementation Person

int age;
NSString *name;

...

Mã đó xác định hai biến toàn cục. Nó không khai báo bất kỳ biến cá thể nào.

Bạn có thể xác định các biến toàn cục trong .mtệp của mình , ngay cả trong tệp của mình @implementation, nếu bạn cần các biến toàn cục - ví dụ: vì bạn muốn tất cả các phiên bản của mình chia sẻ một số trạng thái, chẳng hạn như bộ nhớ cache. Nhưng bạn không thể sử dụng tùy chọn này để khai báo ivars, vì nó không khai báo ivars. (Ngoài ra, các biến toàn cục riêng tư đối với việc triển khai của bạn thường nên được khai báo staticđể tránh làm ô nhiễm không gian tên chung và có nguy cơ mắc lỗi thời gian liên kết.)

Điều đó để lại các tùy chọn 1 và 3 của bạn.

Tùy chọn 1: Trong @implementationdấu ngoặc nhọn (Do It)

Thông thường, chúng tôi muốn sử dụng tùy chọn 1: đặt chúng vào @implementationkhối chính của bạn , trong dấu ngoặc nhọn, như sau:

@implementation Person {
    int age;
    NSString *name;
}

Chúng tôi đặt chúng ở đây vì nó giữ cho sự tồn tại của chúng ở chế độ riêng tư, ngăn ngừa các vấn đề mà tôi đã mô tả trước đó và bởi vì thường không có lý do gì để đặt chúng vào một phần mở rộng lớp.

Vậy khi nào chúng tôi muốn sử dụng tùy chọn 3 của bạn, đưa chúng vào phần mở rộng lớp?

Tùy chọn 3: Trong phần mở rộng lớp học (Chỉ thực hiện khi cần thiết)

Hầu như không bao giờ có lý do để đặt chúng trong phần mở rộng lớp trong cùng tệp với của lớp @implementation. Chúng tôi cũng có thể đặt chúng vào @implementationtrường hợp đó.

Nhưng đôi khi chúng ta có thể viết một lớp đủ lớn để chúng ta muốn chia mã nguồn của nó thành nhiều tệp. Chúng tôi có thể làm điều đó bằng cách sử dụng các danh mục. Ví dụ: nếu chúng tôi đang triển khai UICollectionView(một lớp khá lớn), chúng tôi có thể quyết định rằng chúng tôi muốn đặt mã quản lý hàng đợi các chế độ xem có thể sử dụng lại (ô và các chế độ xem bổ sung) trong một tệp nguồn riêng biệt. Chúng tôi có thể làm điều đó bằng cách tách các thư đó thành một danh mục:

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

OK, bây giờ chúng ta có thể triển khai các UICollectionViewphương thức chính trong UICollectionView.mvà chúng ta có thể triển khai các phương thức quản lý các khung nhìn có thể sử dụng lại trong UICollectionView+ReusableViews.mđó, điều này làm cho mã nguồn của chúng ta dễ quản lý hơn một chút.

Nhưng mã quản lý chế độ xem có thể tái sử dụng của chúng tôi cần một số biến phiên bản. Những biến phải được tiếp xúc với những lớp học chính @implementationtrong UICollectionView.m, do đó trình biên dịch sẽ phát ra chúng trong .otập tin. Và chúng ta cũng cần hiển thị các biến cá thể đó với mã trong UICollectionView+ReusableViews.m, để các phương thức đó có thể sử dụng ivars.

Đây là nơi chúng ta cần một phần mở rộng lớp. Chúng tôi có thể đặt các ivars quản lý chế độ xem có thể tái sử dụng trong phần mở rộng lớp trong tệp tiêu đề riêng tư:

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

Chúng tôi sẽ không gửi tệp tiêu đề này cho người dùng thư viện của chúng tôi. Chúng tôi sẽ chỉ nhập nó vào UICollectionView.mvà vào UICollectionView+ReusableViews.mđể mọi thứ cần xem các ivars này đều có thể nhìn thấy chúng. Chúng tôi cũng đã đưa vào một phương thức mà chúng tôi muốn initphương thức chính gọi để khởi tạo mã quản lý chế độ xem có thể tái sử dụng. Chúng tôi sẽ gọi phương thức đó từ -[UICollectionView initWithFrame:collectionViewLayout:]trong UICollectionView.mvà chúng tôi sẽ triển khai nó trong UICollectionView+ReusableViews.m.


4
Tùy chọn 3: cũng có thể sử dụng được khi 1) bạn muốn thực thi sử dụng các thuộc tính cho ivars của mình, ngay cả trong quá trình triển khai của riêng bạn (@property int age) và 2) bạn muốn ghi đè một thuộc tính chỉ đọc (@property (readonly) int age) trong tiêu đề của bạn để cho phép mã của bạn truy cập thuộc tính dưới dạng ghi đọc trong quá trình triển khai (@property (readwrite) int age). Trừ khi có những cách khác để làm điều này, @rob mayoff?
leanne

@leanne Nếu bạn muốn ghi đè một thuộc readonlytính thành private readwrite, bạn cần sử dụng phần mở rộng lớp. Nhưng câu hỏi đặt ra là kê khai ở đâu chứ không phải kê khai tài sản ở đâu. Tôi không hiểu cách sử dụng tiện ích mở rộng lớp có thể ngăn việc triển khai truy cập ivars. Để làm điều đó, bạn cần phải đặt các triển khai phương thức của mình trong một danh mục, bởi vì trình biên dịch phải xem tất cả các phần mở rộng lớp khai báo ivar trước khi nó nhìn thấy @implementation.
cướp mayoff

@rob mayoff, true và true - bạn tạo ra điểm hợp lệ. Holli chỉ rõ rằng các thuộc tính không được đề cập ở đây và "thực thi" có một chút mạnh mẽ về phía tôi về việc sử dụng chúng. Bất kể, nếu một người muốn sử dụng thuộc tính để truy cập ivars của họ, thay vì làm như vậy trực tiếp, thì đó sẽ là cách để làm điều đó. Và tất nhiên, bạn vẫn có thể truy cập trực tiếp vào ivars bằng cách sử dụng '_', (_age = someAge). Tôi đã thêm nhận xét vì tôi nghĩ việc sử dụng tài sản đáng được đề cập khi thảo luận về ivars, vì nhiều người sử dụng chúng cùng nhau và đây sẽ là cách để làm điều đó.
leanne

Đó là “Lựa chọn 0: Trong @interface (Đừng làm điều đó)”.
cướp mayoff

5

Phương án 2 là sai. Đó là các biến toàn cục, không phải biến cá thể.

Tùy chọn 1 và 3 về cơ bản giống hệt nhau. Nó hoàn toàn không tạo ra sự khác biệt.

Sự lựa chọn là đặt các biến cá thể vào tệp tiêu đề hay tệp triển khai. Ưu điểm của việc sử dụng tệp tiêu đề là bạn có một phím tắt nhanh chóng và dễ dàng (Command + Control + Up trong Xcode) để xem và chỉnh sửa các biến phiên bản và khai báo giao diện của bạn.

Điểm bất lợi là bạn để lộ các chi tiết riêng tư của lớp mình trong tiêu đề công khai. Đó là điều không mong muốn trong một số trường hợp, đặc biệt nếu bạn đang viết mã cho người khác sử dụng. Một vấn đề tiềm ẩn khác là nếu bạn đang sử dụng Objective-C ++, bạn nên tránh đưa bất kỳ kiểu dữ liệu C ++ nào vào tệp tiêu đề của mình.

Các biến cá thể triển khai là lựa chọn tuyệt vời cho một số trường hợp nhất định, nhưng đối với hầu hết các mã của tôi, tôi vẫn đặt các biến cá thể trong tiêu đề đơn giản vì nó thuận tiện hơn cho tôi với tư cách là một lập trình viên làm việc trong Xcode. Lời khuyên của tôi là hãy làm bất cứ điều gì bạn cảm thấy thuận tiện hơn cho mình.


4

Phần lớn nó liên quan đến khả năng hiển thị của ivar đối với các lớp con. Các lớp con sẽ không thể truy cập các biến cá thể được xác định trong @implementationkhối.

Đối với mã có thể sử dụng lại mà tôi định phân phối (ví dụ như mã thư viện hoặc mã khung), nơi tôi không muốn hiển thị các biến phiên bản để kiểm tra công khai, thì tôi có xu hướng đặt các ivars trong khối triển khai (tùy chọn 1 của bạn).


3

Bạn nên đặt các biến cá thể trong một giao diện riêng tư phía trên việc triển khai. Tùy chọn 3.

Tài liệu để đọc về điều này là hướng dẫn Lập trình trong Objective-C .

Từ tài liệu:

Bạn có thể xác định các biến phiên bản mà không cần thuộc tính

Cách tốt nhất là sử dụng một thuộc tính trên một đối tượng bất kỳ lúc nào bạn cần theo dõi một giá trị hoặc một đối tượng khác.

Nếu bạn cần xác định các biến cá thể của riêng mình mà không cần khai báo thuộc tính, bạn có thể thêm chúng vào bên trong dấu ngoặc nhọn ở đầu giao diện lớp hoặc triển khai, như thế này:


1
Tôi đồng ý với bạn: Nếu bạn định định nghĩa ivar, thì phần mở rộng lớp riêng là nơi tốt nhất. Tuy nhiên, thật kỳ lạ, tài liệu bạn tham khảo không chỉ không có vị trí về nơi mà ivars nên được xác định (nó chỉ đưa ra cả ba lựa chọn thay thế), nhưng nó còn đi xa hơn và khuyến nghị rằng không nên sử dụng ivars chút nào, nhưng đúng hơn là "cách tốt nhất là sử dụng một thuộc tính." Đó là lời khuyên tốt nhất mà tôi thấy cho đến nay.
Rob

1

Các ivars công khai thực sự nên được khai báo thuộc tính trong @interface (có thể là những gì bạn đang nghĩ đến trong 1). Các ivars riêng tư, nếu bạn đang chạy Xcode mới nhất và sử dụng thời gian chạy hiện đại (64-bit OS X hoặc iOS), có thể được khai báo trong @implementation (2), thay vì trong phần mở rộng lớp, có thể là những gì bạn ' đang nghĩ đến trong 3.

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.