如何在Objective-C中声明类级别的属性?


205

也许这很明显,但是我不知道如何在Objective-C中声明类属性。

我需要按类缓存字典,并想知道如何将其放入类中。

Answers:


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 2012年


59
行静态NSDictionary * fooDict = nil; 只会执行一次!即使在多次调用的方法中,如果存在带有此名称的静态变量,则将忽略使用关键字static的声明(在本示例中为初始化)。
Binarian

3
@ BenC.R.Leggiero是的,绝对是。.-accessor语法与Objective-C中的属性无关,它只是任何不带任何参数就返回内容的方法的嵌入式快捷方式。在这种情况下,我会更喜欢- .对于客户端代码想要获取某些内容,不执行任何操作(即使实现代码可能仅创建一次或执行副作用操作)的情况,我个人更喜欢使用语法。大量使用.语法还会导致代码更具可读性:[…]s的存在表示,当访存使用.语法代替时,表示正在做有意义的事情。
斯利普·汤普森

4
看看Alex Nolasco的答案,自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
Matt Kantor

1
实际上,这很酷。您可以声明自己的类级别属性,这些属性与真实属性完全一样。我想自我同步等同于在属性声明中使用“原子”,如果需要“非原子”版本,可以将其省略吗?我也考虑将后备变量命名为“ _”,这是苹果默认设置,并且还提高了可读性,因为从getter / setter返回/设置self.value会导致无限递归。我认为这是正确的答案。
彼得·塞格布洛姆

3
这很酷,但是...仅需创建10行代码即可创建1个静态成员?真是可笑。苹果公司应该使它成为一个功能。
约翰·亨克尔

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()
Ryan

这看起来不是线程安全的。如果可以从代码中的不同线程中更改此代码,那么我将确保您处理将要创建的潜在竞争条件。
smileBot

一个不错的干净的界面,可以使用类,但仍然需要大量工作。尝试了一个静态块,这并不有趣。实际上,仅使用静态方法要容易得多。
Departamento B

1
使用dispatch_once令牌,可以轻松增强线程安全的“单例”行为的实现-否则,该解决方案将正确显示答案
Motti Shneor

63

如果您正在寻找的类级等效项@property,那么答案是“没有这样的事情”。但是请记住@property,反正只是语法糖。它只是创建适当命名的对象方法。

您想创建访问静态变量的类方法,就像其他人所说的那样,它们仅具有稍微不同的语法。


甚至认为属性都是语法的。能够将点语法用于MyClass.class之类而不是[MyClass class]仍然很不错。
Zaky German

4
@ZakyGerman可以!UIDevice.currentDevice.identifierForVendor为我工作。
tc。

1
@tc。谢谢!现在装傻。由于某种原因,我坚信我过去曾尝试过,但是没有用。这是一个新功能吗?
Zaky German

1
@ZakyGerman对于类方法,它已经工作了至少一两年。我相信,如果getter / setter方法具有预期的类型,则它始终可用于实例方法。
tc。

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
因为可以在+(NSDictionary *)字典方法的第一行上初始化静态NSDictionary,并且因为它是静态的,所以无论如何它只会被初始化一次,所以dispatch_once代码不是无关紧要的吗?
jcpennypincher 2013年

@jcpennypincher尝试在与其静态声明相同的行上初始化字典,从而产生以下编译器错误:Initializer element is not a compile-time constant
乔治WS

@GeorgeWS您仅会收到该错误,因为您试图将其初始化为函数的结果(alloc和init是函数)。如果将其初始化为nil,然后添加if(obj == nil)并在那里进行初始化,则可以。
罗布

1
Rob,那不是线程安全的。此处提供的代码是最好的。
伊恩·奥尔曼

我最喜欢这个答案,因为它是完整的并且是线程安全的-但是,它只对实现有更好的处理,而op的问题是关于类属性的声明的-与类的实现有关(与实现无关)。不管怎么说,还是要谢谢你。
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在实现文件中声明它来隐藏它。

您还可以考虑使用对象之间的特定关系:您将master角色分配给类的特定对象,并将其他对象链接到该master。母版将字典作为一个简单属性保存。我想到的是一棵像可可应用程序中用于视图层次结构的树。

另一个选择是创建一个专用类的对象,该类既包含“类”字典,又包含与此字典相关的所有对象的集合。这有点像NSAutoreleasePool可可粉。


7

从Xcode 8开始,您可以使用Berbie回答的class属性属性。

但是,在实现中,您需要使用静态变量代替iVar为class属性定义类getter和setter。

样本

@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
当然,它不是线程安全的,您是第一个提到线程安全的人,并且默认情况下在非线程安全的上下文(即单线程)中没有意义的线程安全过载。
Pedro Borges

dispatch_once在这里使用a会很容易。
伊恩·麦克唐纳

PO希望在声明方面得到答案,而不是实现—甚至建议的实现都不完整(不是线程安全的)。
Motti Shneor

-3

[尝试此解决方案很简单]您可以在Swift类中创建一个静态变量,然后从任何Objective-C类中调用它。


1
OP并未询问如何在Swift中创建静态属性,但这并不能解决他的问题。
Nathan F.

或更确切地说,它解决了问题,但没有回答问题。不确定是否值得并投票否决…
AmitaiB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.