关于“幽灵架构”的补充说明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老爷子的讲解...
985 0
|
编译器 Swift
关于“幽灵架构”的补充说明4:协议的应用场景与局限性
再次解释一下协议的意义:定义某个功能模块的最小粒度,因为Swift是单继承,而无论是值类型还是引用类型都可以遵守多个协议,因此协议是比父类的粒度还要小的功能模块。
920 0
|
编译器 数据安全/隐私保护 Swift
关于“幽灵架构”的补充说明5:改造控制器
Swift中的泛型有非常多的用处,除了我在之前介绍的方法中作为占位符之外,还可以被用在协议中,构成一个泛型协议,那么遵守这个泛型协议的成员就会变成泛型成员。
895 0
关于“幽灵架构”的补充说明2:Struct以及Copy - on -Write
在“幽灵架构”Demo中我把两个数据模型声明成了Struct,苹果WWDC2015的414号视频讲解了非常多关于Struct的优势,其实也是所有值类型的优势。
1186 0
|
Swift
关于“幽灵架构”的补充说明1:协议中的方法定义
承接上一篇博文,上一篇的篇幅有点太长了,我觉得有一些相关的技术点需要说明,所以重新写几篇博文。
1032 0
|
前端开发 iOS开发
Swift黑科技:还在争论MVC和MVVM?博主独创幽灵架构MV!
本人原创,长文慎入,但此文可能会改写你的编程风格。我认为数据和模型交互的关键问题是如何处理数据源和视图源本身的异构性。
1165 0
|
6天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
3天前
|
监控 安全 应用服务中间件
微服务架构下的API网关设计策略与实践####
本文深入探讨了在微服务架构下,API网关作为系统统一入口点的设计策略、实现细节及其在实际应用中的最佳实践。不同于传统的摘要概述,本部分将直接以一段精简的代码示例作为引子,展示一个基于NGINX的简单API网关配置片段,随后引出文章的核心内容,旨在通过具体实例激发读者兴趣,快速理解API网关在微服务架构中的关键作用及实现方式。 ```nginx server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_service:5000;
|
5天前
|
缓存 监控 API
探索微服务架构中的API网关模式
随着微服务架构的兴起,API网关成为管理和服务间交互的关键组件。本文通过在线零售公司的案例,探讨了API网关在路由管理、认证授权、限流缓存、日志监控和协议转换等方面的优势,并详细介绍了使用Kong实现API网关的具体步骤。
19 3
|
6天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
36 4

热门文章

最新文章