Làm cách nào để khai báo các thuộc tính cấp lớp trong Objective-C?


205

Có lẽ điều này là hiển nhiên, nhưng tôi không biết cách khai báo các thuộc tính lớp trong Objective-C.

Tôi cần lưu trữ mỗi từ điển một lớp và tự hỏi làm thế nào để đặt nó trong lớp.

Câu trả lời:


190

các thuộc tính có một ý nghĩa cụ thể trong Objective-C, nhưng tôi nghĩ bạn có ý nghĩa gì đó tương đương với một biến tĩnh? Ví dụ: chỉ có một ví dụ cho tất cả các loại Foo?

Để khai báo các hàm lớp trong Objective-C, bạn sử dụng tiền tố + thay vì - vì vậy việc triển khai của bạn sẽ trông giống như:

// Foo.h
@interface Foo {
}

+ (NSDictionary *)dictionary;

// Foo.m
+ (NSDictionary *)dictionary {
  static NSDictionary *fooDict = nil;
  if (fooDict == nil) {
    // create dict
  }
  return fooDict;
}

23
Thê nay đung không? Không đặt fooDict thành nil là dòng đầu tiên của phương thức từ điển luôn dẫn đến việc từ điển được tạo lại mỗi lần?
PapillonUK


59
Dòng NSDadata tĩnh * fooDict = nil; sẽ chỉ được thực hiện một lần! Ngay cả trong một phương thức được gọi là nhiều lần, khai báo (và trong ví dụ này là khởi tạo) với từ khóa tĩnh sẽ bị bỏ qua nếu một biến tĩnh tồn tại với tên này.
Binary

3
@ BenC.R.Leggiero Vâng, hoàn toàn. Các .cú pháp -accessor không gắn với tài sản trong Objective-C, nó chỉ là một biên soạn trong phím tắt cho bất kỳ phương pháp mà lợi nhuận một cái gì đó mà không cần dùng bất kỳ args. Trong trường hợp này, tôi thích nó hơn. Cá nhân tôi thích .cú pháp cho bất kỳ việc sử dụng nào trong đó mã máy khách dự định nhận một cái gì đó, không thực hiện một hành động (ngay cả khi mã thực thi có thể tạo ra một cái gì đó một lần hoặc thực hiện các hành động tác dụng phụ) . .Cú pháp sử dụng nhiều cũng dẫn đến mã dễ đọc hơn: sự hiện diện của […]s có nghĩa là một cái gì đó quan trọng đang được thực hiện khi tìm nạp sử dụng .cú pháp thay thế.
Slipp D. Thompson

4
Hãy xem câu trả lời của Alex Nolasco, các thuộc tính lớp có sẵn kể từ khi phát hành Xcode 8: stackoverflow.com/a/37849467/6666611
n3wbie

112

Tôi đang sử dụng giải pháp này:

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

Và tôi thấy nó cực kỳ hữu ích khi thay thế mẫu Singleton.

Để sử dụng nó, chỉ cần truy cập dữ liệu của bạn bằng ký hiệu dấu chấm:

Model.value = 1;
NSLog(@"%d = value", Model.value);

3
Điều đó thật tuyệt. Nhưng những gì trên trái đất có selfnghĩa là bên trong một phương thức lớp?
Todd Lehman

8
@ToddLehman selfđối tượng nhận được tin nhắn . Và vì các lớp cũng là đối tượng , trong trường hợp này selfcó nghĩa làModel
spooki

6
Tại sao các getter cần phải được @synchronized?
Matt Kantor

1
Điều này thực sự khá tuyệt mà điều này hoạt động. Rằng bạn có thể sử dụng các thuộc tính cấp lớp của riêng bạn, hoạt động chính xác như thực tế. Tôi đoán tự đồng bộ hóa là tương đương với việc sử dụng 'nguyên tử' trong khai báo tài sản của bạn và có thể được bỏ qua nếu bạn muốn phiên bản 'không phải là'? Ngoài ra, tôi sẽ xem xét việc đặt tên các biến sao lưu với '_' là mặc định của apple và cũng cải thiện khả năng đọc kể từ khi trả về / thiết lập self.value từ getter / setter gây ra đệ quy vô hạn. Theo tôi đây là câu trả lời đúng.
Peter Segerblom

3
Thật tuyệt, nhưng ... 10 dòng mã chỉ để tạo 1 thành viên tĩnh? Thật là một hack. Apple chỉ nên biến điều này thành một tính năng.
John Henckel

91

Như đã thấy trong WWDC 2016 / XCode 8 (có gì mới trong phiên LLVM @ 5: 05). Các thuộc tính lớp có thể được khai báo như sau

@interface MyType : NSObject
@property (class) NSString *someString;
@end

NSLog(@"format string %@", MyType.someString);

Lưu ý rằng các thuộc tính lớp không bao giờ được tổng hợp

