Як я можу оголосити властивості рівня класу в Objective-C?


205

Можливо, це очевидно, але я не знаю, як оголосити властивості класу в Objective-C.

Мені потрібно кешувати словник за кожним класом і цікавитись, як його розмістити в класі.

Відповіді:


190

властивості мають конкретне значення в Objective-C, але я думаю, ви маєте на увазі щось еквівалентне статичній змінній? Наприклад, лише один екземпляр для всіх типів Foo?

Для оголошення функцій класу в Objective-C ви використовуєте префікс + замість - щоб ваша реалізація виглядала приблизно так:

// Foo.h
@interface Foo {
}

+ (NSDictionary *)dictionary;

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

23
Чи це правильно? Чи не встановити fooDict на нуль, оскільки перший рядок методу словника завжди призводить до того, що словник буде відтворюватися кожен раз?
PapillonUK


59
Лінійний статичний NSDictionary * fooDict = nil; буде виконаний лише один раз! Навіть у методі, який називається кілька разів, декларація (а також у цьому прикладі ініціалізація) з ключовим словом статичний буде ігноруватися, якщо статична змінна існує з цим іменем.
Бінаріан

3
@ BenC.R.Leggiero Так, абсолютно. .Синтаксис -accessor не прив'язаний до властивостей в Objective-C, це просто скомпільований ярлик для будь-якого методу , який повертає то , що без прийняття будь - яких Арга. У цьому випадку я вважаю за краще - я особисто віддаю перевагу .синтаксису для будь-якого використання, коли клієнтський код має намір отримати щось, а не виконувати дію (навіть якщо код реалізації може щось створити один раз або виконувати дії з побічними ефектами) . .Синтаксис великого використання також призводить до більш читабельного коду: наявність […]s означає, що щось істотне робиться, коли .замість них використовується синтаксис.
Сліпп Д. Томпсон

4
Подивіться відповідь Alex Ноласько, в властивості класу доступні з моменту випуску Xcode 8: stackoverflow.com/a/37849467/6666611
n3wbie

112

Я використовую це рішення:

@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

І я вважав це надзвичайно корисним як заміна шаблону Singleton.

Щоб скористатись цим, просто отримайте доступ до своїх даних за допомогою крапки:

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

3
Це дійсно круто. Але що на землі selfозначає метод класу?
Тодд Леман

8
@ToddLehman self- це об'єкт, який отримав повідомлення . А оскільки заняття також є об’єктами , то в цьому випадку selfозначаєModel
spooki

6
Навіщо потрібно геттеру @synchronized?
Метт Кантор

1
Це насправді досить круто, що це працює. Щоб ви могли виправити свої власні властивості на рівні класу, які працюють точно як реальні речі. Я думаю, що синхронізація "self" - еквівалент використання "atomic" у вашій декларації властивостей, і її можна опустити, якщо ви хочете "неатомічної" версії? Крім того, я б розглядав можливість іменування резервних змінних "_", як за замовчуванням, яблуко, а також покращує читабельність, оскільки повернення / встановлення self.value від getter / setter викликає нескінченну рекурсію. На мою думку, це правильна відповідь.
Пітер Сегерблом

3
Це круто, але ... 10 рядків коду просто для створення 1 статичного члена? що хак. Apple повинна просто зробити цю особливість.
Джон Генкель

92

Як видно з WWDC 2016 / XCode 8 ( що нового в сесії LLVM @ 5: 05). Властивості класу можна оголосити наступним чином

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

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

Зауважте, що властивості класу ніколи не синтезуються

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

8
Можливо, слід чітко сказати, що це лише цукор для декларацій постачальника. Як ви зазначали, властивість не синтезується: staticзмінна ( ) все одно повинна бути оголошена та використана, а методи реалізовані явно, як і раніше. Синтаксис точок вже працював і раніше. Загалом, це звучить як більша справа, ніж насправді.
jscs

6
Велика справа полягає в тому, що ви можете отримати доступ до одиночних кодів з коду Swift без використання (), а суфікс типу видаляється умовно. Наприклад, XYZMyClass.shared (Swift 3) замість XYZMyClass.sharedMyClass ()
Райан

Це не виглядає безпечно. Якби це могло бути вимкнено з різних потоків у вашому коді, я б переконався, що ви обробляєте потенційну умову гонки, яку це створить.
усмішкаБот

Приємний чистий інтерфейс для споживання занять, але це все-таки багато роботи. Спробував це статичним блоком, і це було не дуже весело. Насправді просто використовувати статику було набагато простіше.
Департамент Б

1
реалізація може бути легко покращена для безпечної для потоків поведінки "одиночки", використовуючи маркер dispatch_once - але в іншому випадку рішення правильно демонструє відповідь
Motti Shneor

63

Якщо ви шукаєте еквівалент рівня класу @property, тоді відповідь "такого не існує". Але пам’ятайте @property, у будь-якому випадку це лише синтаксичний цукор; він просто створює відповідно названі об'єктні методи.

