阅读更多

0顶
0踩

移动开发

转载新闻 单例在Swift中的正确实现方式

2015-07-29 14:53 by 副主编 mengyidan1988 评论(0) 有5379人浏览
尽管在我之前的博文里我就写过关于管理状态的那些坑,但是有时候我们就是无法避免它们。其中一类管理状态的方式我们耳熟能详 - 单例。但是在Swift中有好几种不同的方式来实现一个单例。到底哪一个才是正确的方式呢?在这边博客里,我将和你好好聊聊单例的历史和在Swift中单例正确的实现方式。

如果你想直接就看在Swift中如何正确地写出单例同时看到证明其“正确性”,你可以直接滚动到这篇博文的底部。

让我们先回忆一下
Swfit源于Objective-C,高于Objective-C。在Objective-C中,我们是这样写单例的:
@interface Kraken : NSObject
@end
 
@implementation Kraken
 
+ (instancetype)sharedInstance {
    static Kraken *sharedInstance = nil;
    static dispatch_once_t onceToken;
 
    dispatch_once(&onceToken, ^{
        sharedInstance = [[Kraken alloc] init];
    });
    return sharedInstance;
}
 
@end

当我们把它写出来之后,就能清楚的看到一个单例的架构,接下来我们把单例的规则理一理,以便更好的理解它:

单例的道义
关于单例,有三件事是你必须要记住的:
  • 单例必须是唯一的,所以它才被称为单例。在一个应用程序的生命周期里,有且只有一个实例存在。单例的存在给我们提供了一个唯一的全局状态。比如我们熟悉的NSNotification,UIApplication和NSUserDefaults都是单例。
  • 为了保持一个单例的唯一性,单例的构造器必须是私有的。这防止其他对象也能创建出单例类的实例。感谢所有帮我指出这点的人。
  • 为了确保单例在应用程序的整个生命周期是唯一的,它就必须是线程安全的。当你一想到并发肯定一阵恶心,简单来说,如果你写单例的方式是错误的,就有可能会有两个线程尝试在同一时间初始化同一个单例,这样你就有潜在的风险得到两个不同的单例。这就意味着我们需要用GCD的dispatch_once来确保初始化单例的代码在运行时只执行一次。

成为独一无二并只在一个地方做初始化并不难。这篇博客接下来要记住的内容就是要单例遵守了更难察觉的dispatch_once的规则。
Swift单例
打从Swift 1.0开始,就有好几种创建单例的方法。在这里,这里和这里都有详细的说明。但谁有空点进去看呢?先来个剧透;一共有四种写法,请让我一一道来:

最丑陋的写法
class TheOneAndOnlyKraken {
    class var sharedInstance: TheOneAndOnlyKraken {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: TheOneAndOnlyKraken? = nil
        }
        dispatch_once(&Static.onceToken) {
            static.instance = TheOneAndOnlyKraken()
        }
        return Static.instance!
    }
}

这种写法其实就是把Objective-C中的写法照搬过来。我觉得奇丑无比因为Swift是一门更加简洁且表达力更强的语言。我们要做的比那些搬运工要好,要比他们好。

结构体写法
class TheOneAndOnlyKraken {
    class var sharedInstance: TheOneAndOnlyKraken {
        struct Static {
            static let instance = TheOneAndOnlyKraken()
        }
        return Static.instance
    }
}

这个在Swift 1.0的时候必须得这么写,因为那个时候,类还不支持全局类变量。而结构体却支持。正因为在全局变量上的局限性,我们不得不这么写。这比直接把Objective-C那一套搬过来要好,但是还不够。好玩的是,在Swift 1.2发布后的几个月后,我还是经常能看到这些的写法,但这个以后再表。

全局变量写法(又名“一句话写法”)
private let sharedKraken = TheOneAndOnlyKraken()
class TheOneAndOnlyKraken {
    class var sharedInstance: TheOneAndOnlyKraken {
        return sharedInstance
    }
}

