테스트는 결과만 본다면, 어떤 행위의 결과가 기대하는 값과 동일한지 비교해보는 일입니다. 어떤 함수의 입력값이 1이면 기대값이 2일 때, 출력값을 2와 비교해서 맞는지 틀리는지 검증하는 방법이죠.
UIColor와 같은 경우에는 이게 좀 복잡해집니다. 색의 객체인 UIColor는 red, blue, green, alpha, 그리고 Color Space를 제공합니다. UIColor에서 제공하는 색이 디바이스의 디스플레이에서 동일하게 보이더라도 막상 객체를 비교하거나 색의 RGBA 값을 비교하면 기대하지 않는 결과를 만나는 경우가 생깁니다.
여기서부터는 이제 어떤걸 기대할지 선택해야 합니다. 절대적 값을 비교하느냐, 눈에 보이는 대로의 값을 비교하느냐죠.
UIView에서 사용하는 UIColor라면 눈에 보이는 대로의 값 비교가 충분하고 또 중요합니다. 여러분이 생성하거나 혹은 asset에서 가져오는 UIColor는 사실 단순한 UIColor가 아닐 수 있거든요. 그러면 UIColor에서 가져오는 색은 뭐가 문제인지, 눈에 보이는 색은 또 무엇인지, 비교할 때는 어떤 문제들이 발생하는지, 설명을 시작해보겠습니다.
*이 글의 모든 코드는 Xcode 11.5, iPhone Simulator 13.5를 기반으로 작성하고 테스트했습니다.
*약간의 어셈블리어도 만나게 됩니다.
UIColor의 레퍼런스 설명은 색에 대한 여러가지 용어를 사용하고 있습니다. 설명을 제대로 이해하기 위해서 다음의 세 가지 용어에 대해서 간단히 알고 가시면 좋을거 같습니다.
UIColor에서는 단순히 색모델 > 색공간 > 색역 순으로 색의 표현 범위가 좁아진다고 생각하셔도 괜찮을거 같습니다.
UIColor에서 색을 가져올 때는 다음과 같은 다섯가지 범주안에서 색을 가져오거나 만들게 됩니다.
붉은 색을 다음과 같이 여러가지 방식으로 만들어보겠습니다.
let red = UIColor.red
let redRGB = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
let redDP3RGB = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
let redHSB = UIColor(hue: 0.0, saturation: 1.0, brightness: 0.5, alpha: 1.0)
let redAsset = UIColor(named: "red")
테스트를 할 때 중요한 것중에 하나는 기대하는 값이 맞는지를 검증하는 한편 기대하지 않은 값이 나오면 틀릴 수 있는지도 검증할 수 있어야 한다는 것입니다.
red1과 red2를 비교하면 사실 당연히 true값을 기대를 할 수 있습니다. 다만 == 함수가 실제로 무엇을 가지고 비교하는지는 의심해볼 가치가 있습니다. 혹시나 타입으로 비교할 수도 있는것 아니겠습니까?(혹시나 말이죠)
그러니 실제 색의 값을 비교하는 것이 맞는지 먼저 확인을 하기 위해 red1과 조금 다른 색을 먼저 비교해보겠습니다.
red1 == UIColor(red: 1.0, green: 0.1, blue: 0.0, alpha: 0.0)
의 결과는
false
입니다.
그리고
red1 == red2
의 결과는
true
입니다. 값을 비교하고 있는것이 확실해졌습니다.
나머지를 비교해보겠습니다. 여기서부터는 비교할 때 XCTest의 XCTAssertEqual등의 테스트 함수를 이용해서 테스트 환경에서 비교해보겠습니다. 테스트니 테스트 함수를 사용하는 것도 있지만, 이 함수들은 기대하던 결과가 아닐때 각 오브젝트의 description을 표시해주기 때문에 살펴보기가 편한것이 이유입니다.
*참고로 객체를 문자열로 변환할 때 CustomStringConvertible과 CustomDebugStringConvertible 프로토콜을 구현하고 있는 경우 CustomStringConvertible구현을 우선시하게 됩니다. 즉, XCTAssertXXX 함수가 알려주는 메시지는 일반적으로 객체의 description 프로퍼티가 반환하는 값을 이용하게 됩니다. 이 설명을 하는 이유는 이 글을 계속 보시다보면 알게 됩니다.
XCTAssertEqual(red, redRGB) - 성공입니다.
XCTAssertEqual(red, redDP3RGB) - 실패입니다.
XCTAssertEqual(red, redHSB) - 성공입니다.
XCTAssertEqual(red, redAsset) - 실패입니다. ??
저는 여기서 몇 가지 궁금증이 생겼습니다.
첫번째, 실제로 색은 어떻게 비교하고 있을까?
두번째, redDP3RGB, redHSB, redAsset은 색을 어떻게 들고 있을까?
그 전에 하나 짚고 넘어갈 게 있습니다. 아시는 분들도 많이 계실거라 생각하는데, 사실 UIColor는 UIColor가 아닙니다.
지금 만든 색들의 타입을 한 번 살펴보겠습니다.
타입의 상속관계를 출력하기 위해 다음과 같은 함수를 만들어 봤습니다.
func types<O>(of: O) -> String {
var m = Mirror(reflecting: of)
var types: [String] = ["\(m.subjectType)"]
while let mirror = m.superclassMirror {
types.append("\(mirror.subjectType)")
m = mirror
}
return types.joined(separator: " -> ")
}
그리고 준비한 UIColor들을 다 출력해보겠습니다.
red, redRGB, redDP3RGB, redHSB, redAsset의 차례로 다음과 같은 결과가 나옵니다.
"UICachedDeviceRGBColor -> UIDeviceRGBColor -> UIColor -> NSObject" <- red
"UIDeviceRGBColor -> UIColor -> NSObject" <- redRGB
"UIDisplayP3Color -> UIColor -> NSObject" <- redDP3RGB
"UIDeviceRGBColor -> UIColor -> NSObject" <- redHSB
"UICGColor -> UIColor -> NSObject" <- redAsset
네 그 동안 우리는 속아왔습니다. 순수한 UIColor는 존재하지 않았던 것입니다. 이것을 알아야 하는 이유는 실제로 색을 비교하기 위해 NSObject를 상속하는 객체들이 isEqual을 사용하는데, 이 내부를 들여다보려면 실제 객체들의 메소드 심볼에 브레이크 포인트를 걸어야 하기 때문입니다. UIColor.isEqual에 백날 걸어도 각각의 UIColor를 상속하고 있는 private 객체들이 저마다의 isEqual을 구현하고 super의 isEqual은 사용하지 않기 때문에 알 수가 없습니다. (하여간 좋은건 자기네만 쓰지)
그럼 갑니다.
먼저 red, redRGB를 비교하는 부분을 살펴보기 위해 UIDeviceRGBColor의 isEqual 메소드에 symbolic breakpoint를 걸어보겠습니다.
Xcode Menu > Debug > Breakpoints > Create Symbolic Breakpoint를 선택해주세요. 그리고 다음과 같이 입력하시면 됩니다.
그리고 다음의 코드를 실행할 수 있게 작성해서 실행해봅니다.
red == redRGB
*raywenderlich의 Assembly Register Calling Convention Tutorial를 읽어보시는것도 좋습니다.
이제 UIDeviceRGBColor 클래스의 isEqual 함수의 frame에서 멈추고 disassembly code가 나타납니다.
저는 아직 이들의 의미와 논리를 잘 알지못합니다. 하지만 심볼정보 등 알아볼 수 있는 정보들도 있고, 알고 있는 몇개의 assembly instruction과 lldb 디버그 명령어를 이용해서 하나하나 추적해보겠습니다. *메모리 주소와 Objective C reference count와 관련한 코드는 생략합니다.
UIKitCore`-[UIDeviceRGBColor isEqual:]:
... 1
0x? <+?>: movq 0x?(%rip), %rbx ; "colorSpaceName"
0x? <+?>: movq 0x?(%rip), %r14 ; (void *)0x?: objc_msgSend
0x? <+?>: movq %r12, %rdi
0x? <+?>: movq %rbx, %rsi
0x? <+?>: callq *%r14
... 2
0x? <+?>: movq %rax, %rbx
0x? <+?>: movq 0x?(%rip), %rsi ; "isEqualToString:"
0x? <+?>: movq %r13, %rdi
0x? <+?>: movq %rax, %rdx
0x? <+?>: callq *%r14
0x? <+?>: movl %eax, %r14d
0x? <+?>: movq %rbx, %rdi
0x? <+?>: callq *0x?(%rip) ; (void *)0x?: objc_release
0x? <+?>: testb %r14b, %r14b
0x? <+?>: je 0x? ; <+284>
1번 블록은 이름만 봐도 colorSpace의 이름을 가져올거 같습니다.(하여간 좋은건 자기네만 쓰지).
잠깐 UIDeviceRGBColor에 해당 메소드가 구현되어있는지 확인해보겠습니다. lldb에는 정규표현식으로 심볼 정보를 가져올 수 있는 명령어가 있습니다.
lldb) image lookup -rn 'UIDeviceRGBColor\ colorSpaceName'
실행하시면 다음과 같은 결과를 보실 수 있습니다.
/Applications/Xcode.app/.../UIKitCore.framework/UIKitCore:
Address: UIKitCore[0x?] (UIKitCore.__TEXT.__text + ?)
Summary: UIKitCore`-[UIDeviceRGBColor colorSpaceName]
대충 봐도 아 그렇구나 하고 이해가 가실듯 합니다.
그럼 인스트럭션을 하나씩 아는 한에서 분석해보겠습니다.
UIKitCore`-[UIDeviceRGBColor isEqual:]:
... 1
0x1 <+?>: movq 0x?(%rip), %rbx ; "colorSpaceName" // rbx 레지스터에 colorSpaceName 셀렉터의 주소를 옮깁니다. rip어드레스는 프레임에서 instruction명령의 주소를 담고 있습니다. 0x?(%rip)의 의미는 rip(다음 명령줄의 주소값 = 0x2)에 0x?를 더한 만큼의 주소값이고 movq instruction은 그 주소를 rbx 레지스터에 옮라는 명령입니다.
0x2 <+?>: movq 0x?(%rip), %r14 ; (void *)0x?: objc_msgSend // r14 레지스터에 objc_msgSend 함수의 주소를 옮깁니다. 이 함수는 이름만 봐도 Objective C 객체에 메시지(셀렉터 정보)를 전달할 수 있을거 같죠?
0x3 <+?>: movq %r12, %rdi // r12 레지스터에는 생성한 UIDeviceRGBColor의 주소가 들어가있습니다. rdi레지스터는 objc_msgSend에 전달할 첫번째 인수를 담는 레지스터입니다.
0x4 <+?>: movq %rbx, %rsi // rsi인자는 두번째 인수입니다. rbx 레지스터에는 colorSpaceName 셀렉터가 담겨 있고, 이 명령을 통해 rsi 레지스터로 옮겨집니다.
0x5 <+?>: callq *%r14 // call(q) instruction은 레지스터 주소의 프로시저를 호출합니다. 이 경우 objc_msgSend를 호출하게 되죠. 참고로 objc_msgSend는 객체, 셀렉터, 기타 인수들의 variadic arguments의 차례로 인수를 가집니다.
... 2
0x6 <+?>: movq %rax, %rbx
0x7 <+?>: movq 0x?(%rip), %rsi ; "isEqualToString:"
0x8 <+?>: movq %r13, %rdi
0x9 <+?>: movq %rax, %rdx
0x10 <+?>: callq *%r14
0x11 <+?>: movl %eax, %r14d
... 3
0x12 <+?>: testb %r14b, %r14b // r14 레지스터의 마지막 1바이트값을 and 연산하고 그 결과를 rflags(제어 레지스터)에 저장합니다. 이 경우 연산 결과가 0이면 rflags에 ZF(zero flag)를 1로 저장하고 그 외에는 0으로 저장합니다.
0x13 <+?>: je 0x주소1 ; <+284> // ZF가 1이면 주소1으로 점프하고 아니라면 다음 인스트럭션인 0x14로 이동합니다.
0x14 <+159>: movq 0x?(%rip), %rax ; UIDeviceRGBColor.redComponent
0x? <+?>: movq 0x?(%rip), %rax ; UIDeviceRGBColor.redComponent
0x? <+?>: movsd (%r15,%rax), %xmm0 ; xmm0 = mem[0],zero
0x? <+?>: ucomisd (%r12,%rax), %xmm0
0x? <+?>: jne 0x7fff4885be98 ; <+459>
0x? <+?>: jp 0x7fff4885be98 ; <+459>
0x? <+?>: movq 0x40f6200e(%rip), %rax ; UIDeviceRGBColor.greenComponent
0x? <+?>: movsd (%r15,%rax), %xmm0 ; xmm0 = mem[0],zero
0x? <+?>: ucomisd (%r12,%rax), %xmm0
0x? <+?>: jne 0x7fff4885be98 ; <+459>
0x? <+?>: jp 0x7fff4885be98 ; <+459>
0x? <+?>: movq 0x40f61ff7(%rip), %rax ; UIDeviceRGBColor.blueComponent
0x? <+?>: movsd (%r15,%rax), %xmm0 ; xmm0 = mem[0],zero
0x? <+?>: ucomisd (%r12,%rax), %xmm0
0x? <+?>: jne 0x7fff4885be98 ; <+459>
0x? <+?>: jp 0x7fff4885be98 ; <+459>
0x? <+?>: movq 0x40f61fe0(%rip), %rax ; UIDeviceRGBColor.alphaComponent
0x? <+?>: movsd (%r12,%rax), %xmm0 ; xmm0 = mem[0],zero
0x? <+?>: jmp 0x7fff4885be87 ; <+442>
...
0x? <+?>: retq
이 블럭에서는 red1과 redRGB의 red, green, blue, alpha값을 비교하게 됩니다. 모든 값이 같으면 최종적으로 rax레지스터에 0x01 즉 true값을 저장하고 ret(q) instruction을 통해 현재 frame에서 나가게 됩니다.
'중간에 디테일 좀 빠진거 같습니다만?'
(그냥 이 이미지를 써보고 싶었습니다.) 어셈블리만 너무 파고들면, 글의 주제가 멀어지는거 같고, 디버깅시 만나게 되는 어셈블리에 대해서 저도 공부를 더 하고 따로 글을 써보려고 합니다. 이 정도면 어느 정도는 디버깅에 도움이 되시지 않을까 싶어요.
어쨌건, 이로써 색공간의 이름의 문자열과 red, green, blue, alpha의 값을 비교한다는 것을 알게 되었습니다.
위에서 본 내용을 되짚어보면
let red = UIColor.red
let redRGB = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
let redDP3RGB = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
let redHSB = UIColor(hue: 0.0, saturation: 1.0, brightness: 0.5, alpha: 1.0)
let redAsset = UIColor(named: "red")
먼저 redAsset의 색공간을 출력해보겠습니다.
print(redAsset.cgColor.colorSpace.name)
결과는
kCGColorSpaceModelRGB
가 되네요. 아하 처음에 어셋을 생성할 때 기본 색공간이 SRGB라고 했던것을 기억하실수 있으실겁니다. 색공간이 달라 false가 나왔을 겁니다. 색공간을 red와 동일하게 바꾸면 true가 나오겠네요.
print(red == redAsset) 실행해보면
true 를 출력합니다.
뭔가 싱겁네요?
네 이 정도로 끝나면 제가 이 글을 쓸 일은 없었을 겁니다.
그러면 살짝 코드 도약을 해보겠습니다.
먼저 redRGB를 다음과 같이 수정합니다.
let redRGB = UIColor(red: 0.9, green: 0.0, blue: 0.0, alpha: 1.0)
그리고 redAsset의 red를 1.0에서 0.9로 다음과 같이 수정합니다.
이 두 색을 다음과 같이 테스트 케이스에서 비교해보겠습니다.
XCTAssertEqual(redRGB, redAsset)
그리고 테스트를 실행하면 다음과 같이 실패하게 됩니다.
XCTAssertEqual failed: ("UIExtendedSRGBColorSpace 0.9 0 0 1") is not equal to ("UIExtendedSRGBColorSpace 0.9 0 0 1")
뭘까요, 같다면서 틀리다는 이 결과는?
일단 각 색의 실제 값을 출력해보겠습니다.
결국 실제값이 다름에도 불구하고 description에서는 red의 값을 둘다 동일하게 0.9로 표시하고 있습니다.
UIColor의 description에서는 색을 출력할 때, 좀 다른 방식을 쓰고 있는거 같군요. 이번에는 UIDeviceRGBColor의 description에 심볼릭 브레이크포인트를 걸어보겠습니다.
UIKitCore`-[UIDeviceRGBColor description]:
-> 0x? <+?>: pushq %rbp
0x? <+?>: movq %rsp, %rbp
0x? <+?>: pushq %r15
0x? <+?>: pushq %r14
0x? <+?>: pushq %r12
0x? <+?>: pushq %rbx
0x? <+?>: movq %rdi, %rbx
0x? <+?>: movq 0x40f488b1(%rip), %r14 ; (void *)0x00007fff87b502e8: NSString
0x? <+?>: movq 0x40f1a91a(%rip), %rsi ; "colorSpaceName"
0x? <+?>: movq 0x3e051c93(%rip), %r12 ; (void *)0x00007fff50ba4400: objc_msgSend
0x? <+?>: callq *%r12
0x? <+?>: movq %rax, %rdi
0x? <+?>: callq 0x7fff49241ca0 ; symbol stub for: objc_retainAutoreleasedReturnValue
0x? <+?>: movq %rax, %r15
0x? <+?>: movq 0x40f621ce(%rip), %rax ; UIDeviceRGBColor.redComponent
0x? <+?>: movsd (%rbx,%rax), %xmm0 ; xmm0 = mem[0],zero
0x? <+?>: movq 0x40f621ca(%rip), %rax ; UIDeviceRGBColor.greenComponent
0x? <+?>: movsd (%rbx,%rax), %xmm1 ; xmm1 = mem[0],zero
0x? <+?>: movq 0x40f621c6(%rip), %rax ; UIDeviceRGBColor.blueComponent
0x? <+?>: movsd (%rbx,%rax), %xmm2 ; xmm2 = mem[0],zero
0x? <+?>: movq 0x40f621c2(%rip), %rax ; UIDeviceRGBColor.alphaComponent
0x? <+?>: movsd (%rbx,%rax), %xmm3 ; xmm3 = mem[0],zero
0x? <+?>: movq 0x40ef404e(%rip), %rsi ; "stringWithFormat:"
0x? <+?>: leaq 0x3e0ae7df(%rip), %rdx ; @"%@ %g %g %g %g"
뭔가 많이 보이지만, 짧게 요약해보면 다음과 같은 코드가 됩니다.
return [NSString stringWithFormat:@"%@ %g %g %g %g", (colorSpaceName), redComponent, greenComponent, blueComponent]
0.8999999761581421 가 0.9로 보이던 마법은 %g 문자열 포맷이 이유였군요. (참고 String Format Specifiers)
한 번, 소숫점을 출력하는 모든 문자열 포맷으로 출력해보겠습니다.
0.8999999761581421
%f -> 0.900000
%e -> 9.000000e-01
%E -> 9.000000E-01
%g -> 0.9
%G -> 0.9
%a -> 0x1.ccccccp-1
%A -> 0X1.CCCCCCP-1
%F -> 0.900000
뭘 쓰던 a, A를 제외하면 0.9로 출력을 합니다
아까 어셋을 바꿀때 red 색을 0.900으로 바꿨습니다. 혹시 색이 0.8xxxx로 들어간것일까요? 확인해보겠습니다.
asset의 red색을 파인더로 찾아가게 되면, Contents.json 파일이 보입니다. 들여다보면 다음과 같네요.
{
"colors" : [
{
"color" : {
"color-space" : "extended-srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.900"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
여기서는 문제가 없습니다.
저는 이 케이스를 만났을 때, 어셋에서 색을 불러올 때 실수 변환의 문제가 있을거라 추측했습니다.
자 그러면, 또 다시 어셈블리의 세상으로 들어가 보겠습니다.
먼저 +[UIColor colorNamed:]
로 심볼릭 브레이크포인트를 걸어보겠습니다.
0x? <+?>: movq 0x?(%rip), %rsi ; "colorNamed:inBundle:compatibleWithTraitCollection:"
...
0x? <+?>: jmpq *0x?(%rip) ; (void *)0x?: objc_msgSend
그러면 해당 클래스 메소드가 다시 클래스 메소드 colorNamed:inBundle:compatibleWithTraitCollection:
를 호출하는 것을 알 수 있습니다.
+[UIColor colorNamed:inBundle:compatibleWithTraitCollection:]
에 심볼릭 브레이크 포인트를 걸어보겠습니다.
그러면 나오는 어셈블리 코드 라인에서 다음이 색을 가져올거라는 것을 쉽게 추측이 가능합니다.
0x? <+?>: movq 0x?(%rip), %rsi ; "colorNamed:withTraitCollection:"
이때, 해당 메소드를 가지고 있는 객체를 레지스터에서 찾아보면, _UIAssetManager
라는 클래스에서 호출하는 것을 알 수 있습니다. 이제부터는 private class를 추적하게 됩니다.
-[_UIAssetManager colorNamed:withTraitCollection:]
로 심볼릭 브레이크 포인트를 걸어봅시다.
UIKitCore`-[_UIAssetManager colorNamed:withTraitCollection:]:
...
0x7fff49039cd0 <+210>: movq 0x40760e21(%rip), %rsi ; "colorWithName:displayGamut:deviceIdiom:"
0x7fff49039cd7 <+217>: movq %r14, %rdx
0x7fff49039cda <+220>: movq %rbx, %rcx
0x7fff49039cdd <+223>: callq *0x3d873b65(%rip) ; (void *)0x00007fff50ba4400: objc_msgSend
colorWithName:displayGamut:deviceIdiom:
메소드는 CUICatalog
의 메소드입니다. 이 메소드는 CUINamedColor
객체를 생성하고 그 객체에서는 CGColor를 가지고 있습니다. 이 CGColor 구조체를 메모리에서 64비트 실수값으로 읽어보면 많은 숫자들이 나오지만, 중요한 부분만 남겨 보겠습니다.. (*참고 CUINamedColor.h)
0.8999999761581421 -> 0 0 0 C0 CC CC EC 3F
0.9 -> CD CC CC CC CC CC EC 3F
메모리에서 실수를 출력할 명령은 다음과 같습니다.
lldb) memory read <address> -f float64
출력은 다음과 같습니다.
... 0.899999976158142 0 0 1 ....
rgba의 색이 제대로 들어가 있다는 것을 확인할 수 있습니다.
저와 여러분을 괴롭히는 일은 이쯤에서 그만두는게 좋겠네요. CUICatalog 에서 더 들어가면 private framework인 Bom.framework(참조: Reverse engineering the .car file format (compiled Asset Catalogs)의 함수들을 사용하여 Assets.car 파일에서 색 데이터를 추출하는데까지 도달하는데, 이때 red의 값은 0.8999999761581421 -> 0 0 0 C0 CC CC EC 3F
로 넘어옵니다.
이유는 Xcode에서 asset을 컴파일하는데 사용하는 actool이 이미 0.9를 0.89999997615814209로 저장해버리기 때문입니다.
생성한 앱 파일에서 Assets.car 파일을 찾아서 다음 명령을 통해 출력해보겠습니다.
> assetutil -I Assets.car
[
{
"Appearances" : {
"UIAppearanceAny" : 0
},
"AssetStorageVersion" : "Xcode 11.5 (11E608c) via IBCocoaTouchImageCatalogTool",
"Authoring Tool" : "@(#)PROGRAM:CoreThemeDefinition PROJECT:CoreThemeDefinition-447.1\n",
"CoreUIVersion" : 609,
"DumpToolVersion" : 609.4,
"Key Format" : [
"kCRThemeScaleName",
"kCRThemeIdentifierName",
"kCRThemeElementName",
"kCRThemePartName"
],
"MainVersion" : "@(#)PROGRAM:CoreUI PROJECT:CoreUI-609.4\n",
"Platform" : "ios",
"PlatformVersion" : "13.5",
"SchemaVersion" : 2,
"StorageVersion" : 17,
"ThinningParameters" : "optimized <idiom 1> <subtype 569> <scale 2> <gamut 1> <graphics 7> <graphicsfallback (6,5,4,3,2,1,0)> <memory 4> <deployment 5> <hostedIdioms (4)>",
"Timestamp" : 1592676227
},
{
"AssetType" : "Color",
"Color components" : [
0.89999997615814209,
0,
0,
1
],
"Colorspace" : "extended srgb",
"Name" : "red",
"NameIdentifier" : 40330,
"Scale" : 1
}
]
하하하. 저는 actool을 생각하지 못하고 일주일정도 꼬박 퇴근 후 남는 시간을 디버깅에 투자했는데, 정말 얼마나 허무하던지요.
하지만, 덕분에 저는 디버깅시에 이전 보다는 좀 더 코드를 읽기가 수월해질거 같습니다.
UIColor를 테스트하다 여기까지 왔습니다. 결국 테스트를 어떻게 하느냐 결론을 내야할텐데, 결론적으로 말하자면 비교시에는 display p3 색공간으로 색을 변환해서 비교하면 됩니다.
UIColor에 색을 Display P3 색공간으로 변환하는 프로퍼티를 extension으로 추가했습니다.
그리고 비교시에 해당 프로퍼티를 사용하도록 하면,
extension UIColor {
var dp3: UIColor {
let converted = self.cgColor.converted(to: CGColorSpace(name: CGColorSpace.displayP3)!, intent: .defaultIntent, options: nil)!
let rgba = converted.components!
return UIColor(displayP3Red: rgba[0], green: rgba[1], blue: rgba[2], alpha: rgba[3])
}
}
...
XCTAssertEqual(redRGB.dp3, redAsset.dp3)
테스트 케이스가 드디어 성공합니다.
테스트 하나 성공시키기 위해 꽤 돌아왔습니다. 여기까지 봐주신 분들에게 고맙습니다. 일하고 공부하면서 짬짬이 쓰느라 두 주가 조금 넘게 걸렸는데, 누군가에게는 조금이라도 유용한 글이었으면 싶습니다.
오류 지적이나 좋은 정보, 개선 사항은 얼마든지 환영합니다.
이 다음에는 또 뭘 분석해볼까 찾아봐야겠네요.
좋아요
를 누르기 위해 가입하고 왔습니다.테스트가 잘 안되네?! 하고 넘겼는데 이런 분석을..!
멋진 글이네요!