Int128 fail in @Model with SwiftData

Swift recently added support for Int128. However, they do need NOT seem to be supported in SwiftData. Now totally possible I'm doing something wrong too.

I have the project set to macOS 15 to use a UInt128 in @Model class as attribute. I tried using a clean Xcode project with Swift Data choosen in the macOS app wizard.

Everything compiles, but it fails at runtime in both my app and "Xcode default" SwiftData:

SwiftData/SchemaProperty.swift:380: Fatal error: Unexpected property within Persisted Struct/Enum: Builtin.Int128

with the only modification to from stock is:

@Model
final class Item {
    var timestamp: Date
    var ipv6: UInt128
    
    init(timestamp: Date) {
        self.timestamp = timestamp
        self.ipv6 = 0
    }
}

I have tried both Int128 and UInt128. Both fails exactly the same. In fact, so exactly, when using UInt128 it still show a "Int128" in error message, despite class member being UInt128 .

My underlying need is to store an IPv6 addresses with an app, so the newer UInt128 would work to persist it. Since Network Framework IPv6Address is also not compatible, it seems, with SwiftData. So not a lot of good options, other an a String. But for an IPv6 address that suffers from that same address can take a few String forms (i.e. "0000:0000:0000:0000:0000:0000:0000:0000" =="0:0:0:0:0:0:0:0" == "::") which is more annoying than having a few expand Int128 as String separator ":".

Ideas welcomed. But potentially a bug in SwiftData since Int128 is both a Builtin and conforms to Codable, so from my reading it should work.

Answered by DTS Engineer in 826650022

Ziqiao and I have been talking about this behinds the scenes (-: so I’m going to chime in on this topic from a networking perspective.

IMO it’s best to avoid integer types when storing IP addresses. An IPv4 address is not a UInt32. Rather, it’s a sequence of 4 bytes. Similarly for an IPv6 address. If you treat IP addresses as integer types, you end up creating more problems for yourself. The classic example of this is the byte ordering behaviour of BSD Sockets, where you have to remember to apply {h,n}to{n,h}{s,l} in the right places. If in_addr were defined like this:

struct in_addr {
	uint8 s_addr[4];
};

the world would be a much better place.

Coming back to your main issue, I see two ways forward here:

  • Data

  • String

The first is obvious. The second does have the ambiguity problem you’ve described, but there are two easy solutions to that:

  • Always use the canonical textual representation format, as defined in RFC 5952. It’s safe to assume that the string representation of the IPv6Address type returns that format.

  • Or, if you want sorting to work, render each byte to hex and concatenate the result. If you insert colons [1] the result will even be parseable by IPv6Address.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Like so:

let str = addr.rawValue
    .map { [$0 >> 4, $0 & 0x0f] }
    .joined()
    .map { String($0, radix: 16) }
    .chunks(ofCount: 4)
    .map { x in x.joined() }
    .joined(separator: ":")

My guess is that this is because SQLite only support 64 bit integers so it won't work "out of the box" but require some extra work to store this type

Interesting, you may be right SQLite does not have a 128-bit integer. And I'll probably figure something else out here, since Int128 also require Sequoia.

But it's a Builtin type... so the SwiftData "rules" seem to imply those are mapped by SwiftData. Now... the internet tells me not all "Codables" work, but exactly what things don't is not well cataloged. All I know is a "IPv6 address" as Swift types have caused a lot of grief. ;)

Accepted Answer

Regarding the UInt128 / Int128 in SwiftData, I agree that SwiftData should provide native support, and would hence suggest that you file a feedback report – If you do so, please share your report ID here for folks to track.

Whether it is appropriate to save an IP address as a UInt128 is arguable, and I'd leave it there for other folks to comment.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Ziqiao and I have been talking about this behinds the scenes (-: so I’m going to chime in on this topic from a networking perspective.

