2016년 10월 11일 화요일

Alamofire 소스 분석 - 1

Alamofire 소스 분석해보기.

1. http://happyhourguide.blogspot.kr/2016/10/uiimaveview-extension.html
2. http://happyhourguide.blogspot.kr/2016/10/alamofire-2.html
3. http://happyhourguide.blogspot.kr/2016/12/alamofire-queue.html

https://github.com/Alamofire/Alamofire

* 사용자의 URL 입력을 내부에서 사용하기 위한 형태로 변환하기
url의 형태로 다음의 타입을 사용할 수 있다: String, NSURL, NSURLComponents, NSURLRequest. 이들은 모두 URLStringConvertible을 구현하고 있다. URLRequest 함수를 통해 앞의 타입을 NSMutableURLRequest(내부에 NSURL을 가지고 있는다)로 변환한다. ParameterEncoding.swift의 encode함수를 통해 url을 서버에 보내기 위한 형태로 변환한다. encode 함수는 URLRequestConvertible을 파라메터로 받는다.
URLStringConvertible -> NSMutableRequest -> URLRequestConvertible

- Alamofire.swift
request, upload, download의 함수를 통해 서버에 HTTP 요청을 할 수 있다. Manager를 통해 작업이 이루어진다. 리턴은 Request.
URLStringConvertible과 URLRequestConvertible을 정의한다.

- Manager.swift
Alamofire에서 사용되는 글로벌한 인스턴스를 가지고 있다.
NSURLSessionConfiguration: 기본 session configuration과 기본 헤더 값(Accept-Encoding, Accept-Language, User-Agent)으로 초기화.
SessionDelegate: session에 대한 delegate
생성시 NSURLSession 초기화하고 종료시 invalidateAndCancel()을 통해 실행중이던 테스크들을 정리한다.

사용자가 backgroundCompletionHandler를 통해 서버 요청이 끝났을 때 실행될 내용을 등록할 수 있다.(sessionDidFinishEventsForBackgroundURLSession에 handler를 등록, dispatch_async(dispatch_get_main_queue())를 통해 메인 쓰레드에서 동작하도록 한다.)

- ParameterEncoding.swift
parameter를 percent encoding하고 이를 URL request에 적용한다.
URL, URLEncodedInURL, JSON, PropertyList, Custom의 다섯가지 방법이 있다.
(URLEncodedInURL은 URL과 같이 인코딩을 하나 항상 url의 뒤에 붙인다.)
encode로 인코딩을 시작하고 queryComponents로 key/value를 나누고 escape로 percent-escaped string으로 바꾼다.

URL의 경우 method가 GET, HEAD, DELETE이면 parameter를 url의 뒤에 붙이고 아니면 body에 넣는다. body에 넣을 때는 헤더에 "Content-Type: application/x-www-form-urlencoded; charset=utf-8"도 같이 추가해 준다.
JSON의 경우 NSJSONSerialization을 사용하고 "Content-Type: application/json"을 헤더에 넣는다.
PropertyList의 경우 NSPropertyListSerialization을 사용하고 "Content-Type: application/x-plist"를 헤더에 넣는다.
Custom은 사용자가 직접 인코딩을 처리하도록 한다.

인코딩의 경우 다음과 같은 예가 있다:
parameter에서 key가 array일 경우는 다음처럼 바꾼다: ?key[]=value1&key[]=value2
nested key를 가지고 있는 경우는 다음처럼 바꾼다: ?key[k1]=value1&key[k2]=value2

percent-escaping에 string.stringByAddingPercentEncodingWithAllowedCharacters를 사용한다.



Solicit 소스 분석

https://github.com/mlalic/solicit

An HTTP/2 implementation in Rust.

- 개요
HttpConnect를 가지고 SimpleClient를 생성한다. 생성시 client preface를 서버에 보내고 또한 서버로부터 settings frame을 받는다. 이 작업이 끝나고 나면 request/get_response 함수의 호출을 통하여 서버로부터 응답을 받을 수 있다.
solicit는 request시 실제로 요청을 서버에 보낸다.(OkHttp나 hyper의 HTTP1.1구현의 경우는 request시 데이터만 만들어 놓고 요청을 서버에 보내지 않고 있다가 response를 받으려고 할 때 실제로 요청을 서버에 보낸다.)

- SimpleClient 만들기
SimpleClient::with_connector에 TlsConnector(HttpConnect를 구현하고 있음)를 넘겨줌. TlsConnector는 TCP를 연결(TcpStream::connect)하여 TcpStream을 생성하고 여기에 인증서를 합하여 SslStream을 생성함. 연결이 되고 나면 처음으로 SettingsFrame(client preface)를 서버에 보냄
첫 settings는 ENABLE_PUSH(0)만 서버에 보냄.

