接口和类方法中的 SELF

由 王巍 (@ONEVCAT) 发布于 2015/06/10

我们在看一些接口的定义时,可能会注意到出现了首字母大写的 Self 出现在类型的位置上:

protocol IntervalType {
//... /// Return `rhs` clamped to `self`. The bounds of the result, even
/// if it is empty, are always within the bounds of `self`
func clamp(intervalToClamp: Self) -> Self //...
}

比如上面这个 IntervalType 的接口定义了一个方法,接受实现该接口的自身的类型,并返回一个同样的类型。

这么定义是因为接口其实本身是没有自己的上下文类型信息的,在声明接口的时候,我们并不知道最后究竟会是什么样的类型来实现这个接口,Swift 中也不能在接口中定义泛型进行限制。而在声明接口时,我们希望在接口中使用的类型就是实现这个接口本身的类型的话,就需要使用 Self 进行指代。

但是在这种情况下,Self 不仅指代的是实现该接口的类型本身,也包括了这个类型的子类。从概念上来说,Self 十分简单,但是实际实现一个这样的方法却稍微要转个弯。为了说明这个问题,我们假设要实现一个 Copyable 的接口,满足这个接口的类型需要返回一个和接受方法调用的实例相同的拷贝。一开始我们可能考虑的接口是这样的:

protocol Copyable {
func copy() -> Self
}

这是很直接明了的,它应该做的是创建一个和接受这个方法的对象同样的东西,然后将其返回,返回的类型不应该发生改变,所以写为 Self。然后开始尝试实现一个 MyClass 来满足这个接口:

class MyClass: Copyable {

    var num = 1

    func copy() -> Self {
// TODO: 返回什么?
// return
}
}

我们一开始的时候可能回写类似这样的代码:

 这是错误代码
func copy() -> Self {
let result = MyClass()
result.num = num
return result
}

但是显然类型是有问题的,因为该方法要求返回一个抽象的、表示当前类型的 Self,但是我们却返回了它的真实类型 MyClass,这导致了无法编译。也许你会尝试把方法声明中的 Self 改为 MyClass,这样声明就和实际返回一致了,但是很快你会发现这样的话,实现的方法又和接口中的定义不一样了,依然不能编译。

为了解决这个问题,我们在这里需要的是通过一个和当前上下文 (也就是和 MyClass) 无关的,又能够指代当前类型的方式进行初始化。希望你还能记得我们在对象类型中所提到的 dynamicType,这里我们就可以使用它来做初始化,以保证方法与当前类型上下文无关,这样不论是 MyClass 还是它的子类,都可以正确地返回合适的类型满足 Self 的要求:

func copy() -> Self {
let result = self.dynamicType()
result.num = num
return result
}

但是很不幸,单单是这样还是无法通过编译,编译器提示我们如果想要构建一个 Self 类型的对象的话,需要有 required 关键字修饰的初始化方法,这是因为 Swift 必须保证当前类和其子类都能响应这个 init 方法。在这个例子中,我们添加上一个 required 的 init 就行了。最后,MyClass 类型是这样的:

class MyClass: Copyable {

    var num = 1

    func copy() -> Self {
let result = self.dynamicType()
result.num = num
return result
} required init() { }
}

我们可以通过测试来验证一下行为的正确性:

let object = MyClass()
object.num = 100 let newObject = object.copy()
object.num = 1 println(object.num) // 1
println(newObject.num) // 100

而对于 MyClass 的子类,copy() 方法也能正确地返回子类的经过拷贝的对象了。

另一个可以使用 Self 的地方是在类方法中,使用起来也十分相似,核心就在于保证子类也能返回恰当的类型。

接口和类方法中的 SELF的更多相关文章

  1. Delphi接口的底层实现(接口在内存中仍然有其布局,它依附在对象的内存空间中,有汇编解释)——接口的内存结构图,简单清楚,深刻 good

    引言 接口是面向对象程序语言中一个很重要的元素,它被描述为一组服务的集合,对于客户端来说,我们关心的只是提供的服务,而不必关心服务是如何实现的:对于服务端的类来说,如果它想实现某种服务,实现与该服务相 ...

  2. Java 私有接口 【类中嵌套接口】

    1.前言 接口十分常用,能规范实现类的命名 和 实现多个实现类的向上转型成统一类型 ,但是接口的修饰符只能是 public吗? 当然不是,可以是private , 难道是像这样? 显然不可以,已经报错 ...

  3. Java中接口和Sala中的特质的区别?

    1.先要区分是Java中哪个版本的接口,因为Java中不同版本接口是不一样2.Java8之前的接口(不包含Java8),这个版本的接口只能属性和抽象方法,和Scala中的特质有完全的不用因为Scala ...

  4. 老猿学5G扫盲贴:中移动的5G计费架构中Nchf'服务化接口以及CHF中的AGF

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.关于Nchf' 在中移动企标中出现了在3GPP ...

  5. C# 用SoapUI调试WCF服务接口(WCF中包含用户名密码的验证)

    问题描述: 一般调试wcf程序可以直接建一个单元测试,直接调接口. 但是,这次,我还要测试在接口内的代码中看接收到的用户名密码是否正确,所以,单一的直接调用接口方法行不通, 然后就想办法通过soapU ...

  6. 对接第三方支付接口-获取http中的返回参数

    这几天对接第三方支付接口,在回调通知里获取返回参数,有一家返回的json格式,请求参数可以从标准输入流中获取. //1.解析参数 , 读取请求内容 BufferedReader br; String ...

  7. SpringMVC框架下实现JSON(类方法中回传数据到jsp页面,使用jQuery方法回传)

    JSON的实现,即将需要的数据回传到jsp页面: 1>.加入实现Json的三个架包到lib中:2>.目标方法上边加入注解,需要返回的值3>.在jsp页面中书写jQuery方法: ec ...

  8. IOS创建目录接口createDirectoryAtPath:withIntermediateDirectories:中参数attributes的设置

    在应用程序执行时,经常需要本地化保存一些重要的数据,这时就有可能需要创建一些目录.Objective-C提供了一个非常强大的创建目录的接口: - (BOOL)createDirectoryAtPath ...

  9. python编程 之 PyMysql包接口,python中如何使用数据库

    1,环境介绍 要求:使用数据库TESTDB.EMPLOYMENT EMPLOYEE表字段为 FIRST_NAME, LAST_NAME, AGE, SEX 和 INCOME. 2,基本用法: impo ...

随机推荐

  1. [Xcode 实际操作]二、视图与手势-(3)UIView视图的基本操作

    目录:[Swift]Xcode实际操作 本文将实现视图的添加与删除,以及切换视图在父视图中的层次. import UIKit class ViewController: UIViewControlle ...

  2. IT兄弟连 JavaWeb教程 EL与JSTL表达式经典面试题

    1.简述EL表达式的作用 EL表达式的作用可分为以下三类 访问Bean的属性. 输出简单的运算结果. 获取请求参数值. 2.JSP标签的作用?如何定义? JSP标签可以分离JSP页面的内容和逻辑,业务 ...

  3. 利用Web服务生成产品编号 执行添加操作

    为什么我想要执行添加操作,却添加不成功,系统提示我comm.ExecuteNonQuery有错误 已找到原因 在 string strsql = "insert into tb_goods( ...

  4. 2019最好用的自动化测试工具Top 10,果断收藏!

    经常有人在公众号留言或是后台问我,做自动化测试用哪个工具好,或是学哪门编程语言好呢? 这个时候总是无奈的说: 你应该学习Python 或是Java. 你应该掌握Selenium. 又或者你需要学会jm ...

  5. 如何使用Tomcat自带的日志实现tomcat-juli.jar

    前言 Tomcat自带的日志实现是tomcat-juli.jar,它是对默认的JDK日志java.util.logging进行一定的封装,和标准JDK日志支持相同的配置,但是和log4j等常用的日志框 ...

  6. 浏览器启动android应用

    window.location.href = "xl://com.caho.app:8888/app?name=chao"; <activity> <intent ...

  7. JMETER通过java代码通过代码/ JMETER API实现示例进行负载测试

    本教程试图解释Jmeter的基本设计,功能和用法,Jmeter是用于在应用程序上执行负载测试的优秀工具.通过使用jmeter GUI,我们可以根据我们的要求为请求创建测试样本并执行具有多个用户负载的样 ...

  8. 最长上升子序列LIS(云笔记图片版)

  9. Dubbo端口占用错误信息

    SEVERE: Exception sending context initialized event to listener instance of class com.common.SysCont ...

  10. 当获取相似数据时,使用不同方法调用不同sp,但是使用同一个方法去用IIDataReader或者SqlDataReader读取数据时需要判断column name是否存在。

    /// <summary> /// Checks clumn Name /// </summary> /// <param name="reader" ...