Ви хочете створити методи класу, які мають доступ до статичних змінних, які, як говорили інші, мають лише дещо інший синтаксис.


Навіть властивості думки є синтаксичними. І все-таки було б добре використовувати синтаксис крапок для таких речей, як MyClass.class замість [MyClass class].
Zaky German

4
@ZakyGerman Ви можете! UIDevice.currentDevice.identifierForVendorпрацює для мене.
тс.

1
@tc. Дякую! Наповнення дурно зараз. Чомусь я був переконаний, що намагався це раніше, але це не вийшло. Це нова функція випадково?
Zaky German

1
@ZakyGerman Він працював принаймні рік-два для класових методів. Я вважаю, що це завжди працювало, наприклад, для методів, якщо очікувані типи методів getter / setter.
тс.

21

Ось безпечний спосіб зробити це:

// 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;
}

Ці редагування гарантують, що fooDict створюється лише один раз.

З документації Apple : "dispatch_once - виконує об'єкт блоку один раз і лише один раз протягом життя програми."


3
Хіба код dispatch_once не має значення, оскільки статичний NSDictionary може бути ініціалізований у першому рядку методу словника + (NSDictionary *), а оскільки він статичний, він буде ініціалізований лише один раз?
jcpennypincher

@jcpennypincher Спроба ініціалізувати словник на тій же лінії , як його статичну заяву дає наступне повідомлення про помилку компілятора: Initializer element is not a compile-time constant.
George WS

@GeorgeWS Ви отримуєте цю помилку лише тому, що ви намагаєтеся ініціалізувати її до результату функції (alloc та init - це функції). Якщо ініціалізувати його на нуль, а потім додати if (obj == nil) та ініціалізувати там, вам буде добре.
Роб

1
Роб, це не безпечно. Код, представлений тут, найкраще.
Ян Оллман

Найкраще мені подобається ця відповідь, оскільки вона є повною та безпечною для потоків - проте, вона лише краще справляється з реалізацією, тоді як питання ОП стосувалося декларування властивостей класу - не про їх реалізацію (що не має нічого спільного з реалізацією). Все одно, дякую.
Motti Shneor

11

Станом на Xcode 8 Objective-C тепер підтримує властивості класу:

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

Оскільки властивості класу ніколи не синтезуються, вам потрібно написати власну реалізацію.

@implementation MyClass
static NSUUID*_identifier = nil;

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

Ви отримуєте доступ до властивостей класу, використовуючи звичайний синтаксис крапок у назві класу:

MyClass.identifier;

7

Властивості мають значення лише в об'єктах, а не в класах.

Якщо вам потрібно зберігати щось для всіх об'єктів класу, вам доведеться використовувати глобальну змінну. Ви можете приховати його, оголосивши його staticу файлі реалізації.

Ви також можете розглянути можливість використання конкретних відносин між вашими об'єктами: ви присвоюєте роль майстра певному об'єкту вашого класу та зв'язуєте інші об'єкти з цим майстром. Майстер буде мати словник як просту властивість. Я думаю про дерево, подібне до того, яке використовується для ієрархії подання в додатках какао.

Інший варіант - створити об’єкт виділеного класу, який складається як зі словника вашого класу, так і з набору всіх об'єктів, пов'язаних із цим словником. Це щось на кшталт NSAutoreleasePoolкакао.


7

Починаючи з Xcode 8, ви можете використовувати атрибут властивості класу , на який відповів Бербі.

Однак у реалізації вам потрібно визначити як getter class, так і setter для властивості класу, використовуючи статичну змінну замість iVar.

Зразок.h

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

Зразок м

@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

Якщо у вас є багато властивостей рівня класу, тоді може бути порядковий однотонний візерунок. Щось на зразок цього:

// 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

Тепер перейдіть до таких властивостей на рівні класу, як цей:

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

4
Ця одиночна реалізація не є безпечною для потоків, не використовуйте її
klefevre

1
Звичайно, це не безпечно для потоків, ви перший, хто згадує про безпеку ниток, і за замовчуванням не має сенсу безпека перевантаження ниток у безпечних контекстах, що не стосуються потоку, тобто в одній нитці.
Педро Борхес

Тут було б досить просто використовувати dispatch_once.
Іван Макдональд

PO вимагав відповіді на Деклараційній стороні, а не на реалізацію - і навіть запропонована реалізація є неповною (не є безпечною для потоків).
Motti Shneor

-3

[Спробуйте це рішення просто] Ви можете створити статичну змінну в класі Swift, а потім викликати її з будь-якого класу Objective-C.


1
ОП не запитували, як створити статичну властивість у Swift, це не вирішує його проблеми.
Натан Ф.

А точніше, це вирішило проблему, але не відповіло на запитання. Не впевнений, чи заслуговує це, і проголосує
проти
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.