2016년 11월 12일 토요일

안드로이드에서 제공하는 머터리얼 스타일 프로그레스 바 만드는 방법

아래의 github code에서 제공하는 CircularProgressView에 대한 소스 분석

https://github.com/rahatarmanahmed/CircularProgressView

View를 상속한 CircularProgressView를 만든다.

public class CircularProgressView extends View

View를 상속하면 최대 4개의 constructor를 구현해야 한다.(보통 여기서 xml에 넣어준 attribute들을 가져와서 설정을 한다.)
constructor에서 attribute들에 대한 값들을 설정하고, 이 값들을 가지고 paint도 설정을 해준다.
(나중에 onDraw에서 그림 그릴때 이렇게 설정된 paint가 사용된다.)

onMeasure와 onSizeChanged로부터 width와 height를 파악해서 작은쪽의 값을 size에 저장하고 bound에는 (left: paddingLeft + thickness, top: paddingTop + thickness, right: size - paddingLeft - thickness, bottom: size - paddingTop - thickness)를 저장한다.

onDraw에서 아래의 코드로 호를 그린다.

canvas.drawArc(bounds, startAngle + indeterminateRotateOffset, indeterminateSweep, false, paint);

호를 startAngle + indeterminateRotateOffset 각도에서 시작해서 indeterminateSweep 크기만큼의 각도로 그린다. 이 세 값은 animator가 업데이트 되어 invalidate()를 호출하기 전에 업데이트 된다.

AnimatorSet을 사용해서 animation set을 만들어 이 set을 실행한다. AnimatorSet의 play()를 통해 animation의 선후관계를 만든다.

이제 가장 중요한 animation을 만들어 보자. 총 4개의 animation을 만든다.
첫번째는 호를 그리는 animation으로 indeterminateSweep(계속 증가)을 변경한다.
두번째는 호를 이동하는 animation으로 indeterminateRotateOffset을 변경한다.
세번째는 호를 줄이는 animation으로 startAngle(증가)과 indeterminateSweep(계속 감소)을 변경한다.
네번째는 두번째와 마찬가지로 호를 이동하는 animation으로 indeterminateRotateOffset을 변경한다.

첫번째를 다시 코드로 자세히 보면, ValueAnimator.ofFloat(start, sweep)을 통해 호를 그리게 될 각도의 interpolation 영역을 지정하고 이의 duration, interpolator도 지정해준다. 그리고 나서 리스너를 달아 값이 나오면 이를 indeterminateSweep에 넣고 invalidate()를 호출한다. 이렇게 함으로서 onDraw에서는 변경된 indeterminateSweep을 가지고 점점 커지는 호를 그리게 된다.

ValueAnimator frontEndExtend = ValueAnimator.ofFloat(INDETERMINANT_MIN_SWEEP, maxSweep);
frontEndExtend.setDuration(animDuration/animSteps/2);
frontEndExtend.setInterpolator(new DecelerateInterpolator(1));
frontEndExtend.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override    public void onAnimationUpdate(ValueAnimator animation) {
        indeterminateSweep = (Float) animation.getAnimatedValue();
        invalidate();
    }
});

첫번째와 두번째 animation을 같이 동작시키고, 그 이후에 세번째와 네번째를 같이 동작시킴으로서 안드로이드에서의 material progress bar와 비슷한 형태의 progress bar가 나오게 된다.




2016년 11월 8일 화요일

WebTorrent 소스 분석

https://github.com/feross/webtorrent

