String character offset <-> byte offset

That sounds like a recipe for trouble, unless details you did not provide constrain it to safe territory. See this post.

Instead, I suggest you dismantle the NSAttributedString using attributes(_:at:effectiveRange:) or one of its siblings, paying special attention to the effectiveRange, and rearranging it into an array such as [(String, [NSAttributedString.Key: Any])] or a simplification thereof. That array can be safely coded, stored or transmitted. Since each slice of the string has become a separate instance, their lengths are irrelevant, and their contents cannot jump across the boundaries.

To assemble the NSAttributedString again on the other side, turn each slice into its own attributed string and then concatenate them.