@implementation
static NSString * _someString;
+ (NSString *)someString { return _someString; }
+ (void)setSomeString:(NSString *)newString { _someString = newString; }
@end

7
Có lẽ nên làm rõ rằng đây chỉ là đường cho các tuyên bố của người truy cập. Như bạn đã lưu ý, thuộc tính không được tổng hợp: một staticbiến ( ) vẫn phải được khai báo và sử dụng và các phương thức được triển khai rõ ràng, giống như trước đây. Cú pháp chấm đã làm việc trước đây, quá. Trong tất cả, điều này nghe có vẻ như một thỏa thuận lớn hơn thực tế.
jscs

5
Vấn đề lớn là điều này có nghĩa là bạn có thể truy cập các singletons từ mã Swift mà không cần sử dụng () và hậu tố loại được loại bỏ theo quy ước. Ví dụ: XYZMyClass. Shared (Swift 3) thay vì XYZMyClass. SharedMyClass ()
Ryan

Điều này không có vẻ an toàn chủ đề. Nếu điều này có thể bị thay đổi từ các luồng khác nhau trong mã của bạn, tôi sẽ đảm bảo bạn xử lý điều kiện cuộc đua tiềm năng mà điều này sẽ tạo ra.
cườiBot

Một giao diện sạch đẹp để tiêu thụ các lớp nhưng nó vẫn còn rất nhiều công việc. Đã thử điều này với một khối tĩnh và nó không vui lắm. Trong thực tế, chỉ cần sử dụng tĩnh là dễ dàng hơn nhiều.
Departamento B

1
việc triển khai có thể dễ dàng được tăng cường cho hành vi "đơn lẻ" an toàn theo luồng, sử dụng mã thông báo
Clark_once

63

Nếu bạn đang tìm kiếm tương đương cấp lớp @property, thì câu trả lời là "không có điều đó". Nhưng hãy nhớ,@property , dù sao cũng chỉ là cú pháp; nó chỉ tạo ra các phương thức đối tượng được đặt tên thích hợp.

Bạn muốn tạo các phương thức lớp truy cập các biến tĩnh, như những người khác đã nói, chỉ có một cú pháp hơi khác nhau.


Ngay cả các thuộc tính suy nghĩ là cú pháp Nó vẫn sẽ rất tuyệt khi có thể sử dụng cú pháp dấu chấm cho các công cụ như MyClass. Class thay vì [MyClass class].
Zaky Đức

4
@ZakyGerman Bạn có thể! UIDevice.currentDevice.identifierForVendorlàm việc cho tôi
tc.

1
@tc. Cảm ơn bạn! Điền vào ngớ ngẩn bây giờ. Vì một số lý do, tôi đã bị thuyết phục rằng tôi đã thử điều đó trong quá khứ nhưng nó không hoạt động. Đó có phải là một tính năng mới bởi bất kỳ cơ hội?
Zaky Đức

1
@ZakyGerman Nó đã hoạt động ít nhất một hoặc hai năm cho các phương thức lớp. Tôi tin rằng nó đã luôn làm việc cho các phương thức ví dụ nếu các phương thức getter / setter có các loại dự kiến.
tc.

21

Đây là một cách an toàn để làm điều đó:

// Foo.h
@interface Foo {
}

+(NSDictionary*) dictionary;

// Foo.m
+(NSDictionary*) dictionary
{
  static NSDictionary* fooDict = nil;

  static dispatch_once_t oncePredicate;

  dispatch_once(&oncePredicate, ^{
        // create dict
    });

  return fooDict;
}

Những chỉnh sửa này đảm bảo rằng fooDict chỉ được tạo một lần.

Từ tài liệu của Apple : "Clark_once - Thực thi một đối tượng khối một lần và chỉ một lần trong suốt vòng đời của một ứng dụng."


3
Không phải mã Clark_once không liên quan vì NSDipedia tĩnh có thể được khởi tạo trên dòng đầu tiên của phương thức từ điển + (NSDipedia *) và vì nó sẽ chỉ được khởi tạo một lần nữa?
jcpennypincher

@jcpennypincher Cố gắng khởi tạo từ điển trên cùng dòng với khai báo tĩnh của nó dẫn đến lỗi trình biên dịch sau : Initializer element is not a compile-time constant.
George WS

@GeorgeWS Bạn chỉ gặp lỗi đó vì bạn đang cố khởi tạo nó thành kết quả của hàm (cấp phát và init là các hàm). Nếu bạn khởi tạo nó thành con số không, sau đó thêm if (obj == nil) và khởi tạo ở đó, bạn sẽ ổn thôi.
Rob

1
Rob, đó không phải là chủ đề an toàn. Mã được trình bày ở đây là tốt nhất.
Ian Ollmann

