Как мне объявить свойства уровня класса в Objective-C?

205

Может быть, это очевидно, но я не знаю, как объявить свойства класса в Objective-C.

Мне нужно кэшировать для каждого класса словарь и удивляться, как поместить его в класс.

mamcx
источник

Ответы:

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 равным nil, поскольку первая строка метода словаря всегда приводит к тому, что словарь создается заново каждый раз?
PapillonUK
59
Строка static NSDictionary * fooDict = nil; будет казнен только один раз! Даже в методе, вызываемом несколько раз, объявление (а также в этом примере инициализация) с ключевым словом static будет игнорироваться, если существует статическая переменная с этим именем.
Бинар
3
@ BenC.R.Leggiero Да, абсолютно. .Синтаксис -accessor не привязан к свойствам в Objective-C, это просто скомпилированный ярлык для любого метода , который возвращает то , что без принятия какого - либо арга. В этом случае я бы предпочел это - я лично предпочитаю .синтаксис для любого использования, когда код клиента намеревается получить что-то, а не выполнить действие (даже если код реализации может создать что-то один раз или выполнить побочные действия) . .Синтаксис интенсивного использования также приводит к более читабельному коду: присутствие […]s означает, что происходит что-то существенное, когда выборки используют .вместо этого синтаксис.
Слипп Д. Томпсон
4
Посмотрите на ответ Алекса Ноласко, свойства класса доступны после выпуска Xcode 8: stackoverflow.com/a/37849467/6666611
n3wbie
113

Я использую это решение:

@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);
spooki
источник
3
Это действительно круто. Но что на самом деле selfозначает внутри метода класса?
Тодд Леман
8
@ToddLehman self- это объект, который получил сообщение . И поскольку классы также являются объектами , в данном случае это selfозначаетModel
spooki
6
Зачем нужен геттер @synchronized?
Мэтт Кантор
1
Это на самом деле довольно круто, что это работает. Что вы можете создать свои собственные свойства уровня класса, которые работают точно так же, как и реальные. Я полагаю, что синхронизация self является эквивалентом использования 'atomic' в объявлении свойства и может быть опущена, если вы хотите использовать 'nonatomic' версию? Также я хотел бы рассмотреть возможность именования переменных поддержки с помощью «_», как это принято по умолчанию в Apple, а также улучшить удобочитаемость, поскольку возвращение / установка 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переменная ( ) все равно должна быть объявлена ​​и использована, а методы должны быть реализованы явно, как и прежде. Синтаксис точек уже работал и раньше. В целом, это звучит как нечто большее, чем на самом деле.
ОАО
6
Это означает, что вы можете получить доступ к синглетам из кода Swift без использования (), а суффикс типа удаляется по соглашению. Например, XYZMyClass.shared (Swift 3) вместо XYZMyClass.sharedMyClass ()
Райан
Это не выглядит потокобезопасным. Если бы это могло быть изменено из разных потоков в вашем коде, я бы позаботился о том, чтобы вы справились с потенциальным состоянием гонки, которое это могло бы создать.
smileBot
Хороший чистый интерфейс для использования классов, но это все еще тонна работы. Попробовал это с помощью статического блока, и это было не очень весело. На самом деле просто использовать статику было намного проще.
Departamento B
1
Реализация может быть легко улучшена для поточно-
ориентированного "
63

Если вы ищете эквивалент класса @property, то ответ «такого нет». Но помните @property, в любом случае это только синтаксический сахар; он просто создает методы объекта с соответствующим именем.

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

Джим Пульс
источник
Даже свойства мысли являются синтаксическими. Было бы хорошо иметь возможность использовать точечный синтаксис для таких вещей, как MyClass.class вместо [MyClass class].
Заки Герман
4
@ZakyGerman Вы можете! UIDevice.currentDevice.identifierForVendorработает для меня.
тк.
1
@tc. Спасибо! Наполнение глупо сейчас. По какой-то причине я был убежден, что пробовал это в прошлом, но это не сработало. Это новая особенность, случайно?
Заки Герман
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 - выполняет объект блока один раз и только один раз за время существования приложения».

Quentin
источник
3
Разве код dispatch_once не имеет значения, поскольку статический NSDictionary можно инициализировать в первой строке метода словаря + (NSDictionary *), а поскольку он статический, он все равно будет инициализирован только один раз?
jcpennypincher
@jcpennypincher Попытка инициализировать словарь на той же линии , как его статическое заявление дает следующее сообщение об ошибке компилятора: Initializer element is not a compile-time constant.
Джордж WS
@GeorgeWS Вы получаете эту ошибку только потому, что пытаетесь инициализировать ее как результат функции (alloc и init являются функциями). Если вы инициализируете его значением nil, а затем добавите if (obj == nil) и инициализируете там, все будет в порядке.
Роб
1
Роб, это не безопасно. Код, представленный здесь, является лучшим.
Ян Оллманн
Мне больше нравится этот ответ, потому что он полный и поточно-ориентированный - однако он лучше работает только с реализацией, в то время как вопрос оператора касался объявления свойств класса, а не их реализации (которая не имеет ничего общего с реализацией). Спасибо, в любом случае.
Мотти Шнеор
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;
berbie
источник
7

Свойства имеют значения только в объектах, а не в классах.

Если вам нужно что-то хранить для всех объектов класса, вы должны использовать глобальную переменную. Вы можете скрыть это, объявив это staticв файле реализации.

Вы также можете рассмотреть возможность использования определенных отношений между вашими объектами: вы приписываете роль мастера определенному объекту вашего класса и связываете другие объекты с этим мастером. Мастер будет содержать словарь как простое свойство. Я думаю о дереве, подобном тому, которое используется для иерархии представления в приложениях Какао.

Другой вариант - создать объект выделенного класса, который состоит из вашего словаря 'class' и набора всех объектов, связанных с этим словарем. Это что-то вроде NSAutoreleasePoolкакао.

mouviciel
источник
7

Начиная с Xcode 8, вы можете использовать атрибут свойства класса в ответе Берби.

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

Sample.h

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

Sample.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

Если у вас много свойств уровня класса, то может быть в порядке одноэлементный шаблон. Что-то вроде этого:

// 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
Эта одноэлементная реализация не является поточно-
ориентированной
1
Конечно, это не потокобезопасность, вы первый, кто упоминает о безопасности потоков, и по умолчанию не имеет смысла перегрузку безопасности потоков в не поточно-безопасных контекстах, т.е. в одном потоке.
Педро Борхес
Было бы довольно легко использовать dispatch_onceздесь.
Ян Макдональд
PO хотел получить ответ на стороне декларации, а не на реализации - и даже предлагаемая реализация является неполной (не поточно-ориентированной).
Мотти Шнеор
-3

[Попробуйте это решение, это просто] Вы можете создать статическую переменную в классе Swift, а затем вызвать ее из любого класса Objective-C.

Джавад
источник
1
ОП не спрашивал, как создать статическое свойство в Swift, это не решает его проблему.
Натан Ф.
Вернее, это решило проблему, но не ответило на вопрос. Не уверен, что этого заслуживает, и проголосуйте ...
AmitaiB