데이터 보내는데 사용하는 방법은 필요한 버퍼를 io::Cursor::new를 사용하여 할당하고, settings의 헤더와 데이터를 이 버퍼에 저장한 후 SslStream을 통해 이 버퍼의 내용을 보냄.
io::Cursor<Vec<u8>>는 FrameBuilder를 구현하고 있음.
frame header를 먼저 구성하여 버퍼에 쓰고 SettingsFrame에 설정한 HttpSetting을 하나씩 버퍼에 씀.
frame header는 (length, frame_type, flags, stream_id)의 FrameHeader로 되어 있음.


SslStream은 TransportStream을 구현하고 있음. 따라서, try_split를 통해 sender와 receiver를 구별.

ClientConnection을 HttpConnection과 DefaultSessionState을 가지고 생성.
결과적으로 SimpleClient는 ClientConnection과 host와 SslStream을 sender와 receiver로 나누어서 가지고 있게 됨.

처음으로 서버로부터 받아야 하는 패킷은 SettingsFrame임.
TransportStream은 SendFrame을 구현하고 있고 TransportReceiveFrame은 RecvFrame을 구현하고 있음.

- request 보내기

-- send_frame
io::Cursor를 사용해서 1024 크기의 버퍼를 할당한다.
보내려는 frame을 serialize하여 할당한 버퍼에 넣는다.
버퍼의 내용을 stream에 보낸다.

-- recv_frame
frame의 header만큼인 9byte를 read_exact를 사용해서 읽는다.
전체 frame을 위한 버퍼를 Vec::with_capacity를 사용해서 할당한다.
read_exact를 사용해서 frame의 나머지 부분을 읽는다.
읽은 raw frame으로 부터 frame type에 맞는 frame을 생성한다.

- stream 만들기
DefaultStream을 만든다. 처음 state는 StreamState::Open이다. body가 없는 경우 StreamState::HalfClosedLocal로 바뀐다.

- start_request
HalfClosedLocal이면 EndStream flag를 설정한다.
stream id와 Stream을 연결(HashMap)한다.
stream id는 client의 경우 1로 시작하고 2씩 증가한다.(홀수)
HttpConnectionSender를 통해 헤더의 key/value를 HPACK의 encoder를 통해 encoding하여 HeaderFrame을 만들어서 보낸다.
TODO: 멀티 헤더 지원 암함

- send_next_data

- simple.rs
http2를 사용할 때 사용자가 간단하게 사용할 수 있도록 해주는 SimpleClient가 있다. SimpleClient는 TransportStream을 sender와 receiver로 구별하고 내부에 가지고 있고 데이터를 보내고 받는데에 ClientConnection을 사용한다.
SimpleClient의 with_connector에 tcp연결을 하는 connector를 넘겨준다. 이의 구현은 ClearTextConnector와 TlsConnector가 있다.
서버에 요청은 request함수로 하고 get_response를 통해 응답을 받을 수 있다. 이에 대한 도움함수로 get과 post가 있다.

- client/mod.rs
암호화 없이 데이터를 주고 받는 CleartextConnector가 정의되어 있다. TCP 연결을 위해 HttpConnect를 구현한다.
CleartextConnector의 connect 구현을 보면 TcpStream::connect를 통해 TcpStream을 얻어오고 이를 ClientStream으로 감싸서 리턴한다. 이때 에러가 발생하면 io::Error를 그대로 리턴하지 않고 이를 CleartextConnectError로 감싸서 리턴한다.
서버와의 첫 연결시 client preface를 서버에 보내는 함수가 write_preface이다. 이 함수를 보면 Settings frame을 보낼 때 push를 disable해서 보낸다.(TODO: push 기능 지원 안함)
SimpleClient가 사용하는 ClientConnection이 정의되어 있다.
ClientConnection은 HttpConnection와 SessionState를 가지고 있다. start_request로 헤더를 보내고 send_next_data로 바디를 보낸다.
start_request는 stream id를 생성하고 HttpConnectionSender를 통해 헤더를 서버에 보낸다.
서버로부터 온 frame의 응답에 대한 처리를 위한 ClientSession이 있다.



- transport.rs
네트워크로 데이터를 보내기 위해 사용하는 TcpStream과 SslStream<TcpStream>가 TransportStream을 구현한다.
read_exact는 버퍼가 찰때까지 stream으로부터 데이터를 읽는다.
데이터를 보내기 위해 SendFrame을 구현하고 데이터를 받기 위해 ReceiveFrame을 구현한다.
여기서 데이터를 받을 때 직접 ReceiveFrame을 사용하지 않고 TransportReceiveFrame으로 한번 감싸서 사용한다. TransportReceiveFrame은 받은 frame을 가지고 있다.






