Apache Fory™ is a blazing-fast multi-language serialization framework.
The Swift implementation provides high-performance object graph serialization with macro-based code generation, schema evolution support, and xlang interoperability.
- Fast Binary Serialization: Efficient encoding for Swift value and reference types
- Macro-Driven Models: Use
@ForyStruct,@ForyEnum, and@ForyUnionto generate serializers - Cross-Language: Exchange payloads with Java, Rust, Go, Python, and other Fory language implementations via xlang
- Shared/Circular References: Preserve object identity with
trackReffor reference graphs - External-Type Serialization: Generate structural serializers for types owned by another module
- Dynamic Values: Serialize
Any,AnyObject, arbitrary application protocols,AnyHashable, and dynamic containers - Schema Evolution: Enable compatible mode for add/remove/reorder field evolution
| Target | Description |
|---|---|
Fory |
Core Swift implementation and macro declarations |
ForyMacro |
Macro implementation used by Fory model and field macros |
ForyXlangTests |
Executable used by Java-driven xlang integration tests |
ForyTests |
Swift unit tests |
Package.swift:
dependencies: [
.package(url: "https://github.com/apache/fory.git", from: "1.7.7")
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "Fory", package: "fory")
]
)
]Swift Package Index documentation for the Swift target:
https://swiftpackageindex.com/apache/fory/main/documentation/fory
import Fory
@ForyStruct
struct User: Equatable {
var name: String = ""
var age: Int32 = 0
}
let fory = Fory()
try fory.register(User.self, id: 1)
let input = User(name: "alice", age: 30)
let data = try fory.serialize(input)
let output: User = try fory.deserialize(data)
assert(input == output)var out = Data()
try fory.serialize(input, to: &out)
let buffer = ByteBuffer(data: out)
let output2: User = try fory.deserialize(from: buffer)
assert(output2 == input)Fory is the fastest option for single-threaded reuse. Keep one instance per thread.
Use Fory model macros, register user types, then serialize/deserialize.
import Fory
@ForyStruct
struct Address: Equatable {
var street: String = ""
var zip: Int32 = 0
}
@ForyStruct
struct Person: Equatable {
var id: Int64 = 0
var name: String = ""
var nickname: String? = nil
var tags: Set<String> = []
var scores: [Int32] = []
var addresses: [Address] = []
var metadata: [Int8: Int32?] = [:]
}
let fory = Fory()
try fory.register(Address.self, id: 100)
try fory.register(Person.self, id: 101)
let person = Person(
id: 42,
name: "Alice",
nickname: nil,
tags: ["swift", "xlang"],
scores: [10, 20, 30],
addresses: [Address(street: "Main", zip: 94107)],
metadata: [1: 100, 2: nil]
)
let bytes = try fory.serialize(person)
let decoded: Person = try fory.deserialize(bytes)
assert(decoded == person)Enable reference tracking for class/reference graphs:
let fory = Fory(ref: true, compatible: false)Shared reference identity is preserved:
import Fory
@ForyStruct
final class Animal {
var name: String = ""
required init() {}
init(name: String) {
self.name = name
}
}
@ForyStruct
final class AnimalPair {
var first: Animal? = nil
var second: Animal? = nil
required init() {}
init(first: Animal? = nil, second: Animal? = nil) {
self.first = first
self.second = second
}
}
let fory = Fory(ref: true)
try fory.register(Animal.self, id: 200)
try fory.register(AnimalPair.self, id: 201)
let shared = Animal(name: "cat")
let input = AnimalPair(first: shared, second: shared)
let data = try fory.serialize(input)
let decoded: AnimalPair = try fory.deserialize(data)
assert(decoded.first === decoded.second)For cyclic graphs, use weak on at least one edge to avoid ARC leaks:
@ForyStruct
final class Node {
var value: Int32 = 0
weak var next: Node? = nil
required init() {}
}Top-level and field-level dynamic serialization is supported for:
AnyAnyObject- arbitrary
any Protocolexistentials AnyHashable[Any][String: Any][Int32: Any][AnyHashable: Any]
If dynamic payloads contain user-defined concrete types, register those types before serialization/deserialization.
Any and AnyObject roots use direct APIs. Arbitrary application protocols
and dynamic carriers explicitly select DynamicSerializer<T> and the
applicable carrier serializers.
import Fory
@ForyStruct
struct DynamicAddress {
var street: String = ""
var zip: Int32 = 0
}
let fory = Fory()
try fory.register(DynamicAddress.self, id: 410)
let dynamic: Any = DynamicAddress(street: "main", zip: 94107)
let dynamicData = try fory.serialize(dynamic)
let dynamicOutput: Any = try fory.deserialize(dynamicData)
let payload: [String: Any] = [
"id": Int32(7),
"name": "alice",
"addr": DynamicAddress(street: "main", zip: 94107),
]
typealias PayloadSerializer = DictionarySerializer<
String,
DynamicSerializer<Any>
>
let data = try fory.serialize(
payload,
with: PayloadSerializer.self
)
let decoded = try fory.deserialize(
data,
with: PayloadSerializer.self
)
assert(decoded["id"] as? Int32 == 7)Null decoding semantics:
Anynull is represented asForyAnyNullValueAnyObjectnull is represented asNSNull- an
AnyHashabledynamic null key is represented asAnyHashable(ForyAnyNullValue())
Use compatible mode to evolve schemas between peers.
import Fory
@ForyStruct
struct PersonV1 {
var name: String = ""
var age: Int32 = 0
var address: String = ""
}
@ForyStruct
struct PersonV2 {
var name: String = ""
var age: Int32 = 0
var phone: String? = nil
}
let writer = Fory()
try writer.register(PersonV1.self, id: 1)
let reader = Fory()
try reader.register(PersonV2.self, id: 1)
let v1 = PersonV1(name: "alice", age: 30, address: "main st")
let bytes = try writer.serialize(v1)
let v2: PersonV2 = try reader.deserialize(bytes)
assert(v2.name == "alice")
assert(v2.age == 30)
assert(v2.phone == nil)Compatible mode supports:
- Add fields
- Remove fields
- Reorder fields
Not supported:
- Arbitrary field type changes (for example
Int32toString)
Use @ForyField(encoding:) to control integer wire encoding.
import Fory
@ForyStruct
struct Metrics {
@ForyField(encoding: .fixed)
var u32Fixed: UInt32 = 0
@ForyField(encoding: .tagged)
var u64Tagged: UInt64 = 0
}Supported combinations:
| Swift type | Supported encodings |
|---|---|
Int32, UInt32 |
.varint, .fixed |
Int64, UInt64, Int, UInt |
.varint, .fixed, .tagged |
Nested collection fields can carry the same integer encoding metadata through field type hints:
@ForyStruct
struct NestedMetrics {
@ListField(element: .encoding(.fixed))
var values: [Int32?] = []
@SetField(element: .encoding(.fixed))
var ids: Set<UInt32?> = []
@MapField(value: .list(element: .encoding(.fixed)))
var grouped: [String: [Int32?]] = [:]
}For List fields with non-null fixed-width integer elements, Swift emits the
corresponding Fory primitive packed-array type. Set fields remain Fory sets,
even when their element metadata uses fixed integer encoding.
Date maps to Fory timestamp. LocalDate maps to Fory date and exposes
epochDay, init(epochDay:), fromEpochDay(_:), init(year:month:day:),
year, month, day, toEpochDay(), init(utcDate:), and toUTCDate().
Use @ForyEnum for C-style enums and @ForyUnion for associated-value enums.
import Fory
@ForyEnum
enum Color: Equatable {
case red
case green
case blue
}
@ForyUnion
enum StringOrLong: Equatable {
@ForyUnknownCase
case unknown(UnknownCase)
@ForyCase(id: 0)
case text(String)
@ForyCase(id: 1)
case number(Int64)
}
let fory = Fory(compatible: false)
try fory.register(Color.self, id: 300)
try fory.register(StringOrLong.self, id: 301)
let a = try fory.serialize(Color.green)
let b = try fory.serialize(StringOrLong.text("hello"))
let color: Color = try fory.deserialize(a)
let value: StringOrLong = try fory.deserialize(b)
assert(color == .green)
assert(value == .text("hello"))Define an external structural serializer when the value type belongs to another module:
@ForyStruct(target: ThirdParty.User.self)
struct UserSerializer {
var name: String
var age: UInt32
}
try fory.register(UserSerializer.self, id: 400)
let data = try fory.serialize(user, with: UserSerializer.self)
let decoded = try fory.deserialize(data, with: UserSerializer.self)Root carriers compose recursively:
let data = try fory.serialize(
users,
with: ArraySerializer<UserSerializer>.self
)See ../docs/object-serialization/swift/external-types.md.
A type that implements Serializer with Target == Self uses ordinary roots,
generated fields, and carriers without with:. An application may give an
external type one retroactive self-target conformance, but that conformance is
process-global and must be the single binding for the (Target, Serializer)
pair.
A separate serializer whose Target is another type is selected explicitly at
every required root, field, or carrier child. Use separate serializers for
public libraries and for multiple or alternative implementations.
See ../docs/object-serialization/swift/custom-serializers.md.
Recommended preset:
let fory = Fory()Type registration can be ID-based or name-based:
try fory.register(MyType.self, id: 100)
try fory.register(MyType.self, name: "com.example.MyType")Cross-language rules:
- Keep registration mappings consistent across peers
- Use compatible mode for independently evolving schemas
- Register all user-defined concrete types used inside dynamic payloads
- Prefer
trackRef=falsefor value-only payloads to avoid reference-table overhead - Reuse the same
Foryinstance and register types once per process/service lifecycle - Use
compatible=falseonly when every reader and writer always uses the same schema and you want faster serialization and smaller size
Run Swift tests:
cd swift
ENABLE_FORY_DEBUG_OUTPUT=1 swift testRun Java-driven Swift xlang tests:
cd java/fory-core
ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest- Swift Guide
- Configuration
- Type Registration
- Schema Evolution
- External-Type Serialization
- Custom Serializers
- Basic Serialization
- Xlang Specification
- Xlang Type Mapping
Licensed under the Apache License, Version 2.0. See LICENSE.
Contributions are welcome. See CONTRIBUTING.md.