Objective-C:使用singleton vs. use类作为对象?

时间:2022-09-07 07:25:19

I've been wondering in what cases it is really necessary to adopt the singleton pattern in objective-C (e.g., define a dedicated class and create a single instance), that using the class as an object won't do.

我一直想知道在什么情况下在objective-C中采用单例模式是真的有必要(例如,定义一个专用类并创建一个单独的实例),使用类作为对象是不行的。

Particularly, I'm thinking of the following solution:

特别是,我正在考虑以下解决方案:

  1. Define and use appropriate class methods, instead of instance methods on the singleton instance;
  2. 在单例实例上定义和使用适当的类方法,而不是实例方法;
  3. Use static variables (file-scope globals), instead of instance variables of the singleton instance;
  4. 使用静态变量(文件范围全局变量),而不是单例实例的实例变量;
  5. Use the class object when registering as an observer for notifications, instead of the singleton instance. Although the class object is an objective-C object in its own right (right?), this would require that the notification handler registered be a class method; (is this possible?)
  6. 在注册为通知的观察者而不是单例实例时使用类对象。虽然类对象本身就是一个Objective-C对象(对吗?),但这需要注册的通知处理程序是一个类方法; (这可能吗?)

For example, instead of having a Texture class (model object) and a TextureManager singleton (resource manager), you could have all texture creation/cleanup implemented as class methods and static variables of the same Texture class (factory pattern plus some resource management).

例如,您可以将所有纹理创建/清理实现为类方法和相同Texture类的静态变量(工厂模式加上一些资源管理),而不是使用Texture类(模型对象)和TextureManager单例(资源管理器)。 。

Any thoughts on this design?

对这个设计有什么看法?

EDIT: Now that I think of it, and still in the Texture example above, even if I keep the two classes separate (Texture and TextureManager) I must choose between A. Having the manager be a singleton, and operate it with instance methods, or B. Having the manager be an instanceless, auxiliary class. To clarify:

编辑:现在我想起了它,仍然在上面的Texture示例中,即使我将两个类分开(Texture和TextureManager),我必须在A之间进行选择。让管理器成为单例,并使用实例方法操作它,或B.让经理成为无实体的辅助班。澄清:

Texture* myTexture = [[TextureManager defaultManager] textureWithName:@"TextureName"]; 
// (singleton, client uses instance methods)

versus

Texture* myTexture = [TextureManager textureWithName:@"TextureName"]; 
// (Class standing in for singleton, client uses class methods)

The latter looks more straightforward and less cumbersome/verbose, but I wonder which design is "more correct". Of course, the former allows for more than one TextureManager instance shall the need arise (not in my case).

后者看起来更直接,不那么繁琐/冗长,但我想知道哪种设计“更正确”。当然,前者允许需要多个TextureManager实例(不是在我的情况下)。

3 个解决方案

#1


7  

I have been thinking about the same thing and I think I have an answer for you.

我一直在考虑同样的事情,我想我有一个答案。

It depends on what you need to do with it. Neither is necessarily more "correct".

这取决于你需要做什么。两者都不一定更“正确”。

Read on if you want the details of how I came to my conclusion or scroll down to the tl;dr section.

如果您想了解我如何得出结论或向下滚动到tl; dr部分,请继续阅读。

As you said, it would appear (externally) less cumbersome to access the singleton to have the class manage the singleton for you. Essentially you would do this by replacing the singleton's factory method with an initializer method. Looking at Apple's documentation on this you can see where they show a "shared" method that acts as the factory to produce the singleton upon demand.

正如你所说,访问单例以让类为你管理单例,看起来(外部)不那么麻烦。基本上你可以通过用初始化方法替换singleton的工厂方法来实现。查看Apple关于此的文档,您可以看到它们显示“共享”方法的位置,该方法充当工厂以按需生成单例。

static MyGizmoClass *sharedGizmoManager = nil;

+ (MyGizmoClass*)sharedManager
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    return sharedGizmoManager;
}

Instead of doing this you could replace the method with a void initializer like so:

您可以使用void初始化程序替换该方法,而不是这样做:

+ (void)initializeMyGizmo
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    // whatever else needs to be done to the singleton to initialize it
}

and then ONLY ever use class methods and allow the MyGizmoClass to manage updates to the singleton like [MyGizmoClass setGizmoName:@"Gadget"].

然后只使用类方法并允许MyGizmoClass管理单例的更新,如[MyGizmoClass setGizmoName:@“Gadget”]。

NOTE: In this scenario it would be confusing to someone looking at the .h file to see properties, in which case they may come to the conclusion that they should create an instance of the object themselves, or be able to have access to the singleton in some form or fashion. So if you were to go the route of encapsulating access to the singleton it would not be wise to use public variables.

