mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
177 lines
7.1 KiB
ReStructuredText
177 lines
7.1 KiB
ReStructuredText
:orphan:
|
|
|
|
.. warning:: This proposal was rejected. We ultimately decided to keep Array as
|
|
a dual-representation struct.
|
|
|
|
Bridging Container Protocols to Class Clusters
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
I think that attempting to bridge ``NSArray`` to a concrete type like
|
|
``Array<T>`` will be a poor compromise, losing both the flexibility of NSArray's
|
|
polymorphism and the performance afforded by ``Array<T>``'s simplicity.
|
|
Here's what I propose instead:
|
|
|
|
- Rename our current ``Array`` back to ``Vector`` or perhaps something like
|
|
``ContiguousArray``.
|
|
- Redefine ``Array`` as a refinement of the ``Collection`` protocol
|
|
that has integer indices.
|
|
- Implement an ``ArrayOf<T>`` generic container, like ``AnyIterator`` and
|
|
``AnySequence``, that can hold an arbitrary type conforming to ``Array``.
|
|
- Bridge ``NSArray`` from ObjC to Swift ``ArrayOf<AnyObject>`` with value
|
|
semantics.
|
|
- Bridge ``Array``-conforming types with class element types in Swift to
|
|
ObjC as ``NSArray``.
|
|
|
|
Although I'll be talking about arrays in this proposal, I think the same
|
|
approach would work for ``NSDictionary`` and ``NSSet`` as well, mapping them
|
|
to generic containers for associative map and unordered container protocols
|
|
respectively.
|
|
|
|
NSArray vs Array
|
|
================
|
|
|
|
Despite their similar names, ``NSArray`` and Swift's ``Array`` have
|
|
fundamentally incompatible design goals. As the root of a class cluster,
|
|
``NSArray`` provides abstraction over many underlying data structures, trading
|
|
weaker algorithmic guarantees for better representational flexibility and
|
|
implementation encapsulation. Swift's ``Array``, on the other hand, is intended to be a direct
|
|
representation of a contiguous region of memory, more like a C array or C++'s
|
|
``vector``, minimizing abstraction in order to provide tight algorithmic
|
|
guarantees. Many ``NSArray`` implementations are lazy,
|
|
such as those over KVO properties or Core Data aggregates, and
|
|
transforming them to concrete ``Array``\ s would have unintended semantic
|
|
effects. And on the other side, the overhead of having to accommodate an
|
|
arbitrary ``NSArray`` implementation inside ``Array`` destroys ``Array``
|
|
as a simple, high-performance container. Attempting to bridge these two types
|
|
will result in an unattractive compromise to both sides, weakening the
|
|
algorithmic guarantees of Array while forgoing the full flexibility of
|
|
``NSArray``.
|
|
|
|
"Array" as a Refinement of the Collection Protocol
|
|
==================================================
|
|
|
|
Swift's answer to container polymorphism is its generics system. The
|
|
``Collection`` protocol provides a common interface to indexable containers
|
|
that can be used generically, which is exactly what ``NSArray`` provides in
|
|
Cocoa for integer-indexable container implementations. ``Array`` could be
|
|
described as a refinement of ``Collection`` with integer indices::
|
|
|
|
protocol Array : Collection {
|
|
where IndexType == Int
|
|
}
|
|
protocol MutableArray : MutableCollection {
|
|
where IndexType == Int
|
|
}
|
|
|
|
The familiar ``NSArray`` API can then be exposed using default implementations
|
|
in the ``Array`` protocol, or perhaps even on the more abstract ``Collection``
|
|
and ``Sequence`` protocols, and we can bridge ``NSArray`` in a way that plays
|
|
nicely with generic containers.
|
|
|
|
This naming scheme would of course require us to rename the concrete
|
|
``Array<T>`` container yet again. ``Vector`` is an obvious candidate, albeit
|
|
one with a C++-ish bent. Something more descriptive like ``ContiguousArray``
|
|
might feel more Cocoa-ish.
|
|
|
|
The ArrayOf<T> Type
|
|
===================
|
|
|
|
Although the language as implemented does not yet support protocol types for
|
|
protocols with associated types, DaveA devised a technique for implementing
|
|
types that provide the same effect in the library, such as his ``AnyIterator<T>``
|
|
and ``AnySequence<T>`` containers for arbitrary ``Stream`` and ``Sequence``
|
|
types. This technique can be extended to the ``Array`` protocol, using class
|
|
inheritance to hide the concrete implementing type behind an abstract base::
|
|
|
|
// Abstract base class that forwards the Array protocol
|
|
class ArrayOfImplBase<T> {
|
|
var startIndex: Int { fatal() }
|
|
var endIndex: Int { fatal() }
|
|
|
|
func __getitem__(_ i: Int) -> T { fatal() }
|
|
|
|
// For COW
|
|
func _clone() -> Self { fatal() }
|
|
}
|
|
|
|
// Concrete derived class containing a specific Array implementation
|
|
class ArrayOfImpl<T, ArrayT: Array where ArrayT.Element == T>
|
|
: ArrayOfImplBase<T>
|
|
{
|
|
var value: ArrayT
|
|
var startIndex: Int { return value.startIndex }
|
|
var endIndex: Int { return value.endIndex }
|
|
func __getitem__(_ i: Int) -> T { return __getitem__(i) }
|
|
|
|
// For COW
|
|
func _clone() -> Self { return self(value) }
|
|
}
|
|
|
|
// Wrapper type that uses the base class to erase the concrete type of
|
|
// an Array
|
|
struct ArrayOf<T> : Array {
|
|
var value: ArrayOfImplBase<T>
|
|
|
|
var startIndex: Int { return value.startIndex }
|
|
var endIndex: Int { return value.endIndex }
|
|
func __getitem__(_ i: Int) -> T { return value.__getitem__(i) }
|
|
|
|
init<ArrayT : Array where ArrayT.Element == T>(arr: ArrayT) {
|
|
value = ArrayOfImpl<T, ArrayT>(arr)
|
|
}
|
|
}
|
|
|
|
The mutable variant can use COW optimization to preserve value semantics::
|
|
|
|
struct MutableArrayOf<T> : MutableArray {
|
|
/* ...other forwarding methods... */
|
|
|
|
func __setitem__(_ i: Int, x: T) {
|
|
if !isUniquelyReferenced(value) {
|
|
value = value._clone()
|
|
}
|
|
value.__setitem__(i, x)
|
|
}
|
|
}
|
|
|
|
Bridging ``NSArray`` into Swift
|
|
===============================
|
|
|
|
We could simply make ``NSArray`` conform to ``Array``, which would be
|
|
sufficient to allow it to be stored in an ``ArrayOf<AnyObject>`` container.
|
|
However, a good experience for ``NSArray`` still requires special-case
|
|
behavior. In particular, ``NSArray`` in Cocoa is considered a value class,
|
|
and best practice dictates that it be defensively ``copy``-ed when used. In
|
|
Swift, we should give bridged NSArrays COW value semantics by default, like
|
|
``NSString``. One way to handle this is by adding a case to the ``ArrayOf``
|
|
implementation, allowing it to either contain a generic value or an ``NSArray``
|
|
with COW semantics.
|
|
|
|
Bridging Swift Containers to ``NSArray``
|
|
========================================
|
|
|
|
We could have an implicit conversion to ``NSArray`` from an arbitrary type
|
|
conforming to ``Array`` with a class element type, allowing ObjC APIs to work
|
|
naturally with generic Swift containers. Assuming we had support for
|
|
``conversion_to`` functions, it could look like this::
|
|
|
|
class NSArrayOf<ArrayT: Array where ArrayT.Element : class> : NSArray {
|
|
/* ...implement NSArray methods... */
|
|
}
|
|
|
|
extension NSArray {
|
|
@conversion_to
|
|
func __conversion_to<
|
|
ArrayT: Array where ArrayT.Element : class
|
|
>(arr: ArrayT) -> NSArray {
|
|
return NSArrayOf<ArrayT>(arr)
|
|
}
|
|
}
|
|
|
|
``NSArray`` has reference semantics in ObjC, which is a mismatch with
|
|
Swift's value semantics, but because ``NSArray`` is a value class, this is
|
|
probably not a problem in practice, because it will be ``copy``-ed as
|
|
necessary as a best practice. There also needs to be a special case for bridging
|
|
an ``ArrayOf<T>`` that contains an ``NSArray``; such a container should be
|
|
bridged directly back to the underlying unchanged ``NSArray``.
|