在Swift 1.2之后,我们拥有了访问控制修饰词和可以使用全局类变量。这意味着我们不用整一个全局变量集群,也可以防止命名空间冲突。这才是我心目中Swift应该有的样子。

这会你可能会问我为什么没有在我们的结构体和全局变量实现中看不到dispatch_once。但其实Apple指出,这两个方法同时满足了我上面提到的dispatch_once条文。下面就截取他们写的Swift Blog中的一段话来证明他们以将dispatch_once整合进去了:
引用

“全局变量(静态成员变量和结构体以及枚举)的延迟构造器在其被第一次访问时会加载,并以dispatch_once的方式启动来确保初始化的原子性。这让你写代码时可以用一种很酷的方式来使用dispatch_once:直接用一个全局变量的构造器去做初始化并用private来修饰。“ — Apple’s Swift Blog

就官方文献来看,Apple就给出这点说明。但这仅意味着对全局变量和结构体/枚举的静态成员我们是有证据的!目前来看,100%不会错的是用一个全局变量将一个单例的初始化包在一个隐含了dispatch_once的延迟构造器中。但是我们的全局类变量怎么办呢?!?!?!?

正确的写法
class TheOneAndOnlyKraken {
    static let sharedInstance = TheOneAndOnlyKraken()
}

为了写这篇文章,我做了一番研究。事实上,之所以写这篇文章,是因为今日在Capital One的一次讨论,一位PR希望在我们所有的应用中用统一的方式来写单例。我们其实知道什么才是单例“正确”的书写方式,但是我们无法自证。如果不旁征博引来证明我们是正确的简直就是徒劳。这是我和缺乏信息的互联网/博文圈之间的较量。每个人都知道如果网上没写那就不是真的。这让我非常难过。



我的搜索达到了互联网的尽头(Google到了第10页)却啥也没得到。难道真就发表一行单例的证明吗?也许有人做了,就是没找到而已。

所以我觉得我自己把所有的初始化构造器的方法都实现一遍,然后通过断点来检查他们。当分析完每个栈帧我所发现的相似点的时候,我终于发现期待已久的东西 - 证据!

直接上图,对了(还有emoji类哦!):



使用全局单例



使用一行单例

第一张图显示了一个全局let实例化。红框表示的地方就是证据。在实际去实例化Karken单例之前,是先由一个swfit_once调用了一个swift_once_block_invoke。加上Apple说他们通过一个dispatch_once的block去延迟初始化一个全局变量,我们现在可以说这就证明了他们所说的。

带着这个信息,我又跟踪了我们既亮眼又简洁的一行单例。正如第二张图所以,两者简直一样!这就足以证明我们的一行单例实现是正确的。现在全世界都清净了。还有,既然这篇文章已经上了互联网,那么它肯定就是真理!

不要忘了INIT的私有化!
@davedelong,Apple的架构师,非常含蓄的给我指出,你必须确保你的inits是私有的。只有这样才能确保你的单例是真正的独一无二,也能防止其他对象通过访问控制机制来创建他们自己的但是是你这个类的单例。因为在Swift中,所有对象的构造器默认都是public,你需要重写你的init让其成为私有的。这并不难实现而且也能确保我们的一行单例的优雅和简洁:
class TheOneAndOnlyKraken {
    static let sharedInstance = TheOneAndOnlyKraken()
    private init() {} // 这就阻止其他对象使用这个类的默认的'()'初始化方法
}

这么做能确保任何类如果尝试通过()初始化方法来初始化TheOneAndOnlyKraken时,编译器都会报错:



你看!这就是完美的,一行实现单例。