Xi-Editor 소스 분석

https://github.com/google/xi-editor

front-end는 swift를 사용한 cocoa로 back-end는 rust로 되어있다.
(그냥 간단하게 맥쪽 코드는 swift라고 하고 rust쪽 코드는 rust라고 부르겠다.)

* Swift

- AppDelegate
CoreConnection을 초기화한다.
AppWindowController의 window를 화면에 띄운다.
AppWindowController가 실행되면 appWindowControllers 리스트에 등록하고 종료하면 리스트에서 제거한다.
rust로 부터 응답이 오면 실행되는 콜백을 CoreConnection에 등록한다: handleCoreCmd
메뉴 선택에 따른 연결 함수들이 여기에 있다.

- AppWindowController
첫 로딩시 "new_tab" 메시지를 rust에게 보낸다.
filename이 변경되면 window의 타이틀을 변경한다.
EditView와 ClipView의 constraint를 설정한다.
ScrollView의 frame이 바뀌거나 ScrollView.contentView의 bound가 바뀌면 EditView와 ShadowView의 scroll위치를 다시 지정한다.

- CoreConnection
NSTask를 사용하여 rust를 실행한다.
swift와 rust의 연결은 NSPipe를 사용해서
(swift의 writing -> rust의 standard input,
rust의 standard ooutput -> swift의 reading)로 연결한다.
rust로 보내는 메시지는 sync 또는 async로 보낼 수 있다.
sync의 경우는 semaphore를 사용해서 block되어 응답을 기다린다.
async의 경우는 응답이 오면 실행하기 위한 콜백을 등록한다. 콜백은 응답 json이 id를 가지고 있는 경우  실행된다.

- ShadowView
텍스트가 위(topShadow), 왼쪽(leadingShadow), 오른쪽(trailingShadow)을 벗어나는 경우 관련 shadow를 화면에 표시해준다.

- EditView
실제 텍스트를 보여주는 뷰

* Rust


AlamofireImage 소스 분석

https://github.com/Alamofire/AlamofireImage

UIImaveView의 extension.

  • 지정한 이미지를 보여주기 위한 extension
  • enum ImageTransition
    • UIView의 transition animation에 사용되는 값을 저장하고 있는다.
    • duration, animationOptions, animations, completion(기본은 nil)
    • completion을 사용하고 싶으면 custom을 사용해야 한다.
  • af_imageDownloader와 af_sharedImageDownloader
    • 이미지 다운로드시 사용하는 instance.
    • 보통은 ImageDownloader.default를 사용하는 af_sharedImageDownloader를 사용하면 되나, 별도의 ImageDownloader가 필요(different auth credential)한 경우에는 af_imageDownloader를 설정하여 이것이 사용되도록 한다.
    • 내부에서 값의 저장은 objc_getAssociatedObject와 objc_setAssociatedObject를 사용해서 key/value의 형태로 저장하고 있는다.
  • af_activeRequestReceipt
    • 현재 진행중인 request에 대한 정보를 가지고 있다. download 요청이 끝나면 nil로 설정된다.
    • request의 취소에 사용한다.
  • af_setImage
    • 이미지 로딩을 요청하는 함수. 1. 이전에 진행중인 이미지 요청이 있으면 취소한다. 1. 캐시가 있으면 캐시에 있는 것을 가져온다. 1. placeholder를 지정해 주었으면 이미지 다운로드가 완료될 때까지 이것을 이미지로 설정한다. 1. ImageDownloader를 통해 이미지를 다운로드 한다.
      • 각각의 request에 unique id를 할당하여 최신인지 아닌지를 파악한다.

ImageDownloader

  • 기본 캐시로 메모리는 20MB, 디스크는 150MB, 디스크 경로로는 "org.alamofire.imagedownloader"를 사용한다. - URLCache에 지정한다.
  • enum DownloadPrioritization을 통해 다운로드 우선순위를 지정한다: FIFO, LIFO
  • 동시에 다운로드 할 수 있는 수를 제한한다. 기본은 4 - maximumActiveDownloads
    • 제한 값이 넘어가면 queuedRequests에 request를 넣어두었다가 기존 request에의 응답이 오면 이 queue에서 request를 꺼내서 실행한다.
  • synchronizationQueue와 responseQueue로 작업을 진행한다.
    • request와 관련된 작업의 경우 쓰레드 문제가 발생하지 않도록 하기 위해 synchronizationQueue를 사용한다.
    • request에 대한 응답은 responseQueue에서 동작하도록 한다.
  • download
    1. 이미 진행중인 request이면 filter와 completion handler를 response handler에 추가한다.
    2. 캐시된 이미지가 있으면 이를 로드한다.
    3. credential이 지정되어 있으면 request에 넣어주고 이미지를 요청한다.
    4. image 응답시 imageResponseSerializer를 사용한다.
    5. 서버로부터 응답시 캐시에 이미지를 넣는다.

