Function have different behaviors when run on Simulator or Device

Hi, All:
The code below will get different behaviors when running on a Simulator or Real device, can anyone point out what causes this issue?

when running on device will get bad access issue
let pixels = UnsafeMutablePointer(mutating: $0) //Thread 1: EXC_BAD_ACCESS (code=1, address=0x103b98000)

when running on the simulator everything is OK

public struct PixelARGB {
    
    public var a: UInt8 = 0
    public var r: UInt8 = 0
    public var g: UInt8 = 0
    public var b: UInt8 = 0
    
    public init(a:UInt8, r:UInt8, g:UInt8, b:UInt8) {
        self.a = a
        self.r = r
        self.g = g
        self.b = b
    }
    
    public init() {}
}

public struct PixelRGBA {
    
    public var r: UInt8 = 0
    public var g: UInt8 = 0
    public var b: UInt8 = 0
    public var a: UInt8 = 0
    
    public init(r:UInt8, g:UInt8, b:UInt8, a:UInt8) {
        self.r = r
        self.g = g
        self.b = b
        self.a = a
    }
    
    public init() {}
}

extension UIImage {
    
    public func transformPixels<T>(_ transformer:
        (_ x: Int, _ y: Int, _ r: UInt8, _ g: UInt8, _ b: UInt8, _ a: UInt8) -> T ) -> [T]? {
        
        guard let dataProvider = self.cgImage?.dataProvider,
            let alphaInfo = self.cgImage?.alphaInfo,
            let pointer = CFDataGetBytePtr(dataProvider.data) else {
                return nil
        }
        
        let width = Int(self.size.width)
        let height = Int(self.size.height)
        let length = width * height
        
        switch alphaInfo {
        case .first, .premultipliedFirst, .noneSkipFirst:
            return pointer.withMemoryRebound(to: PixelARGB.self, capacity: length) {
                let pixels = UnsafeMutablePointer<PixelARGB>(mutating: $0)
                var result = [T]()
                result.reserveCapacity(length)
                for y in 0 ..< height {
                    for x in 0 ..< width {
                        let i = width * y  + x
                        let pixel = pixels[i]
                        result.append(transformer(x, y, pixel.r, pixel.g, pixel.b, pixel.a))
                    }
                }
                return result
            }
        case .last, .premultipliedLast, .noneSkipLast:
            return pointer.withMemoryRebound(to: PixelRGBA.self, capacity: length) {
                let pixels = UnsafeMutablePointer<PixelRGBA>(mutating: $0)
                var result = [T]()
                result.reserveCapacity(length)
                for y in 0 ..< height {
                    for x in 0 ..< width {
                        let i = width * y + x
                        let pixel = pixels[i]
                        result.append(transformer(x, y, pixel.r, pixel.g, pixel.b, pixel.a))
                    }
                }
                return result
            }
        default: return nil
        }
    }
}

I’m not 100% sure what’s causing this crash but the code you posted has problems that will require a significant rethink. Specifically, you define two Swift structs, PixelARGB and PixelRGBA, and assume that they have a known memory layout. This isn’t correct. Swift does not provide a way to fix the layout of structs, and thus you can’t create a Swift struct that maps to a memory layout in this way.

You have a couple of options here:

  • You can keep using Swift structs and make the various properties computed that extract the right byte from, say, a UInt32.

  • You can define these structures in C. C does give you some control over struct layout, and Swift will honour that when it imports a struct from C.


My next concern relates to the lifetime of memory objects. Your transformPixels(_:) function starts with code that determines pointer and then uses that value later in the function. The pitfall here is that the CFDataGetBytePtr only guarantees that the returned pointer is valid as long as the CFData is retained (and unmodified), and you have nothing to guarantee that retain. You need a withExtendedLifetime(_:_:) call here.


Finally, I’m not sure why you’re converting $0 to a mutating pointer in the first place; you don’t seem to mutate the resulting pixels pointer.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Thanks for your reply.
Finally, have tried the implementation below, seems everything works properly on both Simulator and Device.
I don't know the exact problem yet, but I feel it's some issue relative to the Generic parameters.

public protocol Pixel32 {
    var r: UInt8 { get set }
    var g: UInt8 { get set }
    var b: UInt8 { get set }
    var a: UInt8 { get set }
}

extension UIImage {
    
    public var pixels: [Pixel32]? {
        
        if  let dataProvider = self.cgImage?.dataProvider,
            let data = dataProvider.data,
            let alphaInfo = self.cgImage?.alphaInfo,
            let pointer = CFDataGetBytePtr(data) {
            
            let width = Int(self.size.width)
            let height = Int(self.size.height)
            let count = width * height
            
            switch alphaInfo {
            case .first, .premultipliedFirst, .noneSkipFirst:
                return pointer.withMemoryRebound(to: PixelARGB.self, capacity: count) { (p: UnsafePointer<PixelARGB>) -> [Pixel32] in
                    return Array(UnsafeBufferPointer.init(start: p, count: count))
                }
            case .last, .premultipliedLast, .noneSkipLast:
                return pointer.withMemoryRebound(to: PixelRGBA.self, capacity: count) { (p: UnsafePointer<PixelRGBA>) -> [Pixel32] in
                    return Array(UnsafeBufferPointer.init(start: p, count: count))
                }
            default:
                return nil
            }
        } else {
            return nil
        }
    }
}