Khai báo / định nghĩa vị trí các biến trong ObjectiveC?


113

Kể từ khi bắt đầu làm việc trên các ứng dụng iOS và mục tiêu C, tôi đã thực sự bối rối trước các vị trí khác nhau nơi người ta có thể khai báo và xác định các biến. Một mặt chúng tôi có phương pháp C truyền thống, mặt khác chúng tôi có các chỉ thị ObjectiveC mới bổ sung OO trên đó. Mọi người có thể giúp tôi hiểu về phương pháp hay nhất và các tình huống mà tôi muốn sử dụng các vị trí này cho các biến của mình và có thể sửa lại hiểu biết hiện tại của tôi không?

Đây là một lớp mẫu (.h và .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Sự hiểu biết của tôi về 1 và 4 là đó là những khai báo và định nghĩa dựa trên tệp kiểu C không hiểu gì về khái niệm lớp, và do đó phải được sử dụng chính xác cách chúng sẽ được sử dụng trong C. Tôi đã thấy chúng được sử dụng để triển khai các singlet dựa trên biến tĩnh trước đây. Có cách sử dụng tiện lợi nào khác mà tôi đang thiếu không?
  • Kinh nghiệm của tôi khi làm việc với iOS là các ivars đã bị loại bỏ hoàn toàn bên ngoài chỉ thị @synthesize và do đó hầu như có thể bị bỏ qua. Đó là trường hợp?
  • Về thứ 5: tại sao tôi lại muốn khai báo các phương thức trong các giao diện riêng tư? Các phương thức lớp riêng của tôi dường như biên dịch tốt mà không cần khai báo trong giao diện. Nó chủ yếu là để dễ đọc?

Cảm ơn rất nhiều, folks!

Câu trả lời:


154

Tôi có thể hiểu sự bối rối của bạn. Đặc biệt là kể từ khi các bản cập nhật gần đây cho Xcode và trình biên dịch LLVM mới đã thay đổi cách khai báo ivars và thuộc tính.

Trước Objective-C "hiện đại" (trong Obj-C 2.0 "cũ"), bạn không có nhiều sự lựa chọn. Các biến phiên bản được sử dụng để khai báo trong tiêu đề giữa các dấu ngoặc nhọn { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Bạn chỉ có thể truy cập các biến này trong quá trình triển khai của mình, nhưng không thể truy cập từ các lớp khác. Để làm điều đó, bạn phải khai báo các phương thức truy cập, trông giống như sau:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

Bằng cách này, bạn cũng có thể lấy và đặt biến cá thể này từ các lớp khác, sử dụng cú pháp dấu ngoặc vuông thông thường để gửi tin nhắn (phương thức gọi):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Bởi vì việc khai báo và triển khai thủ công mọi phương thức trình truy cập khá khó chịu @property@synthesizeđã được giới thiệu để tự động tạo các phương thức trình truy cập:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Kết quả là mã rõ ràng hơn và ngắn hơn nhiều. Các phương thức của trình truy cập sẽ được thực hiện cho bạn và bạn vẫn có thể sử dụng cú pháp ngoặc như trước. Nhưng ngoài ra, bạn cũng có thể sử dụng cú pháp dấu chấm để truy cập các thuộc tính:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Vì Xcode 4.4 bạn không phải tự khai báo một biến cá thể nữa và bạn cũng có thể bỏ qua @synthesize. Nếu bạn không khai báo ivar, trình biên dịch sẽ thêm nó cho bạn và nó cũng sẽ tạo ra các phương thức truy cập mà bạn không cần phải sử dụng @synthesize.

Tên mặc định cho ivar được tạo tự động là tên hoặc thuộc tính của bạn bắt đầu bằng dấu gạch dưới. Bạn có thể thay đổi tên của ivar đã tạo bằng cách sử dụng@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Điều này sẽ hoạt động chính xác như mã ở trên. Vì lý do tương thích, bạn vẫn có thể khai báo ivars trong tiêu đề. Nhưng vì lý do duy nhất tại sao bạn muốn làm điều đó (chứ không phải khai báo thuộc tính) là tạo một biến private, nên bây giờ bạn cũng có thể làm điều đó trong tệp triển khai và đây là cách được ưu tiên.

Một @interfacekhối trong tệp triển khai thực sự là một Phần mở rộng và có thể được sử dụng để chuyển tiếp các phương thức khai báo (không cần thiết nữa) và (lại) khai báo các thuộc tính. Ví dụ, bạn có thể khai báo một readonlythuộc tính trong tiêu đề của mình.

@property (nonatomic, readonly) myReadOnlyVar;

và khai báo lại nó trong tệp triển khai của bạn readwriteđể có thể đặt nó bằng cú pháp thuộc tính và không chỉ thông qua truy cập trực tiếp vào ivar.

Đối với việc khai báo các biến hoàn toàn bên ngoài bất kỳ @interfacehoặc @implementationkhối nào, có, đó là các biến C thuần túy và hoạt động hoàn toàn giống nhau.


2
câu trả lời chính xác! Cũng lưu ý: stackoverflow.com/questions/9859719/…
nycynik

44

Đầu tiên, hãy đọc câu trả lời của @ DrummerB. Đây là một tổng quan tốt về lý do và những gì bạn thường nên làm. Với suy nghĩ đó, cho các câu hỏi cụ thể của bạn:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Không có định nghĩa biến thực tế nào ở đây (về mặt kỹ thuật thì làm như vậy là hợp pháp nếu bạn biết chính xác mình đang làm gì, nhưng đừng bao giờ làm điều này). Bạn có thể xác định một số loại thứ khác:

  • typdefs
  • enums
  • làm kiệt quệ

Externs trông giống như khai báo biến, nhưng chúng chỉ là một lời hứa để thực sự khai báo nó ở một nơi khác. Trong ObjC, chúng chỉ nên được sử dụng để khai báo các hằng và thường chỉ là các hằng chuỗi. Ví dụ:

extern NSString * const MYSomethingHappenedNotification;

Sau đó, bạn sẽ .mkhai báo hằng số thực trong tệp của mình :

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Theo ghi nhận của DrummerB, đây là di sản. Đừng đặt bất cứ thứ gì ở đây.


// 3) class-specific method / property declarations

@end

Vâng.


#import "SampleClass.h"

// 4) what goes here?

Các hằng số bên ngoài, như được mô tả ở trên. Ngoài ra tệp các biến tĩnh có thể truy cập tại đây. Chúng tương đương với các biến lớp trong các ngôn ngữ khác.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Vâng


@implementation SampleClass
{
    // 6) define ivars
}

Nhưng rất hiếm. Hầu như lúc nào bạn cũng nên cho phép clang (Xcode) tạo các biến cho bạn. Các ngoại lệ thường xảy ra xung quanh các ivars không phải objC (như các đối tượng Core Foundation và đặc biệt là các đối tượng C ++ nếu đây là một lớp objC ++) hoặc các ivars có ngữ nghĩa lưu trữ kỳ lạ (như các ivars không khớp với một thuộc tính vì một số lý do).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Nói chung, bạn không nên @synthesize nữa. Clang (Xcode) sẽ làm điều đó cho bạn, và bạn nên để nó.

Trong vài năm qua, mọi thứ đã trở nên đơn giản hơn đáng kể. Tác dụng phụ là hiện nay có ba thời đại khác nhau (ABI mong manh, ABI không dễ vỡ, ABI không dễ vỡ + tự động tổng hợp). Vì vậy, khi bạn nhìn thấy mã cũ hơn, nó có thể hơi khó hiểu. Do đó nhầm lẫn phát sinh từ đơn giản: D


Chỉ thắc mắc thôi, nhưng tại sao chúng ta không nên tổng hợp một cách rõ ràng? Tôi làm điều đó vì tôi thấy mã của mình dễ hiểu hơn, đặc biệt là khi một số thuộc tính có trình truy cập tổng hợp và một số có triển khai tùy chỉnh, vì tôi đã quen với việc tổng hợp. Có bất kỳ hạn chế nào để tổng hợp rõ ràng không?
Metabble

Vấn đề với việc sử dụng nó làm tài liệu là nó không thực sự ghi lại bất cứ điều gì. Mặc dù sử dụng tổng hợp, bạn có thể đã ghi đè một hoặc cả hai trình truy cập. Không có cách nào để nói từ dòng tổng hợp bất cứ điều gì thực sự hữu ích. Điều duy nhất tệ hơn không có tài liệu là tài liệu sai lệch. Để nó ra.
Rob Napier

3
Tại sao # 6 lại hiếm? Đây không phải là cách dễ nhất để lấy biến riêng sao?
pfrank

Cách dễ nhất và tốt nhất để có được tài sản riêng là # 5.
Rob Napier

1
@RobNapier Nó vẫn còn cần thiết để sử dụng @ tổng hợp đôi (ví dụ nếu một tài sản được readonly đã accessor nó ghi đè)
Andy

6

Em cũng còn khá mới nên hy vọng không vướng bận gì.

1 & 4: Biến toàn cục kiểu C: chúng có phạm vi tệp rộng. Sự khác biệt giữa hai tệp này là, vì chúng có chiều rộng tệp, tệp đầu tiên sẽ có sẵn cho bất kỳ ai nhập tiêu đề trong khi tệp thứ hai thì không.

2: biến cá thể. Hầu hết các biến thể hiện được tổng hợp và truy xuất / thiết lập thông qua các trình truy cập bằng cách sử dụng các thuộc tính vì nó giúp quản lý bộ nhớ tốt và đơn giản, cũng như cung cấp cho bạn ký hiệu dấu chấm dễ hiểu.

6: Các ivars triển khai có phần mới. Đó là một nơi tốt để đặt các ivars riêng tư, vì bạn chỉ muốn hiển thị những gì cần thiết trong tiêu đề công khai, nhưng các lớp con không kế thừa chúng AFAIK.

3 & 7: Phương thức công khai và khai báo thuộc tính, sau đó triển khai.

5: Giao diện riêng tư. Tôi luôn sử dụng các giao diện riêng tư bất cứ khi nào có thể để giữ mọi thứ sạch sẽ và tạo ra một loại hiệu ứng hộp đen. Nếu họ không cần biết về nó, hãy đặt nó ở đó. Mình cũng làm vì dễ đọc, không biết có lý do nào khác không.


1
Đừng nghĩ rằng bạn đã làm hỏng bất cứ điều gì :) Một vài nhận xét - # 1 & # 4 đặc biệt với # 4 bạn thường thấy các biến lưu trữ tĩnh. # 1 thường thì bạn sẽ thấy bộ nhớ bên ngoài được chỉ định và sau đó là bộ nhớ thực được phân bổ trong # 4. # 2) thường chỉ khi một lớp con cần nó vì bất kỳ lý do gì. # 5 không cần thiết để chuyển tiếp các phương thức khai báo riêng tư nữa.
Carl Veazey

Vâng, tôi vừa tự kiểm tra tờ khai chuyển tiếp. Nó được sử dụng để đưa ra cảnh báo nếu một phương thức private được gọi là phương thức khác được xác định sau nó mà không có khai báo chuyển tiếp, phải không? Tôi hơi ngạc nhiên khi nó không cảnh báo tôi.
Metabble

Vâng, nó là một phần mới của trình biên dịch. Gần đây, họ thực sự đã có nhiều tiến bộ.
Carl Veazey

6

Đây là một ví dụ về tất cả các loại biến được khai báo trong Objective-C. Tên biến cho biết quyền truy cập của nó.

Tệp: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Tệp: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Lưu ý rằng các biến iNotVosystem không hiển thị từ bất kỳ lớp nào khác. Đây là một vấn đề về khả năng hiển thị, vì vậy hãy khai báo chúng với @propertyhoặc@public không thay đổi nó.

Bên trong một hàm tạo, bạn nên truy cập các biến được khai báo bằng @propertycách sử dụng dấu gạch dướiself để tránh các tác dụng phụ.

Hãy thử truy cập các biến.

Tệp: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Tệp: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Chúng tôi vẫn có thể truy cập các biến không hiển thị bằng thời gian chạy.

Tập tin: Cow.m (phần 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Hãy thử truy cập các biến không hiển thị.

Tệp: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Bản in này

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Lưu ý rằng tôi có thể truy cập ivar hỗ trợ _iNotVisible2là riêng tư đối với lớp con. Trong Objective-C, tất cả các biến có thể được đọc hoặc đặt, ngay cả những biến được đánh dấu @private, không có ngoại lệ.

Tôi đã không bao gồm các đối tượng liên quan hoặc các biến C vì chúng là các loài chim khác nhau. Đối với biến C, bất kỳ biến nào được định nghĩa bên ngoài @interface X{}hoặc @implementation X{}là biến C có phạm vi tệp và lưu trữ tĩnh.

Tôi đã không thảo luận về các thuộc tính quản lý bộ nhớ, hoặc các thuộc tính readonly / readwrite, getter / setter.

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.