注意:在这种情况下,对于查看.h文件以查看属性的人来说会很困惑,在这种情况下,他们可能会得出结论,他们应该自己创建对象的实例,或者能够访问单例以某种形式或方式。因此,如果您要使用封装访问单例的路径,那么使用公共变量是不明智的。

To that point:

到那时:

If you do limit access to solely through the class itself you lose any getters and setters or other free things that come along with properties. This means that if MyGizmoClass were to have as part of it's model an NSString *gizmoName you would be forced to create custom getters and setters for this "property" and keep it either as an ivar or property in an interface extension in the .m file (i.e. private) of the singleton class, or as an adjacent static variable.

如果您仅通过课程本身限制访问权限,则会丢失任何getter和setter或其他与属性一起出现的免费内容。这意味着如果MyGizmoClass要将NSString * gizmoName作为其模型的一部分,您将*为此“属性”创建自定义getter和setter,并将其保存为.m文件中接口扩展中的ivar或property单例类(即私有),或作为相邻的静态变量。

So this begs the question (and is what got me pondering in the first place), should we even include the line static MyGizmoClass *sharedGizmoManager = nil; at all or can we nix the internal interface extension altogether and replace any possible ivars or properties that we want to limit access to with static implementations in the implementation?

所以这引出了一个问题(并且首先让我思考的是),我们是否应该包含静态MyGizmoClass * sharedGizmoManager = nil;在所有或者我们可以完全修改内部接口扩展并替换我们想要限制访问的任何可能的ivars或属性与实现中的静态实现?

I answered that already...

我已经回答了......

It depends on what you need to do with it.

这取决于你需要做什么。

tl;dr

First Scenario

If you ever (even the slightest chance) need to subclass your TextureManager or could create multiple instances of it (making it no longer a singleton) it would be better to stick to the regular Apple convention for a singleton.

如果您(甚至是最轻微的机会)需要继承您的TextureManager或者可以创建它的多个实例(使它不再是单例),那么最好坚持单一的常规Apple约定。

This includes multiple "singletons" wherein you might have several TextureManagers preconfigured with different settings.

这包括多个“单例”,其中您可能有几个预配置了不同设置的TextureManagers。

In this case you would use properties as you need them (publicly or privately) as well as ivars. You could also use a mix of ivars and statics but you would still always need to have a static instance of your TextureManager inside of the TextureManager implementation.

在这种情况下,您可以根据需要使用属性(公开或私有)以及ivars。你也可以混合使用ivars和静态,但你仍然需要在TextureManager实现中使用TextureManager的静态实例。

Second Scenario

If you ONLY will ever need ONE instance of the TextureManager and it will run completely standalone with no intermixing further down the line then you could completely remove the static instance of your class within the implementation in the .m file and replace ivars and properties with static variables within that implementation.

如果你只需要一个TextureManager的实例,它将完全独立运行,没有进一步的混合,那么你可以在.m文件的实现中完全删除你的类的静态实例,并用静态替换ivars和属性该实现中的变量。

This can be useful if you are storing off properties or settings in CoreData and only need them for configuration.

如果要在CoreData中存储属性或设置并且仅需要它们进行配置,这将非常有用。

Just remember in this case you will have to create all getters and setters for the static variables and will only be able to access them using class methods (but that's sorta the point).

请记住,在这种情况下,您将必须为静态变量创建所有getter和setter,并且只能使用类方法访问它们(但这很重要)。

Other Interesting Stuff

This answer offers an interesting solution to the question of when and how to call the "initializer" method or create the singleton. This can be used with each scenario to either initialize the singleton in the first scenario, or preload defaults into the class-level statics in the second scenario.

这个答案为何时以及如何调用“初始化器”方法或创建单例提供了一个有趣的解决方案。这可以与每个场景一起使用,以初始化第一个场景中的单例,或者将默认值预加载到第二个场景中的类级静态。

If you want to stick with a static singleton in the implementation you might look at this article to give you a better idea at the true "global scope" of your singleton.

如果你想在实现中坚持使用静态单例,你可以查看本文,以便更好地了解单例的真正“全局范围”。

#2


2  

Yes you can definitely make a Texture class without needing a singleton.

是的,你绝对可以制作一个Texture类而不需要单例。

Singletons probably should not be created and used as an object.

单身人士可能不应该被创建并用作对象。

Singletons can be used for many important things.
I certainly don't know all of the things they can be used for, but i will tell you what i have used them for in the past.

单身人士可以用于许多重要的事情。我当然不知道他们可以用的所有东西,但我会告诉你我过去使用过的东西。

I usually use singletons for level navigation in a game with many levels (like Angry Birds).
By level navigation, i mean... when a player completes a certain level in a game i simply call a class method on the singleton and pass in the level number, then the singleton's class method figures out which level is next (if user presses 'next level' button).

我通常使用单身人士在多级游戏(如愤怒的小鸟)中进行关卡导航。通过关卡导航,我的意思是......当玩家在游戏中完成一定级别时,我只需在单身人士上调用一个类方法并传入关卡编号,然后单例的类方法就可以确定下一个等级(如果用户按下) '下一级'按钮)。