DataRequest에 extension을 구현한다.

  • 지원하는 이미지 타입은 acceptableImageContentTypes에 지정해 놓는다.
  • imageResponseSerializer
    • 다운로드한 data를 UIImage에 넣어서 image를 생성한다.
    • 지원하지 않는 이미지 타입인 경우 에러를 리턴한다.

AutoPurgingImageCache

  • in-memory image cache
  • 내부에 저장하는 이미지의 key로 request와 unique filter identifier를 사용한다.
  • 기본 memoryCapacity는 100MB로 설정하고 preferredMemoryUsageAfterPurge는 60MB로 설정한다.
  • 이미지는 CachedImage의 형태로 저장된다. - 내부에 마지막으로 접근한 시간을 기록하고 있는다.
  • 이미지를 추가할 때마다 currentMemoryUsage에 이미지 크기를 추가한다.
  • currentMemoryUsage가 memoryCapacity보다 커지면 내부의 이미지를 마지막으로 접근한 시간 기준으로 정렬한 다음 오래된 이미지를 제거한다. 이미지 제거시 currentMemoryUsage가 preferredMemoryUsageAfterPurge보다 작아지면 이미지 제거를 중단한다.

ImageFilter

  • 이미지 변경을 위한 필터들을 제공한다.
  • UIImage extension에서 제공하는 이미지 변경 함수들에 대한 필터 기능이라고 보면 된다.
  • protocol ImageFilter

UIImage extension

  • thread safe하게 UIImage를 초기화하기 위한 함수를 제공한다.
    • af_threadSafeImage
    • UIImage()의 앞뒤로 NSLock()의 lock/unlock를 사용한다.
  • UIImage에 stored property를 저장하기 위해 AssociatedObject를 사용한다.
    • struct AssociatedKey, objc_getAssociatedObject, objc_setAssociatedObject
  • inflation하기
    • cgImage?.dataProvider?.data
    • inflation을 여러번 반복하지 않기 위해 한번 inflation을 하고 나면 af_inflated를 AssociatedObject를 사용해서 true로 설정한다.
  • alpha정보 확인하기
    • cgImage?.alphaInfo
  • 아래의 함수들은 모두 새로운 UIImage를 리턴한다.
  • af_imageScaled
    • 지정한 크기로 이미지 스케일링한다.
    • UIGraphicsBeginImageContextWithOptions -> draw -> UIGraphicsGetImageFromCurrentImageContext -> UIGraphicsEndImageContext
  • af_imageAspectScaled
    • 비율을 유지하면서 스케일링한다.
    • fit(큰쪽 기준)과 fill(작은쪽 기준) 두가지로 구분되어 있다.
  • af_imageRounded
    • 이미지를 모서리가 둥글게 처리한다.
    • UIBezierPath를 사용해서 이미지 clipping을 하여 그 영역 안에 draw를 하도록 한다.
  • af_imageRoundedIntoCircle
    • 이미지를 원형으로 변경한다.
    • 이미지가 1:1이 아니면 먼저 af_imageAspectScaled(toFill)를 사용해서 1:1로 변경한다.
  • af_imageFiltered
    • CIFilter를 사용하여 이미지를 변경한다.

XCTest

  • Unit test
  • BaseTestCase
    • setUp and tearDown
  • expectation으로 새 기대값을 만들고, 테스트 하고자 하는 결과가 완료되면 fulfill을 불러준다.
  • waitForExpectations에서 fulfill이 되거나 timeout이 될 때까지 기다린다.
  • 원하는 결과값들은 테스트한다.

2016년 1월 19일 화요일

The Wizard of Oz 1 - The Wonderful Wizard of Oz 2016004

