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

承接上文,已经简明阐述了使用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属性,这个属性返回的是实例本身,所以可以顺利通过编译器的检测。

这里不得不再提一句泛型的好处,泛型保证了原始数据的传递,而不会像协议类型那样多一步寻址的过程,保证了高效。所以“幽灵架构”中的数据绑定虽然被移出了控制器代码,但是其实和在控制器中直接进行数据绑定同样高效,为了证明这一点,我们在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的时候,普通的闭包和方法不会产生循环引用,只有把闭包作为参数的时候才会产生循环引用,如上面看到的,因为实例只会持有自己的参数而不会持有方法。

时间: 2024-10-12 22:04:11

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

关于“幽灵架构”的补充说明1:协议中的方法定义

承接上一篇博文,上一篇的篇幅有点太长了,我觉得有一些相关的技术点需要说明,所以重新写几篇博文.这个系列的文章将要说明以下几个问题: 1.giveData和getData在各自协议中的位置 2.使用struct代替class的好处 3."幽灵架构"为什么不会产生循环引用 4.协议的应用场景与局限性 5.运用面向协议编程思想改造控制器 让我们来简单回顾下这个架构,如果不明白的可以参考上一篇博文: 核心只有两个协议: //视图使用的协议 protocol ViewType{ func get

关于“幽灵架构”的补充说明5:改造控制器

Swift中的泛型有非常多的用处,除了我在之前介绍的方法中作为占位符之外,还可以被用在协议中,构成一个泛型协议,那么遵守这个泛型协议的成员就会变成泛型成员.还用我们之前的事件节日提醒Demo来展示,在之前的版本中我们使用了一个TableView来展示数据,现在如果有新的需求,需要在CollectonView中做类似的排列,并且针对数据源提供日期的筛选功能,面对相似的功能需求显然你不想写两份代码,那么此时最好的办法就是提炼出一个协议来,并且在协议扩展中声明一份默认的实现. 让我们来实现第一步:分析

关于“幽灵架构”的补充说明4:协议的应用场景与局限性

再次解释一下协议的意义:定义某个功能模块的最小粒度,因为Swift是单继承,而无论是值类型还是引用类型都可以遵守多个协议,因此协议是比父类的粒度还要小的功能模块.协议的功能定义一定要具体并且严格,someObject:Protocol where -中where子句的匹配条件只能针对someObject的类型或者遵守的其他协议,以及Protocol中的associatedType的约束,也就是说不能根据someObject中的成员判断是否遵守该协议.举例如下: Swift标准库中的很多基础类型都

关于“幽灵架构”的总结:适用场景与方法重载

前几篇博文对"幽灵架构"做了用法的介绍和相关技术点的补充,本文是一篇总结性质的文章,分析该架构的适用场景和限制,首先让我们回顾一下iOS开发的MVC模式,参考斯坦福公开课里Paul老爷子的讲解,如下图所示: 在MVC模式下Model和View是不能直接通信的,在"幽灵架构"体系中Model和View依旧不能直接通信,在传统的MVC中,这种通信的阻隔很多时候是因为在没有得到Model和View实体的情况下无法完成二者的数据绑定,在过去你可能想过在View中写一个方法来

iOS开发之oc(对于前面的补充1)[email&#160;protected]、循环引用

(一)@class的用处 [email protected]的作用:仅仅告诉编译器,某个名称是一个类(#import "Person.h"是要包含所有的方法声明,每次都要复制,不利于提高性能) @class Person; // 仅仅告诉编译器,Person是一个类(要实现方法还要在.m文件中包含Person头文件) 2.开发中引用一个类的规范 1> 在.h文件中用@class来声明类 2> 在.m文件中用#import来包含类的所有东西 (二)两端循环引用解决方案 第一类

如何成为一名架构师,架构师成长之路(转)

转自http://blog.csdn.net/fei33423/article/details/61934514 如何成为一名架构师,架构师成长之路 原创 2017年03月13日 22:50:34 3116 大量阅读别人的系统实现文章( 架构= 模块图 + 模块流程图(启动 和 主流程 ,可以用拟物 tag) 或者 模块时序图) 动态+静态 .对象很重要,模块很重要. 从产品角度,用户很重要 脑图不需要按空格,收缩行 https://www.processon.com/view/link/58c

从输入url开始,完善前端体系架构

原文链接: https://segmentfault.com/a/1190000013662126 从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系! javascript 前端 232 前言 见解有限,如有描述不当之处,请帮忙指出,如有错误,会及时修正. 为什么要梳理这篇文章? 最近恰好被问到这方面的问题,尝试整理后发现,这道题的覆盖面可以非常广,很适合作为一道承载知识体系的题目. 关于这道题目的吐槽暂且不提(这是一道被提到无数次的题,得到不少人的赞同,也被很多人反感),本文的

一张图秒懂微服务网络架构

? 最近参与了 公有云微服务项目,已经有一段时间未公开发表.通过这次改造公有云微服务项目的实践过程,分享一下公有云微服务网络架构,及服务部署方案. 每个平台的网络架构图都类似,但细节根据自有服务有组件又各不一样,别人的架构拿过来不一致适合你的架构,那么首先要了解每层架构及每个服务的职责,以及服务与服务之间的交互逻辑.我们根据私有云的架构迁移过来,保持了部分架构,补充了原来在私有云部署中公共组件部分.迁移到公有云后,一些公共组件由我们自己搭建并运维.整理总览图请看下图: 网络架构总览图 一.互联网

iOS开发之组件化架构漫谈

前段时间公司项目打算重构,准确来说应该是按之前的产品逻辑重写一个项目.在重构项目之前涉及到架构选型的问题,我和组里小伙伴一起研究了一下组件化架构,打算将项目重构为组件化架构.当然不是直接拿来照搬,还是要根据公司具体的业务需求设计架构. 在学习组件化架构的过程中,从很多高质量的博客中学到不少东西,例如蘑菇街李忠.casatwy.bang的博客.在学习过程中也遇到一些问题,在微博和QQ上和一些做iOS的朋友进行了交流,非常感谢这些朋友的帮助. 本篇文章主要针对于之前蘑菇街提出的组件化方案,以及cas