IMO it’s best to avoid integer types when storing IP addresses. An IPv4 address is not a UInt32. Rather, it’s a sequence of 4 bytes. Similarly for an IPv6 address. If you treat IP addresses as integer types, you end up creating more problems for yourself. The classic example of this is the byte ordering behaviour of BSD Sockets, where you have to remember to apply {h,n}to{n,h}{s,l} in the right places. If in_addr were defined like this:

struct in_addr {
	uint8 s_addr[4];
};

the world would be a much better place.

Coming back to your main issue, I see two ways forward here:

  • Data

  • String

The first is obvious. The second does have the ambiguity problem you’ve described, but there are two easy solutions to that:

  • Always use the canonical textual representation format, as defined in RFC 5952. It’s safe to assume that the string representation of the IPv6Address type returns that format.

  • Or, if you want sorting to work, render each byte to hex and concatenate the result. If you insert colons [1] the result will even be parseable by IPv6Address.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Like so:

let str = addr.rawValue
    .map { [$0 >> 4, $0 & 0x0f] }
    .joined()
    .map { String($0, radix: 16) }
    .chunks(ofCount: 4)
    .map { x in x.joined() }
    .joined(separator: ":")

I agree with both of you.[1] I posted in the "iCloud and Data" section, not Networking, as a result. ;)

FWIW, the contrarians in Apple+ world is pkl-lang. They use an Int128 approach. Now on plus side pkl has nice pre-canned functions to deal with "collapsing zeros" in IP string and similar transforms: https://pkl-lang.org/package-docs/pkg.pkl-lang.org/pkl-pantry/pkl.experimental.net/current/net/IPv6Address.html

But...from SwiftData POV, Int128 is a built-in type, regardless of use case. And the double-whammy that you don't find out it's an issue until runtime is actually what prompted the post. Normally XCode is pretty good at telling these things. I'll file a Feedback report, since even some warning be useful. And a runtime error is not a great way to deal with "bad" @Model.

Anyway, IP is a normalized[2] String in my case to SwiftData. Ending the trail of trouble of "IPv6Address" from Linux+NIO to SwiftData. I imagine any "subnet-based queries" should likely be religated to convert a "subnet" into some notion of Bonjour domain. And store "domain" in SwiftData for filtering by "subnet".

The root of my problem is I need to listen on UDP broadcasts — which wasn't my choice (mDNS/DNS-SD/RFC-8305 is hard to argue against) — so got off the RN3151/etc wagon with:

Network framework supports UDP multicast using the NWConnectionGroup class, but that support has limits and, specifically, it does not support UDP broadcast. If you need something that’s not supported by Network framework, use BSD Sockets.

Since you're both here... is that advice actually still true? I actually took it at face value and didn't test it.

The backstory is I'd to have some swift-on-server "agent" that ideally convert UDP broadcasts in proper mDNS/DNS-SD things eventually. Thought being is that more consumable to iOS/macOS apps, and also avoid the wasted duplicative UDP broadcasts. Now the app still may need to do UDP discovery – possible there wouldn't be a some server agent to proxy UDP broadcast (or LLDP etc) to Bonjour. While in my straw man I'm using same NIO code in app and Linux - which works - actually using NW Framework in app be preferable to having the EventLoopGroup and friends on the iOS app side.


[1] My only defense on the networking side in using Int128 to store IPv6 in SwiftData is you might want to do some indexed query operation, based on "subnet", from SwiftUI. And, I wasn't sure that work in practice, even if one fiddled with endianness. But since SQLite does not support Int128, that mean no possibility to have some index on it. So even less useful.

[2] The esoteric case of an IPv4 string of 17.1 is actually 17.0.0.1 is just left unhandled.

Written by a2m0 in 826985022
is that advice actually still true?

Yes.

I have a lot more to say about that in the various posts hung off Extra-ordinary Networking.

If you have questions about networking stuff, I’m gonna recommend a new thread.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn. I hadn't see that post.

I just did not want to be on the "extra-ordinary" track unnecessarily,.. as that how someone ends up with Int128 questions.

Int128 fail in @Model with SwiftData
 
 
Q