Swift standard library for Skip apps.
See what API is currently implemented here.
SkipLib vends the skip.lib
Kotlin package. It serves two purposes:
- SkipLib is a reimplementation of the Swift standard library for Kotlin on Android. Its goal is to mirror as much of the Swift standard library as possible, allowing Skip developers to use Swift standard library API with confidence.
- SkipLib contains custom Kotlin API that the Skip transpiler takes advantage of when translating your Swift source to the equivalent Kotlin code. For example, the Kotlin language does not have tuples. Instead, SkipLib's
Tuple.kt
defines bespoke KotlinTuple
classes. When the transpiler translates Swift code that references tuples, it uses theseTuple
classes in the Kotlin it generates.
SkipLib depends on the skip transpiler plugin and has no additional library dependencies.
It is part of the core SkipStack and is not intended to be imported directly.
The module is transparently adopted through the automatic addition of import skip.lib.*
to transpiled files by the Skip transpiler.
- SkipLib's Swift symbol files (see Implementation Strategy) are nominally complete. They should declare all Swift standard library API. This is difficult to validate, however, so if you find anything missing, please report it to us.
- Unimplemented API is appropriately marked with
@available(*, unavailable)
annotations. Skip will generate an error when you attempt to use an unimplemented API. - In particular, a significant portion of the collections API is not yet implemented.
- Unit testing is not comprehensive.
See Swift Standard Library Support.
We welcome contributions to SkipLib. The Skip product documentation includes helpful instructions and tips on local Skip library development.
The most pressing need is to reduce the amount of unimplemented API. To help fill in unimplemented API in SkipLib:
- Find unimplemented API. Unimplemented API should be marked with
@available(*, unavailable)
in the Swift symbol files. - Write an appropriate Kotlin implementation. See Implementation Strategy below. For collections API, make sure your implementation is duplicated for
String
as well. - Write unit tests.
- Submit a PR.
Other forms of contributions such as test cases, comments, and documentation are also welcome!
Apart from the Skip transpiler itself, SkipLib implements the lowest levels of the Swift language. Its implementation strategy, therefore, differs from other Skip libraries.
Most Skip libraries call Kotlin API, but are written in Swift, relying on the Skip transpiler for translation to Kotlin. Most of SkipLib, however, is written in pure Kotlin. Consider SkipLib's implementation of Swift's Array
. SkipLib divides its Array
support into two files:
Sources/SkipLib/Array.swift
acts as a Swift header file, declaring theArray
type's Swift API but stubbing out the implementation. The// SKIP SYMBOLFILE
comment at the top of the file marks it as such. Read more about special Skip comments in the Skip product documentation.Sources/SkipLib/Skip/Array.kt
contains the actualArray
implementation in Kotlin.
This pattern is used for most Swift types throughout SkipLib. Meanwhile, SwiftLib implementations of constructs built directly into the Swift language - e.g. tuples or inout
parameters - only have a Kotlin file, with no corresponding Swift symbol file.
The following table summarizes SkipLib's Swift Standard Library API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within #if !SKIP
blocks - you can use any API you want.
Support levels:
- β β Full
- π’ β High
- π‘ - Medium
- π β Low
Support | API |
---|---|
π’ |
|
β | Any |
β | AnyActor |
β | AnyHashable |
β | AnyObject |
π’ |
|
β | assert |
β | assertionFailure |
β | AsyncSequence |
β |
|
π’ |
|
β | CaseIterable |
β | CGAffineTransform |
β | CGFloat |
β | CGPoint |
β | CGRect |
β | CGSize |
π’ |
|
π’ |
|
π‘ |
|
β | Comparable |
β | CustomDebugStringConvertible |
β | CustomStringConvertible |
π’ |
|
π’ |
|
β | DiscardingTaskGroup |
π’ |
|
π’ |
|
β | Equatable |
β | Error |
β | fatalError |
π’ |
|
β | Hashable |
β | Hasher |
β | Identifiable |
π’ |
|
π’ |
|
π’ |
|
π’ |
|
π’ |
|
π’ | @MainActor |
π’ |
|
π’ |
|
β | max(_:_:) |
β | min(_:_:) |
β | ObjectIdentifier |
β | OptionSet |
β | precondition |
β | preconditionFailure |
β | RandomNumberGenerator |
π |
|
β | RawRepresentable |
π |
|
π |
|
π |
|
β | Result |
π‘ |
|
π’ |
|
π’ |
|
π’ |
|
β | strlen |
β | strncmp |
π’ |
|
β | SystemRandomNumberGenerator |
π‘ |
|
β | TaskGroup |
β | ThrowingDiscardingTaskGroup |
β | ThrowingTaskGroup |
β | type(of:) |
π’ |
|
π’ |
|
π’ |
|
π’ |
|
π’ |
|
β | withDiscardingTaskGroup |
β | withTaskCancellationHandler |
β | withThrowingTaskGroup |
β | withThrowingDiscardingTaskGroup |
β | withThrowingTaskGroup |
Collections are perhaps the most complex part of the Swift standard library, and of SkipLib. Swift's comprehensive collection protocols allow Array
, Set
, Dictionary
, String
, and other types to all share a common set of API, including iteration, map
, reduce
, and much more.
Corresponding Kotlin types - List
, Set
, Map
, String
, etc - do not share a similarly rich API set. As a result, SkipLib must duplicate collection protocol implementations in both Collections.kt
and String.kt
, and must duplicate SetAlgebra
implementations in both Set.kt
and OptionSet.kt
.
See the explanatory comments in Collections.kt
for more information on the design of SkipLib's internal collections support.
Skip is able to synthesize default Codable
conformance for the Android versions of your Swift types. The Android versions will encode and decode exactly like their Swift source types. Skip also supports your custom CodingKeys
as well as your custom encode(to:)
and init(from:)
functions for encoding and decoding.
There are, however, a few restrictions:
-
Skip cannot synthesize
Codable
conformance for enums that are notRawRepresentable
. You must implement the required protocol functions yourself. -
If you implement your own
encode
function orinit(from:)
decoding constructor and you useCodingKeys
, you must declare your ownCodingKeys
enum. You cannot rely on the synthesized enum. -
Array
,Set
, andDictionary
are fully supported, but nesting of these types is limited. So for example Skip can encode and decodeArray<MyCodableType>
andDictionary<String, MyCodableType>
, but notArray<Dictionary<String, MyCodableType>>
. Two forms of container nesting are currently supported: arrays-of-arrays - e.g.Array<Array<MyCodableType>>
- and dictionaries-of-array-values - e.g.Dictionary<String, Array<MyCodableType>>
. In practice, other nesting patters are rare. -
When calling
decode
, you must supply a concrete type literal to decode. This applies to both top-levelDecoders
likeJSONDecoder
as well as containers likeKeyedDecodingContainer
. The following will work:let object = try decoder.decode(MyType.self, from: jsonData)
But these examples will not work:
let type = MyType.self let object = try decoder.decode(type, from: jsonData) // T is a generic type let object = try decoder.decode(T.self, from: jsonData)
It is common for developers to take advantage of Decodable
-typed generic functions to be able to decode arbitrary types, so this last limitation is the most onerous. You must consider it when writing your decoding code, and it often requires refactoring existing decoding code being ported to Skip.
One mechanism to ease this restriction and allow you to decode unknown generic types is to write inline
decoding functions that take advantage of Kotlin's reified types. Inline functions, however, come with their own limitations and tradeoffs. You can read more about this topic in the Kotlin language documentation. Skip automatically converts any Swift function with the @inline(__always)
attribute into a Kotlin inline function with reified generics.
For example, a function like the following will work with Skip, so long as you call it with a concrete Response
type or with a generic Response
type from another inline
function:
@inline(__always) public func send<R: Request, Response: Decodable>(request: R) async throws -> Response {
let data = try await download(request: request)
return try jsonDecoder.decode(Response.self, from: data)
}
It transpiles to code like:
inline suspend fun <reified R, reified Response> send(request: R): Response where R: Request, Response: Decodable {
val data = download(request = request)
return jsonDecoder.decode(Response::class, from = data)
}