iOS-正确单例的写法

作者: 枫叶无处漂泊 | 来源:发表于2019-03-07 17:33 被阅读0次

    前言

    单例应该是iOS中很简单的设计模式,写个单例很简单很方便。网上例子也很多,大家也是基本上copy下来就可以了,但是要知其所以然这个问题的文章就很少。所以我在这写一下好的单例,以及为什么这样么写。

    创建单例的几种方式

    一、单线程模式单例

        + (instancetype)sharedInsance{
            static Singleton *singleton = nil;
            if (!singleton ) {
                singleton = [[Singleton alloc] init];
            }
            return singleton;
        }
    
    • 单线程单例只有在单个线程使用的情况下实用,在多线程的情况下,会产生线程不安全的情况。
    • 我们还需要把alloc方法变为私有方法才行,严格的单例是不允许再创建其他实例的,而alloc方法可以在外部任意生成实例
    • 两条线程里同时调用sharedLoadData方法,没有锁的话可能会产生两个singleton实例,这样单例就失去意义了。

    二、多线程加锁单例

    + (instancetype)sharedInsance {
        static Singleton *singleton;
        @synchronized (self) {
            if (!singleton) {
                singleton = [[Singleton alloc] init];
            }
        }
        return singleton;
    }
    
    • 加锁以后,当多个线程同时调用shareInstance时,由于@synchronized已经加锁,只能有一个线程创建singleton实例。
    • 只有在singleton未创建时,加锁才是必要的。如果singleton已经创建,这个时候还加锁的话,会影响性能

    三、多线程加锁单例

        + (instancetype)sharedLoadData {
            static Singleton *singleton = nil;
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                singleton = [[Singleton alloc] init];
            });
        return singleton;
        }
    
    • dispatch_once 无论使用多线程还是单线程,都只执行一次
    • GCD创建单例不仅可以解决多条线程的线程安全问题,也能保证性能,是官方推荐的方式。
    • dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
      • 当onceToken=0时,线程执行dispatch_once的block中代码
      • 当onceToken=-1时,线程跳过dispatch_once的block中代码不执行
      • 当onceToken为其他值时,线程被阻塞,等待onceToken值改变

    dispatch_once执行的流程

    • 当线程调用shareInstance,此时onceToken = 0,调用block中的代码。 此时onceToken的值变为140734537148864。
    • 当其他线程再调用shareInstance方法时,onceToken的值已经是140734537148864了,线程阻塞。
    • 当block线程执行完block之后,onceToken变为-1.其他线程不再阻塞,跳过block。
    • 下次再调用shareInstance时,block已经为-1.直接跳过block。

    单例的健壮性

    要是自己用的话,直接用shareInstance方法创建没啥问题,但是如果同组或者别人没注意用alloc创建、或者别人不小心使用copy、mutableCopy就可能产生两个实例,也就不存在单例。健壮性就是要保持怎么创建就这个实力,就返回位子的内存地址。

    static Singleton* _instance = nil;
    + (instancetype)shareInstance {
        static dispatch_once_t onceToken ;
        dispatch_once(&onceToken, ^{
            _instance = [[super allocWithZone:NULL] init] ;
            //不是使用alloc方法,而是调用[[super allocWithZone:NULL] init] 
            //已经重载allocWithZone基本的对象分配方法,所以要借用父类(NSObject)的功能来帮助出处理底层内存分配的杂物
        }) ;
        return _instance ;
        }
     
     //用alloc返回也是唯一实例
    +(id) allocWithZone:(struct _NSZone *)zone {
        return [Singleton shareInstance] ;
    }
    //对对象使用copy也是返回唯一实例
    -(id)copyWithZone:(NSZone *)zone {
        return [Singleton shareInstance] ;//return _instance;
    }
     //对对象使用mutablecopy也是返回唯一实例
    -(id)mutableCopyWithZone:(NSZone *)zone {
        return [Singleton shareInstance] ;
    }
    @end    
    

    上面代码注意点:

    • 当static关键字修饰局部变量时,只会初始化一次且在程序中只有一份内存
    • copyWithZone mutablecopyWithZone 这个类遵守<NSCopying,NSMutableCopying>协议
    • 如果_instance = [self alloc] init];创建的话,将会和-(id) allocWithZone:(struct _NSZone *)zone产生死锁。 dispatch_once中的onceToken线程被阻塞,等待onceToken值改变。
    • 当用alloc创建对象、以及对对象进行copy mutableCopy也是返回唯一实例

    结尾

    写一个健壮的单例,知其所以然,才能更好的理解的单例。大家加油!!

    相关文章

      网友评论

        本文标题:iOS-正确单例的写法

        本文链接:https://www.haomeiwen.com/subject/hnrhpqtx.html