#3


2  

I can help you understand the Singleton class better and when it applies.

我可以帮助您更好地理解Singleton类,何时适用。


Pattern : Singleton

图案:单身人士

Intent : Enforce that a class can only have a single instance, as well as making that instance accessible to any other object.

意图:强制一个类只能有一个实例,并使该实例可以被任何其他对象访问。

Motivation : Sometimes we need to make sure that there exists only a single object of a certain type in our problem domain. Example: A student carries around only a single backpack, which he can fill with books. We would not want to relate him to secondary backpack, with even more books.

动机:有时我们需要确保在我们的问题域中只存在某种类型的单个对象。示例:学生只携带一个背包,可以装满书籍。我们不想把他和二手背包联系在一起,甚至还有更多的书。

Use when :

使用时间:

  • There is need for only a single instance of a class, and that instance must be accessible from different objects within your code.
  • 只需要一个类的单个实例,并且该实例必须可以从代码中的不同对象访问。
  • When you (possibly) need to be able to add more functionality to that class by subclassing it.
  • 当您(可能)需要通过子类化为该类添加更多功能时。

#1


7  

I have been thinking about the same thing and I think I have an answer for you.

我一直在考虑同样的事情,我想我有一个答案。

It depends on what you need to do with it. Neither is necessarily more "correct".

这取决于你需要做什么。两者都不一定更“正确”。

Read on if you want the details of how I came to my conclusion or scroll down to the tl;dr section.

如果您想了解我如何得出结论或向下滚动到tl; dr部分,请继续阅读。

As you said, it would appear (externally) less cumbersome to access the singleton to have the class manage the singleton for you. Essentially you would do this by replacing the singleton's factory method with an initializer method. Looking at Apple's documentation on this you can see where they show a "shared" method that acts as the factory to produce the singleton upon demand.

正如你所说,访问单例以让类为你管理单例,看起来(外部)不那么麻烦。基本上你可以通过用初始化方法替换singleton的工厂方法来实现。查看Apple关于此的文档,您可以看到它们显示“共享”方法的位置,该方法充当工厂以按需生成单例。

static MyGizmoClass *sharedGizmoManager = nil;

+ (MyGizmoClass*)sharedManager
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    return sharedGizmoManager;
}

Instead of doing this you could replace the method with a void initializer like so:

您可以使用void初始化程序替换该方法,而不是这样做:

+ (void)initializeMyGizmo
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    // whatever else needs to be done to the singleton to initialize it
}

and then ONLY ever use class methods and allow the MyGizmoClass to manage updates to the singleton like [MyGizmoClass setGizmoName:@"Gadget"].

然后只使用类方法并允许MyGizmoClass管理单例的更新,如[MyGizmoClass setGizmoName:@“Gadget”]。

NOTE: In this scenario it would be confusing to someone looking at the .h file to see properties, in which case they may come to the conclusion that they should create an instance of the object themselves, or be able to have access to the singleton in some form or fashion. So if you were to go the route of encapsulating access to the singleton it would not be wise to use public variables.

注意:在这种情况下,对于查看.h文件以查看属性的人来说会很困惑,在这种情况下,他们可能会得出结论,他们应该自己创建对象的实例,或者能够访问单例以某种形式或方式。因此,如果您要使用封装访问单例的路径,那么使用公共变量是不明智的。

To that point:

到那时:

If you do limit access to solely through the class itself you lose any getters and setters or other free things that come along with properties. This means that if MyGizmoClass were to have as part of it's model an NSString *gizmoName you would be forced to create custom getters and setters for this "property" and keep it either as an ivar or property in an interface extension in the .m file (i.e. private) of the singleton class, or as an adjacent static variable.

如果您仅通过课程本身限制访问权限,则会丢失任何getter和setter或其他与属性一起出现的免费内容。这意味着如果MyGizmoClass要将NSString * gizmoName作为其模型的一部分,您将*为此“属性”创建自定义getter和setter,并将其保存为.m文件中接口扩展中的ivar或property单例类(即私有),或作为相邻的静态变量。

So this begs the question (and is what got me pondering in the first place), should we even include the line static MyGizmoClass *sharedGizmoManager = nil; at all or can we nix the internal interface extension altogether and replace any possible ivars or properties that we want to limit access to with static implementations in the implementation?

所以这引出了一个问题(并且首先让我思考的是),我们是否应该包含静态MyGizmoClass * sharedGizmoManager = nil;在所有或者我们可以完全修改内部接口扩展并替换我们想要限制访问的任何可能的ivars或属性与实现中的静态实现?

