关于单例模式(Singleton),我们可能并不陌生。今天就简单介绍一下iOS的单例模式(Singleton),并且总结一下使用单例模式的时候需要注意的一些问题。
单例模式是一个类在系统中只有一个实例对象。通过全局的一个入口点对这个实例对象进行访问。在iOS开发中,单例模式是非常有用的一种设计模式。
下面的链接是关于单例模式的详细介绍:http://en.wikipedia.org/wiki/Singleton_pattern
iOS SDK中也有许多类使用了单例模式,例如,UIApplication:当程序启动的时候,会调用UIApplicationMain方法,在该方法中,会实例化一个UIApplication对象,之后在程序中的任意地方调用sharedApplication方法都将返回一个与当前应用程序相关的UIApplication实例(UIApplicationMain方法中创建的UIApplication单例)。
什么时候使用单例模式?在程序中,单例模式经常用于只希望一个类只有一个实例,而不运行一个类还有两个以上的实例。当然,在iOS SDK中,根据特定的需求,有些类不仅提供了单例访问的接口,还为开发者提供了实例化一个新的对象接口,例如,NSFileManager可以通过defaultManager方法返回相同的一个NSFileManager对象。如果需要新的一个NSFileManager实例对象,可以通过init方法。
单例模式的实现方法:
单例模式官方文档推荐的有两种实现方法:
第一种如下:
+ (Singleton *)instance { if (!instance) { instance = [[super allocWithZone:NULL] init]; } return instance; } + (id)allocWithZone:(NSZone *)zone { return [[self instance] retain]; }
第二种如下:
+ (MySingleton *)sharedInstance { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [[MySingleton alloc] init]; } } return sharedInstance; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [super allocWithZone:zone]; return sharedInstance; // assignment and return on first allocation } } return nil; // on subsequent allocation attempts return nil }
但是在我们的工程中,有很多单例模式,是直接只写了下面的方法:
@implementation Singleton static Singleton *instance = nil; + (Singleton *)sharedInstance { @synchronized(self) { if(!instance) { instance = [[Singleton alloc] init]; } } return instance; } @end
上面这种做法其实是很不安全的。在实现单例模式的时候,我们需要实现以下几个方法:
// 当第一次使用这个单例时,会调用这个init方法。 - (id)init { self = [super init]; if (self) { // 通常在这里做一些相关的初始化任务 } return self; } // 这个dealloc方法永远都不会被调用--因为在程序的生命周期内容,该单例一直都存在。(所以该方法可以不用实现) -(void)dealloc { [super dealloc]; } // 通过返回当前的sharedInstance实例,就能防止实例化一个新的对象。 //在实现单例模式的时候,这个方法最好还是要实现,因为如果有其他人试图不通过sharedInstance方式而是通过alloc方式去实现一个对象的时候,可能会造成多个实例被创建。而重写allocWithZone方法,可以用来保证其他人直接使用alloc和init试图获得一个新实力的时候不产生一个新实例。 + (id)allocWithZone:(NSZone*)zone { return [[self sharedInstance] retain];//或者 return [self instance]; } // 同样,不希望生成单例的多个拷贝。 - (id)copyWithZone:(NSZone *)zone { return self; } // 什么也不做——该单例并不需要一个引用计数(retain counter) - (id)retain { return self; } // 替换掉引用计数——这样就永远都不会release这个单例。 - (NSUInteger)retainCount { return NSUIntegerMax; } // 该方法是空的——不希望用户release掉这个对象。 - (oneway void)release { } //除了返回单例外,什么也不做。 - (id)autorelease { return self; }
以上的这些方法,我们在实现单例模式的时候,最好还是要实现一下,可以避免单例使用不当带来的一些问题。
以上是单例模式需要注意的一些内容。下面简单介绍一下NSZone相关的知识。
我们在alloc或者copy一个对象的时候,经常会看到如下方法:
+(id) allocWithZone:(NSZone *)zone + (id)copyWithZone:(NSZone *)zone
如果我们实现一下这两个方法,会发现,我们在alloc或者copy的时候,会调用以上方法。所以在上文,我们为了防止单例类被重复创建,重载了上面两个方法。
所以最开始我以为zone是一个对象,但是这种想法是错误的!zone不是一个对象!它是一个难懂的C结构,被用于记录关于内存处理(管理)一系列对象的信息。
NSZone 是苹果对内存分配和释放的优化方式。NSZone不是一个对象;它是一个难懂的C结构,它被用于纪录关于内存处理(管理)一系列对象的信息。
你几乎不需要担忧你自己的应用(applications)是怎样管理你自己的空间(zones)的;Cocoa透明地管理它。默认的NSZone在程序启动和所有对象被分配时创建。所以你为什么想要去用你自己的NSZone呢?
如果你大量分配数百个小对象,事实上你会发现你花费精力来为他们分配内存是有意义的。因为这种标准的(默认的)空间会被一直使用,它会变得斑驳起来;释放对象的过程会给整个内存留下令人尴尬的空隙。标准空间的分配器(allocator)也知道知道这一点,所以它尝试着优先去使用被用户释放的内存,去填补这些空隙,但是这种方式只有在空间(zone)变得很大时才有明显效果。
如果你想为大量对象分配内存,然后,你可以创建你自己的空间(zone)并且告诉它不用去为了为新对象分配内存而去查找那些空隙。分配器现在能够每次跳到内存分配的末尾为你的新对象分配内存,能起到不错的效果。
另外,分配器也能为你节省时间,当分配器向操作系统请求更多内存时,分配器去查找哪块空间什么时候被填满,需要花费不少时间。一种更快的时间是一次去请求一大块内存,你也能告诉你的NSZone在这儿做什么。
NSZone也能节省你释放内存的时间。它有方法释放大量分配的内存,而不打扰释放器(deallocators)。如果用一个集合(set)包含一系列对象,这样能够节省时间,你可以一次释放它们而不用去乏味地一个个释放它们。