结论
呼应一下jtbandestop rated answer to swift singletons on Stack Overflow上精彩的评论,我就无法找到关于使用’let’就能确保线程安全的任何文献。我其实依稀记得在去年的WWDC上有类似的这么一个说法,但你可不能指望读者或者googlers在尝试证明这就是在Swift中写单例的正确方法的时候就恰巧碰到那说法。不管怎么讲,我希望这篇博文能帮助一些人理解一行单例在Swift中是打开单例的正确方式。

基友们 Happy Coding!

原文链接:The Right Way to Write a Singleton
原文作者:Hector Matos
译文出自:开发技术前线 www.devtf.cn
译者:Gottabe
  • 大小: 124.7 KB
  • 大小: 268.9 KB
  • 大小: 270.1 KB
  • 大小: 32.3 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • python实现凯撒密码加密解密

    python实现凯撒密码加密解密 凯撒加密就是通过将字母移动一定的位数来实现加密和解密。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移,被替换成密文。例如,当偏移量是2的时候,所有的字母B将...

  • AES 128位CBC加密解密,IV不固定

    安全检查时要求账号和密码加密后才能存到数据库中,要求加密算法如下: 1)分组密码算法:AES(密钥长度在128位及以上)(GCM或CBC模式) 2)流密码算法:AES(密钥长度在128位及以上)(OFB或CTR模式)、chacha20 3...

  • Qt使用QuaZip解密带有密码的压缩文件

    开发环境:VS2017+Qt5.14.2 x64环境 1:有编译完成的quazip库以及头文件,如果环境与我的相匹配可以直接运行 2:采用非静态方式QuaZip的方式解密 带有密码的文件

  • AES 128位CBC加密解密(不使用固定IV)

    安全检查时要求账号和密码加密后才能存到数据库中,要求加密算法如下: 1)分组密码算法:AES(密钥长度在128位及以上)(GCM或CBC模式) 2)流密码算法:AES(密钥长度在128位及以上)(OFB或CTR模式)、chacha20 3...

  • PHP 程序如何实现加密解密?

    对称加密是一种加密方式,使用同一个密钥加密和解密数据。openssl_private_decrypt():使用私钥解密数据。openssl_private_encrypt():使用私钥加密数据。openssl_public_encrypt():使用公钥加密数据。openssl_...

  • c语言中如何编写保密程序,如何用C语言对文件进行加密和解密?

    这种加密是很简单很自由的,例如你在存文件的时候可以将文件中的每个字符都加上一个数,然后读取该文件的时候再每个字符相应地减去那个数,即可实现就简单的加密,这样你储存的文件看上去就是乱码了。只是这个规则太...

  • php 固定长度加密解密,如何加密/解密数据在PHP?

    字段Fname,Lname和Email将使用由OpenSSL提供的对称密码加密,> IV字段将存储用于加密的initialisation vector。存储要求取决于使用的密码和模式;更多关于这一点。>密码字段将使用单向密码哈希哈希,加密密码...

  • Java对字母移动三位加密_凯撒密码加密解密算法 汇编语言写个凯撒密码加密和解密的...

    用C#实现凯撒密码算法急急急用C#实现凯撒密码算法:要CSS布局HTML小编今天和大家分享明文是读取文件类容,结果送到另一个文件;欢迎来到CSS布局HTML,凯撒算法的原理很简单,就是对字母进行移位,比如最常用的右移3...

  • 爬虫工程师必备技术栈——加密解密以及字符编码原理

    前言——最近很多粉丝私信我说他们在进行JS渗透的时候总是碰到SHA,MD5,AES,RSA之类的玩意;...而本文也是为了带大家走入加密解密的神奇世界,并讲解常见的字符编码方式。拿出小本本仔细听课哦????

  • java下载文件时解密,Java实现文件的加密与解密

    最近在做一个项目,需要将资源文件(包括图片、动画等类型)进行简单的加密后再上传至云上的服务器,而在应用程序中对该资源使用前先将读取到的文件数据进行解密以得到真正的文件信息。此策略的原因与好处是将准备好的...

  • 易语言文本_解密c,易语言CNA算法实现快速加密解密文件的代码

    CNA文件加解密数据算法.版本 2.支持库 spec.支持库 iext.程序集 窗口程序集_启动窗口.子程序 __启动窗口_创建完毕.局部变量 code, 字节集, , , 加密密码.局部变量 test, 字节集, , , 要加密码的数据字节集.局部变量 ...

  • 恺撒密码加密与解密

    它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。 1.凯撒加密与解密: 恺撒...

  • C#实现AES算法对文件的加密解密

    网上的实现基本上都是要16,24或32位密码,密钥向量也要16位,都固定死在程序中,在此程序中,用户可以输入任意位数的密码,和自定义任意位数的密钥向量,不足的位数采取自动填充空格的方式。此程序中还用到了一些...

  • 凯撒密码(Caesar)加密解密算法C/C++实现

    本文的框架:摘要和关键字: 1、 引言 2、 凯撒密码(Caesar)基本原理 3、 凯撒密码(Caesar)加密算法 4、 凯撒密码(Caesar)解密算法 5、 C/C++程序框架 6、 结束语 摘要:凯撒密码...

  • 哈希与加密解密

    哈希函数(hash function)可以把任意长度的数据(字符串)计算出一个固定长度的结果数据。 我们习惯把要计算的数据称之为源数据,计算后的数据结果称之为哈希值(hash value) 有好几种常用哈希函数,对应不同的算法,...

  • MD5加密是什么?为什么不可解密?

    MD5是一种我们日常开发中经常使用到的加密方式,它使用起来操作简单且不可逆向解密。那么MD5到底是什么呢?又为什么不可逆呢?

  • 用c语言写一个文件加密程序,用C语言设计程序进行文件的加密

    依次类推,然后再与加密字符异或方法/步骤首先打开VC++6.0选择文件,新建选择C++ source file 新建一个空白文档声明头文件#include#include#include首先写个加密函数,算法就是简介里说的void EncryptFile(FILE *sfp...

  • 使用凯撒密码对字符串进行加密解密

    键盘输入一个原始字符串作为明文,然后使用加密方法加密,再对加密字符串进行解密。样例如下图,加密方法自定,完成其功能并测试。

  • 小米15工程固件 可以用于修改参数 修复tee损坏 修复底层分区 会用的下载

    资源描述: 机型代码:dada 1-----工程固件可以用于修改参数 开启diag端口。可以用于修复tee损坏以及修复底层分区。 2-----此固件是完整官方。不是第三方打包。请知悉 3-----此固件可以解锁bl后fast模式刷写。也可以底层深刷。也可以编程器写入 4-----请会用此固件 了解工程固件常识以及会用的朋友下载。 5-----个别高版本深刷需要授权才可以刷入。需要自己会刷写。 6------资源有可复制性。下载后不支持退。请考虑清楚在下载哦 工程资源常识可以参考博文:https://blog.csdn.net/u011283906/article/details/141815378 了解基本

  • 牙买加太阳能电池板检测7-YOLO(v5至v11)、COCO、CreateML、Paligemma、TFRecord、VOC数据集合集.rar

    牙买加太阳能电池板检测7-YOLO(v5至v11)、COCO、CreateML、Paligemma、TFRecord、VOC数据集合集.rar牙买加太阳能电池板检测-V11 2023-02-09 7:47 PM ============================= *与您的团队在计算机视觉项目上合作 *收集和组织图像 *了解和搜索非结构化图像数据 *注释,创建数据集 *导出,训练和部署计算机视觉模型 *使用主动学习随着时间的推移改善数据集 对于最先进的计算机视觉培训笔记本,您可以与此数据集一起使用 该数据集包含1406张图像。 太阳能板以可可格式注释。 将以下预处理应用于每个图像: *调整大小为640x640(拉伸) 没有应用图像增强技术。

Global site tag (gtag.js) - Google Analytics