Tôi thích câu trả lời này nhất, vì nó hoàn chỉnh và luồng an toàn - tuy nhiên, nó chỉ giải quyết tốt hơn khi thực hiện, trong khi câu hỏi của op là về việc khai báo các thuộc tính lớp - không phải là việc thực hiện của chúng (không liên quan gì đến việc triển khai). Dẫu sao cũng xin cảm ơn.
Motti Shneor

11

Kể từ Xcode 8 Objective-C hiện hỗ trợ các thuộc tính lớp:

@interface MyClass : NSObject
@property (class, nonatomic, assign, readonly) NSUUID* identifier;
@end

Vì các thuộc tính lớp không bao giờ được tổng hợp, bạn cần phải viết triển khai của riêng bạn.

@implementation MyClass
static NSUUID*_identifier = nil;

+ (NSUUID *)identifier {
  if (_identifier == nil) {
    _identifier = [[NSUUID alloc] init];
  }
  return _identifier;
}
@end

Bạn truy cập các thuộc tính lớp bằng cú pháp dấu chấm bình thường trên tên lớp:

MyClass.identifier;

7

Các thuộc tính chỉ có giá trị trong các đối tượng, không phải các lớp.

Nếu bạn cần lưu trữ một cái gì đó cho tất cả các đối tượng của một lớp, bạn phải sử dụng một biến toàn cục. Bạn có thể ẩn nó bằng cách khai báo nó statictrong tệp thực hiện.

Bạn cũng có thể xem xét sử dụng các mối quan hệ cụ thể giữa các đối tượng của mình: bạn gán một vai trò chủ cho một đối tượng cụ thể của lớp và liên kết các đối tượng khác với chủ này. Các chủ sẽ giữ từ điển như một tài sản đơn giản. Tôi nghĩ về một cái cây giống như cái cây được sử dụng cho hệ thống phân cấp khung nhìn trong các ứng dụng Cacao.

Một tùy chọn khác là tạo một đối tượng của một lớp chuyên dụng bao gồm cả từ điển 'lớp' của bạn và một tập hợp tất cả các đối tượng liên quan đến từ điển này. Đây là một cái gì đó giống như NSAutoreleasePooltrong ca cao.


7

Bắt đầu từ Xcode 8, bạn có thể sử dụng thuộc tính thuộc tính lớp như được trả lời bởi Berbie.

Tuy nhiên, trong quá trình triển khai, bạn cần định nghĩa cả lớp getter và setter cho thuộc tính lớp bằng cách sử dụng biến tĩnh thay cho iVar.

Mẫu.h

@interface Sample: NSObject
@property (class, retain) Sample *sharedSample;
@end

Mẫu.m

@implementation Sample
static Sample *_sharedSample;
+ ( Sample *)sharedSample {
   if (_sharedSample==nil) {
      [Sample setSharedSample:_sharedSample];
   }
   return _sharedSample;
}

+ (void)setSharedSample:(Sample *)sample {
   _sharedSample = [[Sample alloc]init];
}
@end

2

Nếu bạn có nhiều thuộc tính cấp lớp thì một mẫu đơn có thể theo thứ tự. Một cái gì đó như thế này:

// Foo.h
@interface Foo

+ (Foo *)singleton;

@property 1 ...
@property 2 ...
@property 3 ...

@end

// Foo.m

#import "Foo.h"

@implementation Foo

static Foo *_singleton = nil;

+ (Foo *)singleton {
    if (_singleton == nil) _singleton = [[Foo alloc] init];

    return _singleton;
}

@synthesize property1;
@synthesize property2;
@synthesise property3;

@end

Bây giờ truy cập các thuộc tính cấp lớp của bạn như thế này:

[Foo singleton].property1 = value;
value = [Foo singleton].property2;

4
Việc triển khai đơn lẻ này không an toàn cho chủ đề, đừng sử dụng nó
klefevre

1
Tất nhiên, nó không an toàn cho luồng, bạn là người đầu tiên đề cập đến an toàn của luồng và theo mặc định không có nghĩa là quá tải an toàn của luồng trong bối cảnh không an toàn của luồng, tức là luồng đơn.
Pedro Borges

Nó sẽ khá dễ dàng để sử dụng dispatch_onceở đây.
Ian MacDonald

PO muốn có câu trả lời về phía Tuyên bố, chứ không phải việc triển khai - và ngay cả việc triển khai được đề xuất là không đầy đủ (không an toàn cho chuỗi).
Motti Shneor

-3

[Thử giải pháp này thật đơn giản] Bạn có thể tạo một biến tĩnh trong lớp Swift sau đó gọi nó từ bất kỳ lớp Objective-C nào.


1
OP đã không hỏi làm thế nào để tạo một thuộc tính tĩnh trong Swift, điều này không giải quyết được vấn đề của anh ta.
Nathan F.

Hay đúng hơn, nó đã giải quyết vấn đề, nhưng không trả lời câu hỏi. Không chắc điều đó có xứng đáng và bỏ phiếu không ...
AmitaiB
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.