콘텐츠로 건너뛰기
Home » 스터디 » Page 4

스터디

Raw 포인터 사용에 대해

https://developer.apple.com/documentation/swift/unsaferawpointer

UnsafeRawPointer 타입은 자동 메모리 관리, 타입 안정성 및 메모리 정렬 보장이 되지 않는 원시 포인터 액세스를 제공합니다. 이 타입을 사용하려면 누수를 피하고, 할당된 메모리의 라이프 사이클을 직접 관리해야 하며, 그 외의 정의되지 않는 동작들을 회피해야 합니다. 수동으로 직접 관리하는 메모리 영역은 특정한 타입에 바운드되거나, 타입이 지정되지 않을 수도 있습니다. 메모리 영역에서 해당 영역이 특정 타입에 묶여있는지 여부와 무관하게 순수 바이트를 액세스하려할 때 UnsafeRawPointer 타입을 사용할 수 있습니다.

막 할당된 Raw 메모리는 타입화되지도 초기화되지도 않은 상태입니다. 이 메모리는 타입화된 연산을 사용하기 전에 반드시 초기화되어야 합니다. (초기화되려면 초기값을 가져야하고, 이는 타입화를 수반해야한다는 의미가 됩니다.) 초기화되지 않은 상태에서 특정 타입에 바인등하려면 bindMemory(to: count:)를 사용합니다. 이 메소드는 타입화된 포인터를 반환하며, 이후에는 해당 포인터를 사용해야 합니다.

더 보기 »Raw 포인터 사용에 대해

버퍼 포인터 이해하기

C에서 특정한 T타입의 배열은 메모리 상에서 연속적인 공간입니다. 이 때문에 정적 배열이든 동적 배열이든 배열을 액세스하는 것은 필연적으로 포인터와 관련됩니다. 반면 Swift의 배열에서 원소들은 반드시 이런 식으로 배치되지는 않습니다. C의 배열이 단지 원소값이 나란히 배치된 메모리 영역임에 비해 Swift의 배열은 struct로 구성되는 보다 복잡한 내부 구조를 가지고 있습니다.

이 때 T 타입이 차지하는 바이트 수가 고정되어 있으므로 배열의 시작번지와 인덱스 값을 알고 있다면 해당 인덱스에 위치한 값을 액세스할 수 있습니다. C에서 배열 이름은 암묵적으로 배열의 시작번지를 의미하므로, arr[i]로 표현되는 i 번째 원소의 값은 실제 컴파일러는 *(arr + i) 로 변환하여 접근합니다.

Swift에서도 UnsafePointer를 사용하여 포인터를 다룰 수 있는데, 이 때 범위(capacity, Pointee 타입의 메모리 사이즈 x 원소의 개수)내에는 동일타입을 구성하는 값들이 연속하여 배치되어 있습니다. 따라서 (ptr + i).pointee 와 같은 식으로 i 번째 원소에 대해 액세스가 가능합니다. 이것은 C의 접근방법과 매우 유사합니다. 하지만 이것은 단순한 메모리 연속체에서 특정 지점을 액세스하는 법일 뿐, Swift의 배열을 다루는 것과는 차이가 있습니다. Swift의 배열은 원소가 연속해있으면서 Sequence, Collection 프로토콜에 의한 여러가지 연산을 지원받습니다.

더 보기 »버퍼 포인터 이해하기

Unmanaged 에 대해 – Swift

그 옛날(?) ARC가 없던 시절에 Objective-C 및 코어 파운데이션에서 객체에 대한 참조수 관리는 완전한 수동 방식에 의존하고 있다. 어떤 객체에 대한 retain(참조수를 늘리는 것) 동작은 반드시 그에 수반하는 release(참조수를 내리는 것) 동작을 필요로 했다. 그리고 이 짝이 제대로 맞지 않으면 객체는 필요한 시점에 사라지고 없거나, 반대로 메모리 누수가 발생했다. 그러던 중 자동 참조수 카운팅(ARC)이 도입되었는데, ARC 환경에서는 모든 retain/release/autorelease 콜이 컴파일러에 의해 코드에 자동으로 삽입되었다. 이는 전체적인 코드량의 감수는 물론 개발 난이도를 매우 낮춰주는 역할을 했다.

ARC가 도입된 이후 Objective-C 메소드에 의해 반환되는 모든 Objective-C 객체와 코어 파운데이션 객체의 메모리 관리는 자동으로 이루어졌다. 하지만 C 함수에 의해 리턴되는 코어 파운데이션 객체는 이러한 은혜를 받지 못했다. 따라서 여기에 속하는 객체들에 대해서는 여전히 CFRetain(), CFRelease()를 호출하거나, __bridge*로 시작하는 함수를 통해 Objecitve-C 객체로 브릿징해야 했다.

더 보기 »Unmanaged 에 대해 – Swift

포인터 관련 메모

타입화된 포인터

Swift는 C API와의 상호작용 혹은 고성능 자료구조의 구현등을 위해 포인터를 통한 메모리 액세스를 제한적으로 지원하고 있습니다. 기본적으로 Swift 타입 혹은 Swift 에서 인식할 수 있는 C 타입에 대한 포인터는 ‘타입화된(typed)’ 포인터라고 하며, UnsafePointer를 사용합니다. UnsafePointer는 제네릭 struct 타입으로 특정 Swift 타입에 대한 포인터로 기능합니다. UnsafePointer는 메모리 주소를 통한 액세스를 허용해주기는 하지만 값의 불변성을 보장하기 때문에 해당 포인터가 가리키는 값(pointee)를 변경하는 것을 허용하지 않습니다. 변수에 대한 포인터는 UnsafeMutablePointer를 별도로 사용합니다.

