-1

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)

jnpdx
  • 45,847
  • 6
  • 64
  • 94
ios coder
  • 1
  • 4
  • 31
  • 91

2 Answers2

1

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.

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thanks, Frankly I never tried worked with `UnsafeMutablePointer`, You used the term of `As an optimization` You mean that `UnsafeMutablePointer` has optimized rather than just using `inout`? – ios coder Nov 07 '21 at 23:12
  • That term isn't mine -- I quoted from the documentation that I linked to. I don't take it as `UnsafeMutablePointer` "has optimized", but rather than a side-effect of using `inout` in Swift is that it ends up with some of the qualities of `UnsafeMutablePointer`. Keep in mind that `inout` is a Swift construct and `getRed` comes from UIKit which is Objective-C, C++, and C. – jnpdx Nov 07 '21 at 23:14
  • Regardless of the optimization, the answer to the original question is that the error message is different because they are two different scenarios -- one using `UnsafeMutablePointer` and one using `inout` -- see my last code sample. – jnpdx Nov 07 '21 at 23:22
  • I am a little confused about understanding of `inout` and `UnsafeMutablePointer`, I think the key point of my question depends on understanding those 2 things. why they are similar and why they are different. Which one is better for which work?! I think this informations I am missing. – ios coder Nov 07 '21 at 23:29
  • 1
    I've updated my answer to address these concerns as well. Let me know if anything needs clarification. – jnpdx Nov 07 '21 at 23:37
0

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)
}