Unsigned Integer to String with Generics
ProgrammingSwift를 사용해서 부호없는 십진수를 십육진수 형태의 문자열로 바꾸는 Generics 함수를 작성한 내용을 기록한다.
Swift의 포인터를 공부하다가 십진수를 십육진수로 바꾸어 1바이트 단위로 보는 기능이 필요했다. 인터넷을 참고하여 String 클래스의 메소드를 사용하면 Decimal을 Hex로 바꿀 수 있었다.
http://stackoverflow.com/questions/24229505/how-to-convert-an-int-to-hex-string-in-swift
let dec = 10 let hex = String(dec, radix:16) print("0x\(hex)") // 0xa
그런데 매번 이렇게 정수를 문자열로 바꾸어주는 표현식을 적기가 귀찮아서 함수로 만들어 보기로 했다.
func toHex(_ num: Int) -> String { let hex = String(num, radix: 16) return "0x\(hex)" } let dec = 10 print("\(toHex(dec))") // 0xa
이 함수를 가지고 아래와 같이 1바이트 부호없는 정수와 8바이트 부호없는 정수의 값을 찍어보았다. 당연히 타입이 안 맞으니 에러가 나왔다.
let uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 8) uint8Pointer.initialize(from: [0x37, 0x77, 0x11, 0x11, 0x02, 0x33, 0x39, 0x00]) let uint64Pointer = UnsafeMutableRawPointer(uint8Pointer).bindMemory(to: UInt64.self, capacity: 1) let rawPointer = UnsafeMutableRawPointer(uint64Pointer) var fullInteger = rawPointer.load(as: UInt64.self) var firstByte = rawPointer.load(as: UInt8.self) func toHex(_ num: UInt64) -> String { let hex = String(num, radix: 16) return "0x\(hex)" } print("8byte: \(toHex(fullInteger))") print("1byte: \(toHex(firstByte))")
./pointer_test3.swift:22:23: error: cannot convert value of type 'UInt64' to expected argument type 'Int' print("8byte: \(toHex(fullInteger))") ^~~~~~~~~~~ Int( ) ./pointer_test3.swift:23:23: error: cannot convert value of type 'UInt8' to expected argument type 'Int' print("1byte: \(toHex(firstByte))") ^~~~~~~~~ Int( )
그럼 타입을 맞춰주면 된다. 전달인자 목록의 Int 대신에 UInt를 넣어서 다시 해본다. 그렇지만 역시 에러가 나온다. UInt가 내부적으로 4바이트이면 UInt8, UInt64 어느 쪽과도 타입이 맞지 않는다. 그러면 타입을 맞추기 위해 함수를 2개 만들어야 할까? 2바이트, 4바이트 데이터에 대응하기 위해서는 4개를 만들어야 한다.
코드중복 문제를 해결하기 위해 Generics 라는걸 써보기로 했다. 'Generics는 C++ 템플릿 비슷한 것이니 전달인자 타입을 대충 T로 우겨 넣고 컴파일하면 되겠지'라고 생각하고 함수를 다시 작성해 보았다.
func toHex<T>(_ num: T) -> String { let hex = String(num, radix: 16) return "0x\(hex)" }
./pointer_test3.swift:18:15: error: cannot invoke initializer for type 'String' with an argument list of type '(T, radix: Int)' let hex = String(num, radix: 16) ./pointer_test3.swift:18:15: note: expected an argument list of type '(T, radix: Int, uppercase: Bool)' let hex = String(num, radix: 16)
이번에는 String 클래스의 생성자가 동작을 안했다. 함수 밖에서와 같은 내용일텐데 함수 안에서는 제대로 동작하지 않는 걸까? Swift의 Generics는 C++의 템플릿과는 달리 타입 체킹을 더 엄격히 할 수 있다고 한다. T로 정의된 타입이 들어와도 이게 String 클래스의 생성자에 정의된 제약 조건에 의해서 아무 T나 받지 못하게 되어 있던 것이다.
https://developer.apple.com/reference/swift/string/1641688-init
String 클래스의 해당 생성자 문서를 보면 메소드가 아래와 같이 where로 지정된 제약 조건이 걸려 있다.
init<T>(_ value: T, radix: Int = default, uppercase: Bool = default) where T : UnsignedInteger
즉, T 타입은 아무 타입이 아니라 UnsignedInteger 여야 하는 것이다. 따라서 toHex 함수에도 똑같이 제약 조건을 달아주어야 한다.
func toHex<T>(_ num: T) -> String where T : UnsignedInteger { let hex = String(num, radix: 16) return "0x\(hex)" } print("8byte: \(toHex(fullInteger))") // 8byte: 0x39330211117737 print("1byte: \(toHex(firstByte))") // 1byte: 0x37
그러면 부호있는 정수를 Hex로 바꾸려면 어떻게 해야할까? 아쉽게도 이 경우는 함수를 하나 더 작성해야 한다. UnsignedInteger를 _SignedInteger 라는 제약 조건으로 대체하여 같은 함수를 하나 더 만들면 된다. String 생성자도 이런 코드 중복이 보인다. 어쩔 수 없는 듯.
https://developer.apple.com/reference/swift/string/1640980-init
'Programming' 카테고리의 다른 글
macOS에 emacs ggtags 설치 및 설정 (0) | 2017.10.17 |
---|---|
Xcode에 assimp 올리기 (0) | 2017.06.06 |
OpenGL로 원 그리기 (1) | 2017.05.27 |
Swift3 - result unused warning 없애기 (0) | 2017.05.23 |
Swift - 튜플에 포인터로 접근하기 (0) | 2017.05.14 |