I answered that already...

我已经回答了......

It depends on what you need to do with it.

这取决于你需要做什么。

tl;dr

First Scenario

If you ever (even the slightest chance) need to subclass your TextureManager or could create multiple instances of it (making it no longer a singleton) it would be better to stick to the regular Apple convention for a singleton.

如果您(甚至是最轻微的机会)需要继承您的TextureManager或者可以创建它的多个实例(使它不再是单例),那么最好坚持单一的常规Apple约定。

This includes multiple "singletons" wherein you might have several TextureManagers preconfigured with different settings.

这包括多个“单例”,其中您可能有几个预配置了不同设置的TextureManagers。

In this case you would use properties as you need them (publicly or privately) as well as ivars. You could also use a mix of ivars and statics but you would still always need to have a static instance of your TextureManager inside of the TextureManager implementation.

在这种情况下,您可以根据需要使用属性(公开或私有)以及ivars。你也可以混合使用ivars和静态,但你仍然需要在TextureManager实现中使用TextureManager的静态实例。

Second Scenario

If you ONLY will ever need ONE instance of the TextureManager and it will run completely standalone with no intermixing further down the line then you could completely remove the static instance of your class within the implementation in the .m file and replace ivars and properties with static variables within that implementation.

如果你只需要一个TextureManager的实例,它将完全独立运行,没有进一步的混合,那么你可以在.m文件的实现中完全删除你的类的静态实例,并用静态替换ivars和属性该实现中的变量。

This can be useful if you are storing off properties or settings in CoreData and only need them for configuration.

如果要在CoreData中存储属性或设置并且仅需要它们进行配置,这将非常有用。

Just remember in this case you will have to create all getters and setters for the static variables and will only be able to access them using class methods (but that's sorta the point).

请记住,在这种情况下,您将必须为静态变量创建所有getter和setter,并且只能使用类方法访问它们(但这很重要)。

Other Interesting Stuff

This answer offers an interesting solution to the question of when and how to call the "initializer" method or create the singleton. This can be used with each scenario to either initialize the singleton in the first scenario, or preload defaults into the class-level statics in the second scenario.

这个答案为何时以及如何调用“初始化器”方法或创建单例提供了一个有趣的解决方案。这可以与每个场景一起使用,以初始化第一个场景中的单例,或者将默认值预加载到第二个场景中的类级静态。

If you want to stick with a static singleton in the implementation you might look at this article to give you a better idea at the true "global scope" of your singleton.

如果你想在实现中坚持使用静态单例,你可以查看本文,以便更好地了解单例的真正“全局范围”。

#2


2  

Yes you can definitely make a Texture class without needing a singleton.

是的,你绝对可以制作一个Texture类而不需要单例。

Singletons probably should not be created and used as an object.

单身人士可能不应该被创建并用作对象。

Singletons can be used for many important things.
I certainly don't know all of the things they can be used for, but i will tell you what i have used them for in the past.

单身人士可以用于许多重要的事情。我当然不知道他们可以用的所有东西,但我会告诉你我过去使用过的东西。

I usually use singletons for level navigation in a game with many levels (like Angry Birds).
By level navigation, i mean... when a player completes a certain level in a game i simply call a class method on the singleton and pass in the level number, then the singleton's class method figures out which level is next (if user presses 'next level' button).

我通常使用单身人士在多级游戏(如愤怒的小鸟)中进行关卡导航。通过关卡导航,我的意思是......当玩家在游戏中完成一定级别时,我只需在单身人士上调用一个类方法并传入关卡编号,然后单例的类方法就可以确定下一个等级(如果用户按下) '下一级'按钮)。

#3


2  

I can help you understand the Singleton class better and when it applies.

我可以帮助您更好地理解Singleton类,何时适用。


Pattern : Singleton

图案:单身人士

Intent : Enforce that a class can only have a single instance, as well as making that instance accessible to any other object.

意图:强制一个类只能有一个实例,并使该实例可以被任何其他对象访问。

Motivation : Sometimes we need to make sure that there exists only a single object of a certain type in our problem domain. Example: A student carries around only a single backpack, which he can fill with books. We would not want to relate him to secondary backpack, with even more books.

动机:有时我们需要确保在我们的问题域中只存在某种类型的单个对象。示例:学生只携带一个背包,可以装满书籍。我们不想把他和二手背包联系在一起,甚至还有更多的书。

Use when :

使用时间:

  • There is need for only a single instance of a class, and that instance must be accessible from different objects within your code.
  • 只需要一个类的单个实例,并且该实例必须可以从代码中的不同对象访问。
  • When you (possibly) need to be able to add more functionality to that class by subclassing it.
  • 当您(可能)需要通过子类化为该类添加更多功能时。