关于“幽灵架构”的补充说明3:为什么不会产生“循环引用”
来源:互联网 发布:戴森无叶风扇 知乎 编辑:程序博客网 时间:2024/04/30 09:13
承接上文,已经简明阐述了使用Struct代替Class的好处,使用Class会使我们的程序出现“意外的共享”以及“循环引用”之类的危险,传统面向对象开发中对Class的依赖主要来自于我们对“继承”的依赖。Swift2.0引入协议扩展后,之前的“类-继承”所能实现的功能使用“结构体(枚举)-协议-协议扩展”都可以实现,并且更加高效和灵活。回到主题上来,首先回顾下“幽灵架构”中的两个主体:View和Model所对应的协议:
//视图使用的协议protocol ViewType{ func getData<M:ModelType>(model:M)}//数据使用的协议protocol ModelType{}//定义默认方法giveDataextension 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的时候,普通的闭包和方法不会产生循环引用,只有把闭包作为参数的时候才会产生循环引用,如上面看到的,因为实例只会持有自己的参数而不会持有方法。
- 关于“幽灵架构”的补充说明3:为什么不会产生“循环引用”
- 关于“幽灵架构”的补充说明5:改造控制器
- 关于“幽灵架构”的补充说明4:协议的应用场景与局限性
- 关于“幽灵架构”的补充说明1:协议中的方法定义
- 关于“幽灵架构”的补充说明2:Struct以及Copy - on -Write
- IE内存泄漏补充:关于循环引用的详细解释
- 为什么系统的block,AFN网络请求的block内使用self不会造成循环引用?
- 关于“幽灵架构”的总结:适用场景与方法重载
- 弱引用,软引用,幽灵的引用
- 关于留言簿的补充说明
- 关于#include的补充说明
- 关于private的补充说明
- Java幽灵引用的作用
- Java幽灵引用的作用
- 关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
- 对C++中引用的补充说明
- “右值引用”的补充说明
- JCIP_5_01_CopyOnWriteArrayList为什么不会产生ConcurrentModificationException?
- 单链表C/C++实现
- PHP中的流程控制
- 优秀开源项目SwipeLayout的使用
- NOJ - 2070 马尔扎哈的疑惑
- HTML5书写规范
- 关于“幽灵架构”的补充说明3:为什么不会产生“循环引用”
- ssh中jndi的配置(struts2.4.1+spring4.1.5+4.3.10+tomcat7)
- NOJ - 1093 阶乘之和
- hadoop集群搭建HDFS、HA、 YARN
- Scala学习9之产生随机数并写入到文件中
- 零编程经验的我是如何找到工作的
- spring +mybatis整合
- 函数执行时间测试
- ACM网络流入门