Ignoring const in unsafe swift

I'm playing around with some code to manipulate UIColors in place, which I understand may be (is) completely unsupported. I was able to quickly write an obj-c version that ignores a const warning, but I've been having a lot of trouble trying to convert it to swift.

I'm not sure exactly how to get unsafe mutable access to a read only property and I would love some help here. The obj-c source is below:

@implementation UIColor (MemoryManipulation)
-(void)replaceWith:(UIColor *)newColor {
    size_t dstNum = CGColorGetNumberOfComponents(self.CGColor);
    size_t srcNum = CGColorGetNumberOfComponents(newColor.CGColor);
    // not true, but fine for now.
    NSAssert(dstNum == 4, @"UIColor must have 4 color components");
    NSAssert(srcNum == 4, @"UIColor must have 4 color components");
    CGFloat *dstComponents = (CGFloat *)CGColorGetComponents(self.CGColor);
    CGFloat *srcComponents = (CGFloat *)CGColorGetComponents(newColor.CGColor);
    dstComponents[0] = srcComponents[0];
    dstComponents[1] = srcComponents[1];
    dstComponents[2] = srcComponents[2];
    dstComponents[3] = srcComponents[3];
}
@end

I don’t think this is a good idea, you’re trying to edit UIColor in-place.
UIColor is immutable so that they can share the same object for the same color. Trying to mutate if could easily lead to unexpected result, and borderline anti-pattern.

Also most of the functions you used are converted safely into Swift, further preventing you from editing them.

I do suggest that you replace UIColor with a new one whenever you want to mutate, compiler should already try to optimize and avoid allocating new instance when possible.

1 Like

I agree this is a bad idea and won’t be put into production code. However, for the purposes of the test, I explicitly want to manipate the color that multiple uikit objects are referencing so they all change together.

Regardless, the question is more along the lines of: how could you get an UnsafeMutableBufferPointer to a readonly/const ivar.

Your best bet would be to create UnsafeMutablePointer from UnsafePointer using this initializer. But I don’t know how COW would interact in this scenario.

1 Like

I can't figure out how to correctly create the first UnsafePointer to underlying CGColor.components. It seems like whatever I try, Swift won't return the same pointer as Obj-c, test code below:

Swift

newColor.cgColor.components!.withUnsafeBufferPointer { (src) -> Void in
    print("swift-src:", src)
}
let src = UnsafePointer(newColor.cgColor.components!)
print("swift-src:", src)

Obj-c

CGFloat *srcComponents = (CGFloat *)CGColorGetComponents(newColor.CGColor);
printf("objc-src: %p\n", srcComponents);

Console

swift-src: UnsafeBufferPointer(start: 0x0000600002e6a120, count: 4)
swift-src: 0x0000600002e6a120
objc-src: 0x600001f14ca0

and even if you do create the UnsafePointer pointing to the same address(unless I'm misinterpreting the prints), somehow the object isn't there anymore

        let cgColor = UIColor.blue.cgColor
        withExtendedLifetime(cgColor) { _ in
            print(cgColor) // <CGColor 0x6000019ad320> [<CGColorSpace 0x6000019a5440> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0 0 1 1 )
            let ptr = UnsafePointer<CGColor>(bitPattern: Int(bitPattern: ObjectIdentifier(cgColor)))!
            print(ptr) // 0x00006000019ad320
            print(ptr.pointee) // __NSCFType
        }

Likely COW has handled it and treat the new UnsafePointer as separate memory.

One other thing I can think of is to bridge to NSMutableArray since they have class semantic, but likely COW will handle that as well.

I thought only swift stdlib collections like Array and Dictionary implemented COW

CGColor should have class semantic, because it's a class

1 Like

This is the implementation for CGColor.components in the Swift overlay for Core Graphics:

extension CGColor {
  @available(macOS 10.3, iOS 2.0, *)
  public var components: [CGFloat]? {
    guard let pointer = self.__unsafeComponents else { return nil }
    let buffer = UnsafeBufferPointer(start: pointer, count: self.numberOfComponents)
    return Array(buffer)
  }
}

Source: swift/CoreGraphics.swift at bbfc0649ed7e5a7b79c080430b31d6fecdc35c72 · apple/swift · GitHub

You can see that it creates a new Array, copying the contents. You may want to experiment with the __unsafeComponents API that the implementation uses. As others have said, this isn't something you should do in production.

2 Likes

I didn't think to look at Foundation! Thanks a bunch, I think I'll be able to get the correct pointers now.

I'm just playing around customizing iOS appearance in as many hacky ways as I can. In reality I use NSNotfiication everywhere to get proper appearance changing.

Just to clarify the terminology: the code I quoted is not part of Foundation. It's part of the so-called Swift overlay for Core Graphics. Apple provides "Swifty" APIs for a number of its C and Objective-C frameworks, and these are called "overlays" because they sit on top of the original frameworks.

Unlike the actual framework code, which is closed source, the overlays are part of the Swift project and thus open source. The overlays live at stdlib/public/Darwin in the Swift repo (there's one subfolder per framework, e.g. CoreGraphics).

I find it a bit weird that this code is located inside the "stdlib" folder because it's not part of the Swift standard library, but that's how it is. The actual stdlib is at stdlib/public/core.

1 Like