더 보기 »포인터 관련 메모

(Swift) 프로토콜 그 자체가 자신을 따르지 않는다

Swift 5.1에 추가된 some 키워드 (불투명 리턴 타입)에 관한 Swift 공식 문서를 살펴보다가 이상한 구절을 발견했다.

Another problem with this approach is that the shape transformations don’t nest. The result of flipping a triangle is a value of type Shape, and the protoFlip(_:) function takes an argument of some type that conforms to the Shape protocol. However, a value of a protocol type doesn’t conform to that protocol; the value returned by protoFlip(_:) doesn’t conform to Shape. This means code like protoFlip(protoFlip(smallTriange)) that applies multiple transformations is invalid because the flipped shape isn’t a valid argument to protoFlip(_:).

프로토콜 타입의 값이 그 프로토콜을 따르지 않는다는 것이다. 왱? 이게 뭔말이지? 특정한 프로토콜을 따르는 객체들은 그 실제 타입에 상관없이 해당 프로토콜을 타입처럼 사용할 수 있다고 했는데, 이번에는 프로토콜 타입의 값이 그 프로토콜을 따르지 않는다라니?

더 보기 »(Swift) 프로토콜 그 자체가 자신을 따르지 않는다

Selector를 사용한 소켓 멀티플렉싱

소켓을 사용하여 간단한 서버를 만들 때에는 서버 소켓을 생성하고, 이를 특정한 네트워크 포트에 바인드한 다음, listen() 메소드를 사용해서 해당 포트로 들어올 수 있는 접속 대기열의 크기를 지정합니다. 그런 다음 해당 소켓의 accept() 메소드를 사용해서 클라이언트 소켓을 생성하고, 이 클라이언트 소켓을 통해 클라이언트가 보낸 요청을 읽고, 그에 대한 응답을 보내게 됩니다.

서버 소켓은 클라이언트가 접속할 때마다 ‘서버가 사용하는 클라이언트 소켓’을 따로 생성하고 실제 통신은 두 클라이언트 소켓 사이의 peer-to-peer 방식의 대화가 됩니다. 따라서 하나의 서버 소켓은 여러 클라이언트의 접속을 받을 수 있습니다.

만약 다중 접속을 허용하는 소켓 서버를 파이썬에서 구현한다면 가장 쉬운 방법은 스레드를 사용하는 것입니다. 클라이언트 소켓을 인자로 받는 핸들러 함수를 하나 작성하고, 서버 소켓의 accept() 메소드가 리턴하는 시점에 핸들러 함수에게 클라이언트 소켓을 주고 새로운 스레드에서 작동하도록 시작해주면 됩니다.

여기까지의 작동 모델은 ‘동기식 소켓’을 사용합니다. 동기식 소켓은 send(), recv(), accept() 등의 동작이 모두 블럭되는 소켓입니다. 따라서 스레드가 소켓의 입출력을 기다리는 동안에는 다른 일을 할 수가 없습니다. 그래서 서버 소켓과 클라이언트 소켓들이 동시에 작동할 수 없으니 스레드를 사용하는 것이겠죠.

소켓 라이브러리는 이와 다른 비동기 소켓을 지원하고 있습니다. 비동기 소켓은 소켓을 바인딩하기 전에 sock.setblocking(False)를 명시해서 블록킹 모드를 논블록킹으로 변경해줍니다. 이렇게 만들어진 비동기 소켓을 소켓 API만으로 사용할 수는 없습니다. Python How To 문서는 select.select() 를 사용할 것을 추천합니다만, 이는 문서가 오래되었음을 감안해야 하며 실제 파이썬 공식문서는 보다 고수준으로 설계되어 사용하기 쉬운 selectors 모듈을 쓸 것을 추천하고 있습니다.

이 글에서는 selectors 모듈을 사용하여, 단일 스레드에서 하나의 소켓 서버가 여러 클라이언트의 요청을 처리하는 멀티플렉싱을 어떻게 구현하는지 소개하며, 셀렉터 사용 방법에 대해서 살펴보겠습니다.

더 보기 »Selector를 사용한 소켓 멀티플렉싱

ZMQ 예제 – Poller를 사용하여 종료 시점을 동기화하기

하나의 ZMQ 소켓은 여러 포트에 바인드하거나 커넥트할 수 있어서, 1:N의 연결을 쉽게 구성할 수 있습니다. 하지만 어떤 경우에는 이 다중 접속이 두 개 이상의 소켓을 사용하는 경우도 있습니다. 이런 경우 두 개의 소켓을 동시에 듣는 방법이 필요합니다. ZMQ소켓의 recv() 메소드는 블럭킹 함수이기 때문에 2개 이상의 소켓 중 데이터가 들어온 소켓을 처리하기 위해서는 소켓만으로는 처리할 수 없습니다. ZMQ는 이런 상황에 사용할 수 있는 Poller라는 수단을 제공합니다.

더 보기 »ZMQ 예제 – Poller를 사용하여 종료 시점을 동기화하기