camp out: 야영하다.
jaw: 턱
snug: 포근한, 아늑한
brook: 시내, 개천
The air was so still that there was hardly a ripple on the pond's surface: 대기가 하도 고요하여 연못 수면위에 잔물결 하나 찾아보기 어려웠다.
ditch: (들판, 도로가의) 배수로
creep up: 기어오르다.
jag: (암석등의)뾰족한 끝
shaggy: 머리털, 털등이 덥수룩한
mane: (말이나 사자 목덜미의) 갈기
dent: 움푹 들어가게 만들다. 찌그러 트리다.
The back of the car was badly dented in the collision: 그 충돌로 승용차 뒷부분이 심하게 찌그러졌다.
crouch: 쭈그리고 앉다.
He crouched down beside her: 그는 그녀 옆에 쭈그리고 앉았다.
leap: (높이, 길게) 뛰다. 높이뛰기, 도약
pant: (숨을) 헐떡이다.
tip: 기울어지다.
The boat tipped to one side: 보트가 한쪽으로 기울어졌다.
brute: 짐승
swiftly: 신속히, 빨리
meadow: 목초지
dot: [주로 수동태로] 여기저기 흩어져 있다.
The country side was dotted with small villages: 그 시골지역에는 작은 마을들이 여기저기 흩어져 있었다.
poppy: 양귀비
beckon: (오라고) 손직하다.
He beckoned to the waiter to bring the bill: 그가 계산서를 가져오라고 웨이터에게 손짓을 했다.
sweep: (방등을 빗자루로) 쓸다. 청소하다. (거칠게) 휩쓸고 가다.
The showroom had been emptied and swept clean: 상품 전시실은 텅 빈 채 깨끗이 청소되어 있었다.
The little boat was swept out to sea: 그 작은 보트는 바다로 휩쓸려 갔다.
cling: 꼭 붙잡다. 매달리다.
wistfully: 아쉬운 듯이, 생각에 잠겨
gay: 흥겨움, 유쾌함
dazzle: 눈이 부시게 하다.
He was momentarily dazzled by the strong sunlight: 그는 강한 햇빛 때문에 순간적으로 눈 앞이 안보였다.
frail: 노쇠한
odor: 냄새, 악취
arouse: 분발하게 하다. 각성시키다.
The whole community was aroused by the crime: 그 범죄 행위에 전 지역 주민이 각성을 했다.

2016년 1월 9일 토요일

The Wizard of Oz 1 - The Wonderful Wizard of Oz 2016003

in return 대신에, 답례로
dwell (...에) 살다[거주하다]
paw (동물의 발톱이 달린) 발
claw (동물, 새의) 발톱
heedless (~에) 세심한 주의를 기울이지 않는. heedless of the danger
slap (손바닥으로) 철썩 때리다.
retort 쏘아 붙이다. 대꾸하다.
pat (특히 애정을 담아) 쓰다듬다.
She patted the dog on the head 그녀가 그 개의 머리를 쓰다듬었다.
whirl 빙그르르 돌다.
blunt 약화시키다.
Age hadn't blunted his passion for adventure 나이가 들어도 모험을 향한 그의 열정은 꺾이지 않은 상태였다.
shiver 부스러기, 파편. 떨다.
be tender of doing ...하지 않도록 주의하다.
She was tender of irritating him 그녀는 그를 화나지 않게 하려고 주의했다.
set off 출발하다.
We set off for London just after ten 우리는 10시가 막 넘었을 때 런던을 향해 출발했다.
stride 성큼성큼 걷다.
We strode across the snowy fields 우리는 눈덮인 들판을 가로질러 성큼성큼 걸어갔다.
comrade 동무
mar (좋은 것을) 손상시키다[망치다]
The game was marred by the behavior of drunken fans
그 경기는 술취한 팬들의 행동 때문에 망쳐졌다.
weep 울다. 눈물을 흘리다.
toil (장시간)힘들게 일하다. 느릿느릿 움직이다.
They toiled the hill in the blazing sun 그들은 이글거리는 햇빛 속에서 느릿느릿 산을 올라갔다.

2016년 1월 5일 화요일

The Wizard of Oz 1 - The Wonderful Wizard of Oz 2016002

spring: (물)샘
groan: (고통, 짜증으로) 신음소리를 내다.
timidly: 겁많게, 소극적으로
ray: 광선
uplift: 올리기
tin: 깡통
joint: 연결하다.
stir: 약간 움직이다.
The dogs snarled and snapped at our heels: 그 개들이 으르렁거리며 우리 발 뛰꿈치를 물려고 들었다.
pave: 포장하다.
stumble: 발을 헛디디다.
Parents are obliged by law to send their children to school: 부모들은 법에 따라 자녀를 학교에 보낼 의무가 있다.
enchant: 마법을 걸다.
daunt: 겁먹게 하다.

Generic interfaces 요점

 https://go.dev/blog/generic-interfaces  Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다. pointer receiver를...