Home > front end >  Issue with understanding getRed input inout`s and inout parameter requirement
Issue with understanding getRed input inout`s and inout parameter requirement

Time:11-08

I was looking to the getRed of UIColor and I noticed we should be working with inout parameters to make it work! But I do not know why Xcode does not help me out to fix my mistake when I am not using the & before parameter! Instead of getting related help from Xcode I do get this help for this mistake:

Cannot convert value of type 'CGFloat' to expected argument type 'UnsafeMutablePointer?'

var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0

UIColor.blue.getRed(r, green: &g, blue: &b, alpha: &a)

Well that I know what is the issue, but the complain of Xcode does not help me out to know that I forgot &.

For example I made a simple test function, in case I do mistake Xcode help me like this:

Passing value of type 'CGFloat' to an inout parameter requires explicit '&'

func test(value: inout CGFloat) { value  = 1.0 }

var r: CGFloat = 0
test(value: r)

So why I do not get same message for the same mistake?

PS: If we could use the getRed without inout parameters, how could be done in that case? because with getting such help from Xcode I am think the issue is not about inout and could be get those value in other way.

Xcode: Version 12.5.1 (12E507)

CodePudding user response:

getRed is not defined as inout, like you see in Swift, although it behaves similarly. If you look at the documentation you'll see that each parameter is defined as UnsafeMutablePointer<CGFloat>?:

func getRed(_ red: UnsafeMutablePointer<CGFloat>?, 
      green: UnsafeMutablePointer<CGFloat>?, 
       blue: UnsafeMutablePointer<CGFloat>?, 
      alpha: UnsafeMutablePointer<CGFloat>?) -> Bool

This answer details the similarities between the two constructs.

See also: https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545

As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body. The optimized behavior is known as call by reference; it satisfies all of the requirements of the copy-in copy-out model while removing the overhead of copying. Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.

That, in turn, explains the error message you're getting and why it's different than the error you get when you define a function using inout. So, in fact, in your test scenario, the following would be more analogous code:

func test(value: UnsafeMutablePointer<CGFloat>?) {
    value?.pointee  = 1
}

var r: CGFloat = 0
test(value: r) //<-- generates the same error here and would be fixed by adding the &

In terms of your PS ("If we could use the getRed without inout parameters, how could be done in that case?"), that's how the library is defined -- you don't have an option of not using UnsafeMutablePointer<CGFloat>


Adendum to address additional issues brought up in the comments:

inout is a Swift construct. See the "In-Out Parameters" of the documentation.

UnsafeMutablePointer generally comes from interacting with C-related APIs in Swift (ie, you're unlikely to encounter it in pure Swift). You can see its documentation here.

inout and UnsafeMutablePointer are similar in that they can both end up pointing to a location in memory.

In terms of your "Which one is better for which work" question: if you're in pure Swift, most likely you'll use inout. If you're interacting with a C API, you'll likely use UnsafeMutablePointer.

CodePudding user response:

If we could use the getRed without inout parameters, how could be done in that case?

Abstract the terrible old API, only using it one time.

public extension UIColor {
  struct HSBA {
    public var hue: CGFloat
    public var saturation: CGFloat
    public var brightness: CGFloat
    public var alpha: CGFloat
  }
  
  struct RGBA {
    public var red: CGFloat
    public var green: CGFloat
    public var blue: CGFloat
    public var alpha: CGFloat
  }
  
  convenience init(_ hsba: HSBA) {
    self.init(
      hue: hsba.hue,
      saturation: hsba.saturation,
      brightness: hsba.brightness,
      alpha: hsba.alpha
    )
  }
  
  convenience init(_ rgba: RGBA) {
    self.init(
      red: rgba.red,
      green: rgba.green,
      blue: rgba.blue,
      alpha: rgba.alpha
    )
  }
}

public extension UIColor.RGBA {
  init(_ color: UIColor) throws {
    self = try makeComponents(
      init: Self.init,
      assignComponents: color.getRed
    )
  }
}

public extension UIColor.HSBA {
  init(_ color: UIColor) throws {
    self = try makeComponents(
      init: Self.init,
      assignComponents: color.getHue
    )
  }
}

private func makeComponents<Components>(
  init: ((CGFloat, CGFloat, CGFloat, CGFloat)) -> Components,
  assignComponents: (
    UnsafeMutablePointer<CGFloat>?,
    UnsafeMutablePointer<CGFloat>?,
    UnsafeMutablePointer<CGFloat>?,
    UnsafeMutablePointer<CGFloat>?
  ) -> Bool
) throws -> Components {
  var components = (
    CGFloat(), CGFloat(), CGFloat(), CGFloat()
  )
  
  guard assignComponents(
    &components.0,
    &components.1,
    &components.2,
    &components.3
  ) else {
    throw Error()
  }
  
  return `init`(components)
}

private struct Error: Swift.Error { }
func test_hsba() {
  let color = #colorLiteral(red: 0.25, green: 0.5, blue: 0.5, alpha: 0.5)
  
  let hsba = try! UIColor.HSBA(color)
  XCTAssertEqual(hsba.hue, 0.5)
  XCTAssertEqual(hsba.saturation, 0.5)
  XCTAssertEqual(hsba.brightness, 0.5)
  XCTAssertEqual(hsba.alpha, 0.5)
  XCTAssertEqual(UIColor(hsba), color)
  
  let rgba = try! UIColor.RGBA(color)
  XCTAssertEqual(rgba.red, 0.25)
  XCTAssertEqual(rgba.green, 0.5)
  XCTAssertEqual(rgba.alpha, 0.5)
  XCTAssertEqual(hsba.alpha, 0.5)
  
  XCTAssertEqual(UIColor(rgba), color)
}
  • Related