您的位置:首页 > 移动开发 > Objective-C

【iOS】Objective-C简约而不简单的单例模式

2015-07-17 14:30 435 查看
http://leyteris.iteye.com/blog/1669198

前些日子在项目中因为误用了单例而导致了一系列问题。原来在objective-c中的单例并没有java或者C#那么简单的实现,这里记录下;

 

问题是这样被发现的,在对于一个UIViewController进行pop时并没有被dealloc,导致了内存泄露。问题代码类似于下面的:

 

C代码  


//LWChatViewController.h  

@interface LWChatViewController : LWTableViewController <LWObjSelectViewDelegate>{  

    UINavigationController *root;  

}  

@property (nonatomic, retain) UINavigationController *root;  

@end  

  

//LWChatViewController.m  

- (void)viewDidLoad  

{  

    [super viewDidLoad];  

    // Do any additional setup after loading the view from its nib.  

    self.root = LWNavigationController;  

}  

 

  这里的LWNavigationController是一个顶级的单例。

问题就出在@property (nonatomic, retain) 这里root居然是一个retain的对象指针,在这里retain一个static的单例将导致内存泄露,MD,这个bug找的我好久。。。

 

 

解决这个问题其实很简单,把retain改为assign就行了,但这样如果在协作编程的时候如果别人不在意这个是单例直接进行常规操作的话会带来很大的问题。

 

 

继续,我们来从根本上解决这个问题。

我们需要重写一些方法:

 

C代码  


- (id)retain  

{  

    return self;  

}  

  

- (NSUInteger) retainCount  

{  

    return NSUIntegerMax;  

}  

  

- (void) release  

{  

    // do nothing  

}  

  

- (id)autorelease  

{  

    return self;  

}  

在retain和autorelease什么都不做只是返回自己,release的时候啥都不做,将retainCount设为UInt的极大值。

 

其次是关于线程安全的实现,这些java都有明确的代码模式:

关于线程安全的单例,这篇外文 http://www.numbergrinder.com/2008/12/patterns-in-objective-c-singleton-pattern/ 有比较详细的解释。

 

 

Java代码  


@implementation Singleton  

static Singleton *instance = nil;  

+ (Singleton *)sharedInstance  {  

   @synchronized(self)   

   {  

        if(!instance) {  

           instance = [[super allocWithZone:NULL] init];  

        }  

    }  

   

    return instance;  

}  

   

@end  

 

嗯,这样就可以实现线程安全的单例了,当然这里也可以用NSLock实例去实现。

 

本类的allocWithZone被改写为:

 

C代码  


+ (id)allocWithZone:(NSZone *)zone  

{  

      return [self sharedInstance];  

}  

  

- (id)copyWithZone:(NSZone *)zone  

{  

      return self;  

}  

 同时深拷贝也直接重载阻止掉多个实例的出现。上面的allocWithZone的重载使得这个单例也能够直接用alloc或是allocWithZone进行初始化,但返回的一如既往是那个static的实例。

dispatch_once
为什么能做到既解决同步多线程问题又不影响性能呢?

下面我们来看看
dispatch_once
的原理:

dispatch_once
主要是根据
onceToken
的值来决定怎么去执行代码。

onceToken
 = 0时,线程执行
dispatch_once
block
中代码
onceToken
 = -1时,线程跳过
dispatch_once
block
中代码不执行
onceToken
为其他值时,线程被线程被阻塞,等待
onceToken
值改变
当线程首先调用
shareInstance
,某一线程要执行
block
中的代码时,首先需要改变
onceToken
的值,再去执行block中的代码。这里
onceToken
的值变为了140734731430192。

这样当其他线程再获取
onceToken
的值时,值已经变为140734731430192。其他线程被阻塞。

block
线程执行完
block
之后。
onceToken
变为-1。其他线程不再阻塞,跳过
block


下次再调用
shareInstance
时,block已经为-1。直接跳过
block


这样
dispatch_once
在首次调用时同步阻塞线程,生成单例之后,不再阻塞线程。
dispatch_once
是创建单例的最优方案

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: