For the longest time now, I thought that the two functions above were the same.

But in actuality, while they may do exactly the same thing between open and closed braces (which in this case is nothing at all), what’s going on behind the scenes is different. To understand what’s going on we’ll first have to talk about the Container.

The Container

Revealed in more detail in Session 416 of WWDC 2016, the container is a wrapper around parameters adhering to a protocol and is used non-generically. The container functions as a box of fixed size (we’ll get back to this in a sec), thus allowing all adherers of a protocol to be of the same size, which is necessary for them to be used interchangeably.

var vehicles: [Drivable]

The fixed size of the container also allows us to store classes/structs that adhere to a protocol in an array of type protocol (as seen above), since the elements are now of the same size and can be stored in contiguous memory.

So what goes into the container?

The container is more or less a box with 5 rows:

1. payload_data_0 = 0x0000000000000004,

2. payload_data_1 = 0x0000000000000000,

3. payload_data_2 = 0x0000000000000000,

4. instance_type = 0x000000010d6dc408 ExistentialContainers`type

metadata for ExistentialContainers.Car,

5. protocol_witness_0 = 0x000000010d6dc1c0

ExistentialContainers`protocol witness table for

ExistentialContainers.Car : ExistentialContainers.Drivable

in ExistentialContainers

The first 3 rows labeled payload_data 0–3, respectively, represent the Value Buffer. The value buffer holds 3 words, each word is a chunk of memory representing 8 bytes. If your struct has just 3 properties and each property has a size within that 8 byte range, then the values are offloaded to the Value Buffer.

If your struct has more than 3 properties or has properties not within the 8 byte range, say a Character (9 bytes) or a String (24 bytes), then the values are stored in a separate value table allocated on the heap. In this case payload_data_0 would hold a pointer to the value table on the heap and the other two payload variables would remain uninitialized. This indirection is what maintains the sizing of the Container.

For clarity here are a few structs, adhering to the Drivable protocol, and their respective payloads:

Structs adhering to the Drivable protocol

car =

payload_data_0 = 0x0000000000000004,

payload_data_1 = 0x0000000000000000,

payload_data_2 = 0x0000000000000000,

instance_type = 0x000000010b50e410

ExistentialContainers`type metadata for

ExistentialContainers.Car,

protocol_witness_0 = 0x000000010b50e1c8

ExistentialContainers`protocol witness table for

ExistentialContainers.Car: ExistentialContainers.Drivable

in ExistentialContainers)

motorcycle =

payload_data_0 = 0x0000608000036820,

payload_data_1 = 0x0000000000000000,

payload_data_2 = 0x0000000000000000,

instance_type = 0x000000010b50e4d8

ExistentialContainers`type metadata for

ExistentialContainers.Motorcycle,

protocol_witness_0 = 0x000000010b50e1d8

ExistentialContainers`protocol witness table for

ExistentialContainers.Motorcycle:

ExistentialContainers.Drivable in ExistentialContainers

bus =

payload_data_0 = 0x00006000000364a0,

payload_data_1 = 0x0000000000000000,

payload_data_2 = 0x0000000000000000,

instance_type = 0x000000010b50e5a8

ExistentialContainers`type metadata for

ExistentialContainers.Bus,

protocol_witness_0 = 0x000000010b50e1e8

ExistentialContainers`protocol witness table for

ExistentialContainers.Bus: ExistentialContainers.Drivable

in ExistentialContainers

As you can see, Car has the expected payload, but Motorcycle has only one payload entry, even though it has two properties. As mentioned before, String variables are 24 bytes, so the licensePlate property causes all of the properties to be stored on the heap, thus having only one payload entry — the pointer to the values on the heap. Bus has 4 properties, so as expected, there is just one payload entry.

Now for the final two rows.

The instance_type variable (4th row) is a pointer to the Value Witness Table (VWT), which is another table structure that contains Type specific information on how to Allocate, Copy, and Destroy the value represented by the container.

The protocol_witness_0 variable (5th row) holds a pointer to the Protocol Witness Table (PWT). The PWT is another table structure that holds references to the implementation of protocol functions defined by an object adhering to the protocol. The PWT is the reason why if we called drive() on a Drivable that happened to be a car object, it knows to execute the Car objects drive function and not, say, the Bus’s implementation.

Function Parameters

So what does all of this have to do with the original question? What’s the difference between our two functions?

Functions in question

Well, there are actually quite a few things — how they’re dispatched, how local variables are instantiated, accessing of associated types for generic return types, compiler optimizations, dynamic behavior … the list goes on.

But for now we’ll focus on how instantiation occurs and the accessing of associated types. Links will be provide below for more details on most of these.

On to how local variable instantiation occurs: The protocol based function on line 6 receives its input in the form of an container since it must support multiple types. A local variable, transportation, is then created using the VWT and PWT of the container.

On the other hand, the generic based function will receive its input without the container, despite also supporting multiple Drivable types. Why is that?

Instead of passing an container to the generic function so that the local variable can be instantiated, the generic function becomes specialized at compile time, aware of type specific information generated at the function’s call site. So, suppose a Car object were passed into startTraveling(), swift will generate a Car specific version of the function, say:

func startTravelingWithCar(transportation: Car) { }

Behind the scenes the function also receives the car’s PWT and VWT, giving the function the necessary information to be able to set up a value buffer if necessary and determine the car object’s protocol specific function implementation of drive(). This newly generated function is now type specific, giving us access to any associated types of the Car object and all of this type information is determined at compile time — which is part of the reason why we can have an associated type be the return type of a generic function, but can’t do the same for protocol based functions.

protocol Returnable {

associateType ReturnType

}

//This will compile

func returnTheType<T: Returnable>(object: T) -> T.ReturnType { } ✅

//This won't compile

func returnTheType(object: Returnable) -> object.ReturnType { }  ❌

However protocols based functions aren’t bad, despite the fact that we can’t utilize associated types as return types. Protocol based functions, unlike their generic counterparts, offer a higher degree of dynamism and flexability at runtime. But, this post is long enough as is

Protocols, Generics, and Existential Containers — Wait What?的更多相关文章

  1. Which dispatch method would be used in Swift?-Existential Container

    In this example: protocol MyProtocol { func testFuncA() } extension MyProtocol { func testFuncA() { ...

  2. swift protocol 见证容器 虚函数表 与 动态派发

    一.测试代码: //protocol DiceGameDelegate: AnyObject { //} // //@objc protocol OcProtocol{ //    @objc fun ...

  3. 【基本功】深入剖析Swift性能优化

    简介 2014年,苹果公司在WWDC上发布Swift这一新的编程语言.经过几年的发展,Swift已经成为iOS开发语言的“中流砥柱”,Swift提供了非常灵活的高级别特性,例如协议.闭包.泛型等,并且 ...

  4. 深入剖析Swift性能优化

    简介 2014年,苹果公司在WWDC上发布Swift这一新的编程语言.经过几年的发展,Swift已经成为iOS开发语言的“中流砥柱”,Swift提供了非常灵活的高级别特性,例如协议.闭包.泛型等,并且 ...

  5. Thinking in Java——笔记(11)

    Holding Your Objects In general, your programs will always be creating new objects based on some cri ...

  6. Which dispatch method would be used in Swift?

    In this example: protocol MyProtocol { func testFuncA() } extension MyProtocol { func testFuncA() { ...

  7. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十一)之Holding Your Objects

    To solve the general programming problem, you need to create any number of objects, anytime, anywher ...

  8. Effective Java 29 Consider typesafe heterogeneous containers

    When a class literal is passed among methods to communicate both compile-time and runtime type infor ...

  9. thinking in java Generics Latent typing

    The beginning of this chapter introduced the idea of writing code that can be applied as generally a ...

随机推荐

  1. Linq中连接

    Linq中连接主要有组连接.内连接.左外连接.交叉连接四种.各个用法如下. 注:本文内容主要来自<Linq实战>,本例中用到的对象请见文章底部. 1. 组连接 组连接是与分组查询是一样的. ...

  2. 使用SQL语句创建数据库1

    创建一个数据文件和一个日志文件的数据库 user master --只想当前使用的数据库 GO --批处理标志 create database BOOK --创建book数据库 on primary ...

  3. 小菜读书---《Effective C#:改善C#程序的50种方法》

    一.用属性代替可访问的字段 1..NET数据绑定只支持数据绑定,使用属性可以获得数据绑定的好处: 2.在属性的get和set访问器重可使用lock添加多线程的支持. 二.readonly(运行时常量) ...

  4. Vue学习笔记:编译过程

    碰到是否有template选项时,会询问是否要对template进行编译: 在template编译(渲染成UI)有一个过程.模板通过编译生成AST,再由AST生成Vue的渲染函数,渲染函数结合数据生成 ...

  5. SpringBoot 之Quartz的使用

    对于Quartz的使用,还是想说一句,SpringBoot真的很好用啊! 第一步:当然是引入依赖啦 <parent> <groupId>org.springframework. ...

  6. Spring学习笔记:spring整合web之spring-web架包的引用(WebApplicationContextUtils注入容器)

    WebApplicationContextUtils 一.Spring整合web之前 案例:给部门列表添加新部门 import org.apache.log4j.Logger; import org. ...

  7. Redis 的 Lua 脚本支持

    Redis 2.6.0 内置的Lua Script支持,可以在Redis的Server端一次运行大量逻辑. 整个Script默认是在一个事务里的. Script里涉及的所有Key尽量用变量,从外面传入 ...

  8. sprintf和sscanf

    sprintf 一个可以将输入打印到字符串的函数,用法与printf差不多 可以参考这篇文章: http://blog.csdn.net/masibuaa/article/details/563488 ...

  9. [SCOI2009]粉刷匠

    线性DP预处理+分组背包 首先设dp[i][j][0/1]表示该木板前i个格刷了j次且最后一次颜色为0/1的最大正确数 做下0/1的前缀和然后转移状态 dp[i][j][k]=max(dp[l][j] ...

  10. Html5 填表 表单(二) input type 各种输入, 各种用户选择,上传等等泛输入用户交互

    <input>        无限制输入     type    限制输入 type = 如下类型     type 后还可以跟一些属性: 如<input type=text max ...