샘플 메모리
MemoryLayout<Int>.size // returns 8 (on 64-bit)
MemoryLayout<Int>.alignment // returns 8 (on 64-bit)
MemoryLayout<Int>.stride // returns 8 (on 64-bit)
MemoryLayout<Int16>.size // returns 2
MemoryLayout<Int16>.alignment // returns 2
MemoryLayout<Int16>.stride // returns 2
MemoryLayout<Bool>.size // returns 1
MemoryLayout<Bool>.alignment // returns 1
MemoryLayout<Bool>.stride // returns 1
MemoryLayout<Float>.size // returns 4
MemoryLayout<Float>.alignment // returns 4
MemoryLayout<Float>.stride // returns 4
MemoryLayout<Double>.size // returns 8
MemoryLayout<Double>.alignment // returns 8
MemoryLayout<Double>.stride // returns 8
struct EmptyStruct {}
MemoryLayout<EmptyStruct>.size // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride // returns 1
struct SampleStruct {
let number: UInt32
let flag: Bool
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8
class EmptyClass {}
MemoryLayout<EmptyClass>.size // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.stride // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.alignment // returns 8 (on 64-bit)
class SampleClass {
let number: Int64 = 0
let flag: Bool = false
}
MemoryLayout<SampleClass>.size // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.stride // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.alignment // returns 8 (on 64-bit)
// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count
// 2
do {
print("Raw pointers")
// 3
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
// 4
defer {
pointer.deallocate(bytes: byteCount, alignedTo: alignment)
}
// 5
pointer.storeBytes(of: 42, as: Int.self)
pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
pointer.load(as: Int.self)
pointer.advanced(by: stride).load(as: Int.self)
// 6
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
for (index, byte) in bufferPointer.enumerated() {
print("byte \(index): \(byte)")
}
}
- 이러한 상수들은 사용된 값들을 종종 들고 있다.
- count는 저장하기 위해 정수를 가지고 있다.
- stride는 Int 타입의 stride를 가지고 있다.
- alignment는 Int 타입의 alignment를 가지고 있다.
- byteCount는 필요한 모든 바이트 수를 가지고 있다.
- 스코프 레벨을 추가하기위해 do 블럭을 추가하였기 때문에, 나중에 예제에서 변수 이름을 재사용할 수 있다.
- UnsafeMutableRawPointer.allocate 메소드는 필요한 바이트를 할당하는데 사용한다. 이 메소드는 UnsafeMutableRawPointer를 반환한다. 저 타입의 이름에서 알 수 있듯, 포인터는 (가변의) Raw 바이트를 불러오고 저장하는데 사용될 수 있다.
- defer 블럭은 포인터가 적절하게 해제되었는지 확인하기위해 추가된 부분이다. 여기서 ARC는 도움이 되지 않는다. 여러분은 스스로 메모리 관리를 해야한다! 여기에서 defer에대해 더 읽어보길 바란다.
- storeBytes와 load 메소드는 바이트를 저장하고 불러오는데에 쓰인다. 두번째 정수의 메모리 주소는 그 포인터의 stride 바이트를 증가시켜서 계산된디.포인터는 stride 하기때문에, (pointer+stride).storeBytes(of: 6, as: Int:Self)로 포인터 계산 또한 할 수 있다.
- UnsafeRawBufferPointer는 메모리가 마치 바이트의 모음인것처럼 접근할 수 있게 해준다. 이 의미는 바이트들을 돌면서 차례로 접근할 수 있게 해주고, 서브스크립트를 이용해 접근하며 filter, map, reduce와같은 멋진 메소드까지 사용할 수 있게 한다. 이 버퍼 포인터는 raw 포인터를 초기화한다.
do {
print("Typed pointers")
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(to: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate(capacity: count)
}
pointer.pointee = 42
pointer.advanced(by: 1).pointee = 6
pointer.pointee
pointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
- UnsafeMutablePointer.allocate 메소드를 사용하여 메모리를 할당한다. 이 제네릭 파라미터는 포인터로 Int 타입의 값을 불러오고 저장하는데 사용될 것이라는 것을 스위프트에게 알려준다.
- typed 메모리는 사용하기전과 소멸하기전에 반드시 초기화되어야한다. 이것은 initialize 메소드와 deinitialize 메소드로 할 수 있다. Update: atrick라는 유저가 달아놓은 커멘트처럼 소멸은 non-trivial 타입들만 필요로 한다. 이 말은, 여러분이 non-trivial의 무언가를 바꿀 경우에 소멸을 가지고 있는 것이 여러분 코드의 훗날을 위해 좋은 방법이다. 또한 컴파일러가 이것을 최적화할것이기 때문에 항상 비용이 발생하지 않는다.
- typed 포인터는 pointee라는 프로퍼티를 가지고 있는데, 이것은 값을 불러오고 저장할때 타입 세이프한 방법을 제공하는 프로퍼티이다.
- typed 포인터를 증가시키면, 여러분의 숫자의 값을 원하는대로 증가해가며 나타낼 수 있다. 그 포인터는 그 타입의 포인터가 가리키는 값을 기반으로 옳바른 stride를 계산할 수 있다. 다시말해 포인터 계산 또한 가능하다. (pointer+1).pointee=6 이런것 또한 가능하다.
- typed 버퍼 포인터와 같은 점은, 바이트 대신에 값을 차례로 반복해갈 수 있다.
do {
print("Converting raw pointers to typed pointers")
let rawPointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
defer {
rawPointer.deallocate(bytes: byteCount, alignedTo: alignment)
}
let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
typedPointer.initialize(to: 0, count: count)
defer {
typedPointer.deinitialize(count: count)
}
typedPointer.pointee = 42
typedPointer.advanced(by: 1).pointee = 6
typedPointer.pointee
typedPointer.advanced(by: 1).pointee
let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
for (index, value) in bufferPointer.enumerated() {
print("value \(index): \(value)")
}
}
do {
print("Getting the bytes of an instance")
var sampleStruct = SampleStruct(number: 25, flag: true)
withUnsafeBytes(of: &sampleStruct) { bytes in
for byte in bytes {
print(byte)
}
}
}
do {
print("Checksum the bytes of a struct")
var sampleStruct = SampleStruct(number: 25, flag: true)
let checksum = withUnsafeBytes(of: &sampleStruct) { (bytes) -> UInt32 in
return ~bytes.reduce(UInt32(0)) { $0 + numericCast($1) }
}
print("checksum", checksum) // prints checksum 4294967269
}
// Rule 1
do {
print("1. Don't return the pointer from withUnsafeBytes!")
var sampleStruct = SampleStruct(number: 25, flag: true)
let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
return bytes // strange bugs here we come ☠️☠️☠️
}
print("Horse is out of the barn!", bytes) /// undefined !!!
}
// Rule 2
do {
print("2. Only bind to one type at a time!")
let count = 3
let stride = MemoryLayout<Int16>.stride
let alignment = MemoryLayout<Int16>.alignment
let byteCount = count * stride
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
let typedPointer1 = pointer.bindMemory(to: UInt16.self, capacity: count)
// Breakin' the Law... Breakin' the Law (Undefined behavior)
let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)
// If you must, do it this way:
typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) {
(boolPointer: UnsafeMutablePointer<Bool>) in
print(boolPointer.pointee) // See Rule 1, don't return the pointer
}
}
badpun
// Rule 3... wait
do {
print("3. Don't walk off the end... whoops!")
let count = 3
let stride = MemoryLayout<Int16>.stride
let alignment = MemoryLayout<Int16>.alignment
let byteCount = count * stride
let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount + 1) // OMG +1????
for byte in bufferPointer {
print(byte) // pawing through memory like an animal
}
}
import Foundation
import Compression
enum CompressionAlgorithm {
case lz4 // speed is critical
case lz4a // space is critical
case zlib // reasonable speed and space
case lzfse // better speed and space
}
enum CompressionOperation {
case compression, decompression
}
// return compressed or uncompressed data depending on the operation
func perform(_ operation: CompressionOperation,
on input: Data,
using algorithm: CompressionAlgorithm,
workingBufferSize: Int = 2000) -> Data? {
return nil
}
// Compressed keeps the compressed data and the algorithm
// together as one unit, so you never forget how the data was
// compressed.
struct Compressed {
let data: Data
let algorithm: CompressionAlgorithm
init(data: Data, algorithm: CompressionAlgorithm) {
self.data = data
self.algorithm = algorithm
}
// Compress the input with the specified algorithm. Returns nil if it fails.
static func compress(input: Data,
with algorithm: CompressionAlgorithm) -> Compressed? {
guard let data = perform(.compression, on: input, using: algorithm) else {
return nil
}
return Compressed(data: data, algorithm: algorithm)
}
// Uncompressed data. Returns nil if the data cannot be decompressed.
func decompressed() -> Data? {
return perform(.decompression, on: data, using: algorithm)
}
}
func perform(_ operation: CompressionOperation,
on input: Data,
using algorithm: CompressionAlgorithm,
workingBufferSize: Int = 2000) -> Data? {
// set the algorithm
let streamAlgorithm: compression_algorithm
switch algorithm {
case .lz4: streamAlgorithm = COMPRESSION_LZ4
case .lz4a: streamAlgorithm = COMPRESSION_LZMA
case .zlib: streamAlgorithm = COMPRESSION_ZLIB
case .lzfse: streamAlgorithm = COMPRESSION_LZFSE
}
// set the stream operation and flags
let streamOperation: compression_stream_operation
let flags: Int32
switch operation {
case .compression:
streamOperation = COMPRESSION_STREAM_ENCODE
flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
case .decompression:
streamOperation = COMPRESSION_STREAM_DECODE
flags = 0
}
return nil /// To be continued
}
// 1: create a stream
var streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
defer {
streamPointer.deallocate(capacity: 1)
}
// 2: initialize the stream
var stream = streamPointer.pointee
var status = compression_stream_init(&stream, streamOperation, streamAlgorithm)
guard status != COMPRESSION_STATUS_ERROR else {
return nil
}
defer {
compression_stream_destroy(&stream)
}
// 3: set up a destination buffer
let dstSize = workingBufferSize
let dstPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: dstSize)
defer {
dstPointer.deallocate(capacity: dstSize)
}
return nil /// To be continued
- compression_stream을 할당하고 소멸을위해 defer 블럭으로 준비한다.
- 그러고 pointee 프로퍼티를 사용하여 스트림을 가져와서, compression_stream_init 함수에 보내준다. 이 컴파일러는 여기서 좀 특별한 일을 한다. inout & maker를 이용하여 여러분의 compression_stream을 받아서 자동으로 UnsafeMutablePointer<compression_stream>으로 바꾼다.(streamPointer로 보낼 수도 있고 이 특정 변환을 필요로 하지 않을 수도 있다)
- 마지막으로 여러분이 작업할 버퍼인 목적지 버퍼를 생성한다.
// process the input
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
// 1
var output = Data()
// 2
stream.src_ptr = srcPointer
stream.src_size = input.count
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
// 3
while status == COMPRESSION_STATUS_OK {
// process the stream
status = compression_stream_process(&stream, flags)
// collect bytes from the stream and reset
switch status {
case COMPRESSION_STATUS_OK:
// 4
output.append(dstPointer, count: dstSize)
stream.dst_ptr = dstPointer
stream.dst_size = dstSize
case COMPRESSION_STATUS_ERROR:
return nil
case COMPRESSION_STATUS_END:
// 5
output.append(dstPointer, count: stream.dst_ptr - dstPointer)
default:
fatalError()
}
}
return output
}
- 아웃픗(압축된 데이터든 압축해제된 데이터든, 그 작업에따라 다르다)을 담은 Data 오브젝트를 생성한다.
- 당신이 할당한 포인터와 그 크기와함께 소스버퍼와 목적지버퍼를 설정한다.
- 그리고 COMPRESSION_STATUS_OK를 반환할때까지 계속 compression_stream_process를 호출한다.
- 목적지버퍼는 마침내 그 함수로부터 반환된 아웃풋으로 복사된다.
- 마지막 패킷이 들어오면, COMPRESSION_STATUS_END가 나오고, 오직 목적지버퍼의 부분만 잠재적으로 복사되는것이 필요하다.
hexdump
import Foundation
enum RandomSource {
static let file = fopen("/dev/urandom", "r")!
static let queue = DispatchQueue(label: "random")
static func get(count: Int) -> [Int8] {
let capacity = count + 1 // fgets adds null termination
var data = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
defer {
data.deallocate(capacity: capacity)
}
queue.sync {
fgets(data, Int32(capacity), file)
}
return Array(UnsafeMutableBufferPointer(start: data, count: count))
}
}
extension Integer {
static var randomized: Self {
let numbers = RandomSource.get(count: MemoryLayout<Self>.size)
return numbers.withUnsafeBufferPointer { bufferPointer in
return bufferPointer.baseAddress!.withMemoryRebound(to: Self.self, capacity: 1) {
return $0.pointee
}
}
}
}
Int8.randomized
UInt8.randomized
Int16.randomized
UInt16.randomized
Int16.randomized
UInt32.randomized
Int64.randomized
UInt64.randomized
- Swift Evolution 0107: UnsafeRawPointer API는 스위프트 메모리 모델의 개괄을 자세히 보여주었고, API 무서를 더 이해할 수 있게 도와줄 것이다.
- Swift Evolution 0138: UnsafeRawBufferPointer API는 untyped 메모리로 작업하는 것에 대해 확장하여 이야기하고, 이것을 사용할때의 이점에대한 오픈소스 프로젝트로 링크를 걸어두었다. 언세이프 코드를 스위프트3으로 바꾸려면 The Migration Guide를 확인해보자. 스위프트 버전 변경을 꼭 하지 않더라도 흥미로운 예제들이 있다.
- Interacting with C APIs는 스위프트가 어떻게 C와 소통할 수 있는지 통찰력을 줄것이다.
- Mike Ash는 Exploring Swift Memory Layout에대한 훌륭한 발표를 했다.
이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은
- 블로그 댓글
- 페이스북 페이지(@나는한다번역)
- 이메일(canapio.developer@gmail.com)
- 트위터(@canapio)
으로 보내주시면 됩니다.
'Swift와 iOS > Advanced Swift' 카테고리의 다른 글
[번역]옵셔널 프로퍼티 (0) | 2017.06.13 |
---|---|
[번역]스위프트에서 옳은 방법으로 실패 뽑아내기 (0) | 2017.06.12 |
[번역]여러분은 아마 enumerated 하고 싶지 않을 것이다 (0) | 2017.06.06 |
[번역]스위프트: guard와 if는 언제 사용할까 (0) | 2017.06.06 |
[번역]스위프트에서 네이밍에 관한 것들 (2) | 2017.05.13 |
WRITTEN BY
- tucan.dev
개인 iOS 개발, tucan9389