bittorrent-dht

  • 처음 시작시 find_node로 node들을 찾는다. lookup으로 내가 원하는 info_hash를 가지고 있는 node들을 찾는다. announce로 node들에게 내가 info_hash를 다운받고 있음을 알린다. ping으로 node가 살아있는지 확인한다. 에러가 발생하면 error를 응답한다.
  • 'KRPC Protocol'에 관련된 부분은 k-rpc 모듈에 의해 이루어진다.
  • id는 k-rpc 생성시 만들어진다.
  • _tables: info_hash를 key로 하여 찾은 node 정보를 저장한다.
  • _values: arbitrary payload를 저장한다.
  • _peers: 나에게 announce 쿼리를 보낸 peer 정보를 저장한다.
  • 5분마다 secret 값을 변경한다. secret는 token 생성에 사용된다.
  • routing table에 있는 값들의 저장을 위해 toJSON 함수를 제공한다.
    • toJSON 함수를 사용하여 뽑아낸 node와 value의 정보를 디스크등에 저장해 놓았다가 다시 시작시 addNode를 통해 routing table에 추가할 수 있다.
  • find_node
    • query: { "a": { "id": "<querying nodes id>", "target": "<id of target node>" }
    • 이 작업이 이루어짐으로서 서버로부터 관련 node들의 정보가 routing table에 저장된다.
    • k-rpc에서 기본으로 사용하는 서버들(BOOTSTRAP_NODES)이 있다: router.bittorrent.com:6881, router.utorrent.com:6881, dht.transmissionbt.com:6881
  • lookup
    • query: { "q": "get-peers", "a": { "id": "<querying nodes id>", "info_hash": "<20-byte infohash of target torrent>" } }
    • 응답이 오면 k-bucket에 넣고(id, host, port, token) 'peer' event를 emit한다. peer는 {host, port}이다.
  • announce
    • table에서 info_hash에 연결되어 있는 k-bucket을 찾는다. 여기에는 info_hash를 가지고 있는 node들의 정보가 들어있다. 여기서 info_hash에 closest인 node들을 찾아 이들에 announce를 한다.
    • query: { "q": "announce_peer", "a": { "id": "<querying nodes id>", "token": "<response to a previous get_peers query>", "info_hash": "<20-byte infohash of target torrent>", "port": "port", "implied_port": "0 or 1" } }
  • ping
    • query: { "q": "ping" }
    • response: { "r": "<querying nodes id>" }
  • error
    • response: { "e":[201, "A Generic Error Ocurred"] }

webtorrent

  • handshake를 보낸다. -> (bitfield를 받는다. -> request를 보낸다. -> piece를 받는다.) -> keep-alive를 60s마다 받는다. -> (interested를 받는다. -> unchoked를 보낸다.) -> peer가 DHT를 지원하는 경우 자신의 DHT 포트를 알리는 port를 받는다. -> 모든 piece를 다 받은 경우 본인을 seeder로 설정하고 remote peer에 choked를 보낸다.

BitTorrent Protocol

  • handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
    • 상대 client와 연결시 첫번째로 보내는 메시지.
    • version 1.0의 경우 pstrlen = 19이고 pstr = "BitTorrent protocol" 이다.
  • message format: <length prefix><message ID><payload>
  • port: <len=0003><id=9><listen-port>
    • DHT를 구현하고 있는 경우 remote peer에게 보내는 메시지.
  • keep-alive: <len=0000>
    • 연결이 끊기지 않도록 하기 위해 보내는 메시지. 보통 2분마다 보낸다(webtorrent는 1분마다 보낸다).
  • bitfield: <len=0001+X><id=5><bitfield>
    • 어떤 piece를 가지고 있고 안가지고 있는지를 알리는 메시지.
    • bit에서 cleared는 missing piece를 set은 downloaded를 의미한다.
    • lazy bitfield: 모든 bit를 cleared 하여 받은 부분이 없다고 알린 후, have 메시지로 가지고 있는 부분을 알리는 방법. ISP filtering을 막는 방법이라고 알려져 있다.
  • have: <len=0005><id=4><piece index>
    • 가지고 있는 piece의 index를 알리는 메시지
  • request: <len=0013><id=6><index><begin><length>
    • 지정한 piece를 요청한다.
  • piece: <len=0009+X><id=7><index><begin><block>
    • 요청받은 piece를 응답한다.
  • state information: interested and choked
    • interested는 remote peer가 block을 요청할 것이라는 것을 의미하고 choked는 remote peer가 요청에 응답을 주지 않을 것이라는 것을 의미한다.
    • interested: <len=0001><id=2>
    • not interested: <len=0001><id=3>
    • choke: <len=0001><id=0>
    • unchoke: <len=0001><id=1>

2016년 11월 4일 금요일

Swift 3.1에 추가될 사항 살펴보기

## 0045 Add prefix(while:) and drop(while:) to the stdlib

https://github.com/apple/swift-evolution/blob/master/proposals/0045-scan-takewhile-dropwhile.md

Collection과 LazySequenceProtocol과 LazyCollectionProtocol에 새로운 2개의 함수 prefix와 drop을 추가한다.

## 0141 Availability by Swift version

https://github.com/apple/swift-evolution/blob/master/proposals/0141-available-by-swift-version.md

@available(...) attribute에 swift version을 추가한다.
기존에 platform이나 os version으로 사용하던 것과 같이 사용하면 된다.


@available(swift, obsoleted: 3.1)
class Foo {
  //...
}

하지만, 아래와 같이 platform availability abbreviation list에 swift를 추가하는 것은 허락하지 않는다.

  • @available(swift 3, *)
  • @available(swift 3, iOS 10, *)

## 0145 - Package Manager Version Pinning

## 0082 - Package Manager Editable Packages

## 0080 - Failable Numeric Conversion Initialisers

새로운 종류의 conversion initialiser를 더한다.
//  Conversions from all integer types.
init?(exactly value: Int8)
init?(exactly value: Int16)
init?(exactly value: Int32)
init?(exactly value: Int64)
init?(exactly value: Int)
init?(exactly value: UInt8)
init?(exactly value: UInt16)
init?(exactly value: UInt32)
init?(exactly value: UInt64)
init?(exactly value: UInt)

//  Conversions from all floating-point types.
init?(exactly value: Float)
init?(exactly value: Double)
#if arch(i386) || arch(x86_64)
init?(exactly value: Float80)
#endif

## 0147 - Move UnsafeMutablePointer.initialize(from:) to UnsafeMutableBufferPointer

UnsafeMutablePointer.initialize(from:)는 deprecated 시키고 UnsafeMutableBufferPointer를 사용한다.
UnsafeMutableRawPointer.initializeMemory(as:from:)는 deprecated 시키고 UnsafeMutableRawBufferPointer.initialize(as:from:)를 사용한다.

기존에 Collection을 취하던 것이 Sequence를 취하는 것으로 변경된다.

Array와 ArraySlice와 ContiguousArray에서의 +=와 append<C : Collection>(contentsOf newElements: C)는 더이상 필요없기 때문에 삭제된다(다른 방식으로 효율적으로 구현할 수 있게 된다.).

## 0151 - Package Manager Swift Language Compatibility Version

## 0152 - Package Manager Tools Version


Rust 변경사항 살펴보기 :

## RFC 1624 : loop-break-value

https://github.com/rust-lang/rfcs/blob/master/text/1624-loop-break-value.md

break가 value를 가질 수 있게 하고 loop가 리턴값을 가질 수 있게 한다.

break는 아래의 4가지가 가능해진다.(label은 loop의 이름이고 EXPR은 expression을 의미)

1. break;
2. break 'label;
3. break EXPR;
4. break 'label EXPR;

loop의 리턴 타입

1. break가 없으면 리턴하지 않는다는 것을 의미하는 !가 된다.
2. break가 있으면 ()가 리턴값이 된다.
3. break EXPR이 있으면 EXPR의 타입이 리턴타입이 된다.

위의 내용을 적용함으로서 코드가 아래처럼 간결해 질 수 있다.

// without loop-break-value:
let x = {
    let temp_bar;
    loop {
        ...
        if ... {
            temp_bar = bar;
            break;
        }
    }
    foo(temp_bar)
};

// with loop-break-value:
let x = foo(loop {
        ...
        if ... { break bar; }
    });

## RFC 1682 : field-init-shorthand

https://github.com/rust-lang/rfcs/blob/master/text/1682-field-init-shorthand.md

named field를 가지는 struct와 union과 enum에서 초기화 시 field가 같은 이름을 가지는 경우 'field: field' 대신 'field'를 사용할 수 있도록 한다.

위의 내용을 적용함으로서 코드가 아래처럼 간결해 질 수 있다.

struct SomeStruct { field1: ComplexType, field2: AnotherType }

impl SomeStruct {
    fn new() -> Self {
        let field1 = {
            // Various initialization code
        };
        let field2 = {
            // More initialization code
        };
        SomeStruct { field1, field2 }
    }
}

## RFC 1665 : windows-subsystem

https://github.com/rust-lang/rfcs/blob/dd3ba8ec65f02c7742c75c7a25d5ce2c49fa5012/text/1665-windows-subsystem.md

현재의 rust program은 Windows에서 실행될 때 항상 console을 띄운다. 이것은 Windows가 CONSOLE subsystem은 main을 entry point로 하고 WINDOWS subsytem은 WinMain을 entry point로 하는데, rust program은 항상 main이 entry point이기 때문이다.

이 문제의 해결을 위해 아래와 같은 attribute를 새로 추가한다.

#![windows_subsystem = "windows"]

여기서 가능한 값은 {windows, console} 이고 나중에 더 추가될 수도 있다.

위 attribute에서 subsystem이 "windows" 이면 "/ENTRY:mainCRTStartup" 을 linker option에 추가함으로서 WINDOWS subsystem의 경우에 main을 entry point로 사용하면서 console이 뜨지 않도록 한다.

## RFC 1725 : unaligned access

https://github.com/rust-lang/rfcs/blob/master/text/1725-unaligned-access.md

unaligned pointer에서 reading/writing을 할 수 있는 ptr::read_unaligned와 ptr::write_unaligned를 추가한다.

사실 위 두 함수의 구현은 ptr::copy_nonoverlapping
의 wrapper이다. 따라서, ptr::copy_nonoverlapping를 직접 사용해도 된다. 그래도 위의 두 함수가 사용하기 더 편리하기도 하고 좀 더 직관적이다.

## RFC 1566 : procedural macros

## RFC 1647 : allow self in where clauses

타입이 trait의 implementations의 어떤 위치에서든 사용이 가능하도록 한다.
impl SomeTrait for SomeType where Self: SomeOtherTrait { }

impl SomeTrait<Self> for SomeType { }

impl SomeTrait for SomeType where SomeOtherType<Self>: SomeTrait { }

impl SomeTrait for SomeType where Self::AssocType: SomeOtherTrait {
    AssocType = SomeOtherType;
}

하지만 아래는 안된다.
// The error here is because this would be Vec<Vec<Self>>, Vec<Vec<Vec<Self>>>, ...
impl SomeTrait for Vec<Self> { }

## RFC 1414 : rvalue static promotion

constexpr rvalues를 static memory에 values로 promote한다.

function body's block에서 아래의 조건이 맞으면
* constexpr rvalue에의 shared reference를 취하는 경우(&<constexpr>)
* constexpr이 UnsafeCell { ... } 생성자를 포함하고 있지 않은 경우
* constexpr이 UnsafeCell을 포함하는 타입을 리턴하는 const fn call을 포함하고 있지 않은 경우
rvalue를 static memory location으로 translate 하고 resulting reference 'static lifetime을 준다.

예는 아래와 같다.
// OK:
let a: &'static u32 = &32;
let b: &'static Option<UnsafeCell<u32>> = &None;
let c: &'static Fn() -> u32 = &|| 42;

let h: &'static u32 = &(32 + 64);

fn generic<T>() -> &'static Option<T> {
    &None::<T>
}

// BAD:
let f: &'static Option<UnsafeCell<u32>> = &Some(UnsafeCell { data: 32 });
let g: &'static Cell<u32> = &Cell::new(); // assuming conf fn new()

## RFC 1651 : movecell

Cell이  non-Copy타입하고도 잘 동작하도록 확장한다.

이에 따라 get 함수는 Copy 타입에만 동작하도록 변경되고, 추가되는 함수들이 있다.
impl<T> Cell<T> {
    fn set(&self, val: T);
    fn replace(&self, val: T) -> T;
    fn into_inner(self) -> T;
}

impl<T: Copy> Cell<T> {
    fn get(&self) -> T;
}

impl<T: Default> Cell<T> {
    fn take(&self) -> T;
}

## RFC 1584 : macros

Declarative macros 2.0. macro_rules!에의 교체

// Syntax (TBA)

macro foo($a: ident) => {
    return $a + 1;
}

## RFC 1558 : closure to fn coercion


Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...