关于“幽灵架构”的补充说明3:为什么不会产生“循环引用”

简介: 承接上文,已经简明阐述了使用Struct代替Class的好处,使用Class会使我们的程序出现“意外的共享”以及“循环引用”之类的危险,传统面向对象开发中对Class的依赖主要来自于我们对“继承”的依赖。

承接上文,已经简明阐述了使用Struct代替Class的好处,使用Class会使我们的程序出现“意外的共享”以及“循环引用”之类的危险,传统面向对象开发中对Class的依赖主要来自于我们对“继承”的依赖。Swift2.0引入协议扩展后,之前的“类-继承”所能实现的功能使用“结构体(枚举)-协议-协议扩展”都可以实现,并且更加高效和灵活。回到主题上来,首先回顾下“幽灵架构”中的两个主体:View和Model所对应的协议:

//视图使用的协议
protocol ViewType{
    func getData<M:ModelType>(model:M)
}
//数据使用的协议
protocol ModelType{
}
//定义默认方法giveData
extension ModelType{
    func giveData<V:ViewType>(view:V){
        view.getData(self)
    }
}

在控制器中生成了Model和View的实例之后,需要将二者绑定起来:

dataList[indexPath.row].giveData(cell)

如果你尝试修改顺序使用:

cell.getData(dataList[indexPath.row])

在dataList是[ModelType]类型的情况下编译无法通过,但是对于[Festival]或者[Event]这样同构的数组,在捆绑时调用getData方法是可行的,这是因为getData的参数是泛型,而数组中的元素是ModelType类型的,不符合泛型的使用规范,所以编译器会报错,但是为什么同样是泛型方法,反过来调用giveData就可以呢?giveData其实有两层类型检查的“关口”,第一层是giveData要求参数是遵守ViewType的泛型类型,而Demo中的每一个cell实例都是具体的类型,所以cell可以作为giveData的参数,而一旦giveData的参数通过考验,那么在giveData默认实现的方法体中,调用了参数的getData方法,这个方法也要求传入一个遵守协议的具体类型,这里传入了self属性,这个属性返回的是实例本身,所以可以顺利通过编译器的检测。所以真正进行数据绑定的是getData方法,giveData方法的作用是避免getData的参数是协议类型的,从而产生不必要的开销,使得即使数据源是异构的,依然可以通过giveData的方式传递真实的数据类型。
这里不得不再提一句泛型的好处,泛型保证了原始数据的传递,而不会像协议类型那样多一步寻址的过程,保证了高效。所以“幽灵架构”中的数据绑定虽然被移出了控制器代码,但是其实和在控制器中直接进行数据绑定同样高效,为了证明这一点,我们在TableViewCell的子类的getData方法中加入一个sizeOfValue方法,检查参数model的长度:

func getData<M : ModelType>(model: M) {
        print(sizeofValue(model))
        //这里不能写成guard let dateModel = model as? DateViewModel else{}令我有些意外
        guard let dateModel = model as? hasDate else{
            return
        }
        //处理相同属性
        dateLabel.text = dateModel.date
        //处理数据源异构
        if let event = dateModel as? Event{
            MixLabel.text = event.eventTitle
            backgroundColor = UIColor.redColor()
        } else if let festival = dateModel as? Festival{
            MixLabel.text = festival.festivalName
        }
    }

运行,打印结果:
这里写图片描述
因为我们定义的Festival和Event中有两个String类型的属性,每个String的大小是24字节,所以Festival和Event都是48字节,现在我们把Event改成一个Class:
这里写图片描述
class是引用类型的,所以它在栈上的长度是8个字节(一个指针的长度),可以看到在“幽灵架构”体系中,Model和View的绑定不会产生中间层,二者是直接绑定的,那么问题来了,这种互相绑定会不会产生“循环引用”?
首先,如果Model都是结构体的话,是不会产生循环引用的,每个值类型都只有一个拥有者(因为Copy),在值类型的拥有者执行完毕后,值类型随着拥有者一同销毁。虽然struct可以定义init,但是你会发现当你想在struct中定义一个deinit方法时,编译器提示你deinit只能被定义在类中:
这里写图片描述
所以你不用关心值类型的生命周期,那么已经被改成了class的Event呢?为了制造可能造成循环引用的场景,我们在事件节日提醒控制器前面再加一个控制器,把新加的控制器和事件节日提醒控制器用导航控制器连接起来,现在通过导航栏返回的时候事件节日提醒控制器会被系统回收,所有的Model和View实例也应该被回收,此时可以检验有没有出现“循环引用”,在Event中定义一个deinit:

