作客户端开发应当时刻考虑多线程问题。我最初是作前端开发的,在这方面考虑得每每不够。谨记。前端
单例的常见写法其实就两种安全
+ (id)sharedInstance { static testClass *sharedInstance = nil; @synchronized(self) { if (!sharedInstance) { sharedInstance = [[self alloc] init]; } } return sharedInstance; }
+ (id)sharedInstance { static testClass *sharedInstance = nil; static dispatch_once_t once; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
dispatch_once的写法更推荐一些。一方面是性能上好一点,另外一方面是语义上更直观。once,执行一次嘛。网络
无论是用锁仍是dispatch_once,本质上都是为了不单例建立过程出现线程安全问题。多线程
更进一步,咱们常常会有懒加载某些属性的写法:框架
- (id<InterfaceEngineA>)engineA { if (_engineA == nil) { _engineA = [EngineA new]; } return _engineA; }
其实跟单例的实现是相似的,这种时候要格外注意线程安全问题。若是存在多线程场景,必定要作好保护性能
- (id<InterfaceEngineA>)engineA { @synchronized(self) { if (_engineA == nil) { _engineA = [EngineA new]; } } return _engineA; }
多线程问题的表现多是各类各样难以预料的。这里我遇到的是,_engineA在多线程场景下小几率被重复建立,其实例1在init时注册了网络层命令字cmd1
的回包,而这个网络层框架的实现是,只接受第一个注册这一命令字的对象。致使实例2注册失败。后面调用实例2发送请求,回包都被实例1接收了。从日志上看,一切都挺正常的。可是下次取数据就是取不到。学习
这个bug第一次提过来的时候,没分析出根本缘由,只在表面上作了保护。结果第二次提过来才真正改掉。spa
丢人呐。仍是要好好学习才是。线程