Holy War: Mutable copies

Applying mutableCopy() to an NSObject returns Any, not the version of the type you’re attempting to make mutable, for example, NSMutableArray, NSMutableParagraphStyle, NSMutableAttributedString or whatever.

Nate asks:

Is is acceptable to use as! with mutableCopy() or is there a better way to do this?

// Approach 1: Forced unwrap
let mutableStyle1 = style.mutableCopy() as! NSMutableParagraphStyle

The forced as! cast used in this approach will always succeed (even if using as! makes you want to wash your hands afterwards). But there are other approaches to consider. What do you think of these alternative takes on the question? Here are some other solutions for you to weigh in on.

// Approach 2: Forced unwrap with explanation on failure

/// Very low precedence group
precedencegroup VeryLowPrecedence { lowerThan: FunctionArrowPrecedence }

infix operator !!: VeryLowPrecedence

/// Guaranteed safe unwrap or fatal error with custom error string
public func !! <Wrapped>(value: Wrapped?, complaint: String) -> Wrapped {
    guard let value = value
        else { fatalError(complaint) }
    return value
}

let mutableStyle2 = style.mutableCopy() as? NSMutableParagraphStyle !! "Guaranteed cast to mutable paragraph style failed"

// Approach 3: Guard with explanatory fatal error
guard let mutableStyle3 = style.mutableCopy() as? NSMutableParagraphStyle
    else { fatalError("Guaranteed cast to mutable paragraph style failed") }

// Approach 4: Create then set with current attributes
let mutableStyle4 = NSMutableParagraphStyle()
mutableStyle4.setParagraphStyle(style)

// Approach 5: Protocol to expose typed mutable version
public protocol AvailableMutatingVersion: NSObjectProtocol {
    associatedtype MutableForm
    func mutableCopy() -> Any
    var mutableVersion : MutableForm { get }
}

extension AvailableMutatingVersion {
    public var mutableVersion: MutableForm {
        guard let copy = self.mutableCopy() as? MutableForm
            else { fatalError("Guaranteed mutable copy could not be constructed") }
        return copy
    }
}

extension NSParagraphStyle: AvailableMutatingVersion {
    public typealias MutableForm = NSMutableParagraphStyle
}

let mutableStyle5 = style.mutableVersion

Which approach reigns supreme? Vote now or offer some alternatives…

9 Comments

  • I’ve been using Approach #3. It seems the Swiftiest and doesn’t require a lot of other helper code.

  • I personally prefer Approach #2; #3 is the same thing as #2, except longer. #4 is verbose. #5 is pretty clever, and I hadn’t considered that, but I still like #2 over it. My version of #2 is almost identical to yours, except I have the slight modification that my !! operator exists at the NilCoalescingPrecedence, because I see it as a corollary to the ?? operator.

  • I’d never have thought of approach 5, but that’s what I’d go with 😀

  • None of the above for me:


    let mutableStyle1 : Style1 = style.mutableCopy() as? NSMutableParagraphStyle ?? []

    After all as you said, you can be sure the cast will work so a fatal error (or any error) around the check just seems like a lot of dead code.

    But to me every “!” is a potential crash as code evolves, as in this case if someone ever changed the storage type of style in the given code, this is about the shortest way I can see to satisfy the compiler things are being checked without adding nearly so much unreachable code.

    If someone did change the storage type, you should get a compiler warning that the cast could never succeed, so it’s not like this will change behavior for end users either as long as you fix compiler warnings before shipping.

    If approach 5 could be done without the manual mapping from non-mutable to mutable, I’d be more tempted to use it.

    • I think I agree with Kendal. I treat every ! as potential problem and avoid as much as possible. In this case, I agree, never ship without examining all compiler warnings. I would go for #1

  • I’m sure there are some implementation details that explain why mutableCopy() doesn’t return an instance of the same type it was called on, but is there any reason not to consider this a bug in the language?

    As for these solutions, #5 seems like the least maintenance, if you’re willing to include that protocol in your “Fixes for Swift annoyances” folder at the top of every product as well as always remembering “Don’t use mutableCopy() in Swift; use my own mutableVersion instead”. The others all seem like verbose apologies for a language wrinkle that would decrease the clarity of code they’re used in.

    In the end I think I vote for Nate’s option 1 as the simplest and clearest solution for a problem we shouldn’t have anyway. I don’t like the idea of putting !s, like goto in a never-ever use box. I prefer a “footguns, but permissible in these 3 limited cases” box.

  • In the end I went with #4, as it just uses the existing API and is pretty easy to understand. I find #5 quite interesting, perhaps next time…

  • I like approach #3. It doesn’t have any extra extensions or protocols and is simple to understand when reading the code, i.e., this should always work. If for some reason it does fail, you have an instant explanation in your console (if you are debugging).

  • We did #5 in Objective-C, except we deprecated -copy and -mutableCopy.