class Event:DateViewModel{
    var date = ""
    var eventTitle = ""
    init(date:String,eventTitle:String){
        self.date = date
        self.eventTitle = eventTitle
    }
    deinit{
        print("deinit")
    }
}

现在运行程序,点击按钮来到事件节日提醒列表页面,然后点击返回,可以看到中控台打印:
这里写图片描述
谢天谢地^ ^。现在来解释一下,Swift中的实例方法其实并不会被保存在实例中,实例方法和全局方法不同的地方是多了一个命名空间,就好像类方法那样的格式,举个例子:

struct Test{
    var a = ""
    var b = ""
    var c = ""
}

使用sizeof查看这个结构体的大小,显示为72,然后向其中增加一个方法:

struct Test{
    var a = ""
    var b = ""
    var c = ""
    func ab(str:String){
        print(str)
    }
}

再次查看Test的大小,仍旧是72。在调用这个方法的时候我们常用的格式是:

a.ab("111")

但其实我们也可以这样用:

Test.ab(a)("111")

这里用到了柯里化,对于实例方法来说,先传入一个实例a,返回一个接受String类型的参数的方法,再传入我们定义的参数类型。所以使用方法进行数据和视图的绑定是安全的,因为二者是同等关系的,并不存在依赖关系。这也解释了为什么在官方文档中讲解capture list的时候,普通的闭包和方法不会产生循环引用,只有把闭包作为参数的时候才会产生循环引用,如上面看到的,因为实例只会持有自己的参数而不会持有方法。

目录
相关文章
|
前端开发 iOS开发
关于“幽灵架构”的总结:适用场景与方法重载
前几篇博文对“幽灵架构”做了用法的介绍和相关技术点的补充,本文是一篇总结性质的文章,分析该架构的适用场景和限制,首先让我们回顾一下iOS开发的MVC模式,参考斯坦福公开课里Paul老爷子的讲解...
992 0
|
编译器 Swift
关于“幽灵架构”的补充说明4:协议的应用场景与局限性
再次解释一下协议的意义:定义某个功能模块的最小粒度,因为Swift是单继承,而无论是值类型还是引用类型都可以遵守多个协议,因此协议是比父类的粒度还要小的功能模块。
928 0
|
编译器 数据安全/隐私保护 Swift
关于“幽灵架构”的补充说明5:改造控制器
Swift中的泛型有非常多的用处,除了我在之前介绍的方法中作为占位符之外,还可以被用在协议中,构成一个泛型协议,那么遵守这个泛型协议的成员就会变成泛型成员。
902 0
关于“幽灵架构”的补充说明2:Struct以及Copy - on -Write
在“幽灵架构”Demo中我把两个数据模型声明成了Struct,苹果WWDC2015的414号视频讲解了非常多关于Struct的优势,其实也是所有值类型的优势。
1192 0
|
Swift
关于“幽灵架构”的补充说明1:协议中的方法定义
承接上一篇博文,上一篇的篇幅有点太长了,我觉得有一些相关的技术点需要说明,所以重新写几篇博文。
1042 0
|
前端开发 iOS开发
Swift黑科技:还在争论MVC和MVVM?博主独创幽灵架构MV!
本人原创,长文慎入,但此文可能会改写你的编程风格。我认为数据和模型交互的关键问题是如何处理数据源和视图源本身的异构性。
1173 0
|
1月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
2月前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
50 3
|
2月前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
1月前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
161 69
从单体到微服务:如何借助 Spring Cloud 实现架构转型

热门文章

最新文章