'분류 전체보기'에 해당하는 글 132건

목표

- http://{username}.github.io에 Hello World 웹페이지가 나타나도록 만들것이다.
 +) 추가로 http://{username}.github.io가 아닌 http://profile.canapio.com 나만의 도메인을 적용시켜 볼것이다. 물론 도메인을 미리 구매하여 준비해둬야한다.

들어가며

Github는 깃을 사용하는 프로젝트를 지원하는 웹 기반의 호스팅 서비스이다. 여기서 홈페이지를 만들 수 있는 기능을 제공한다. 다르게 말하면 홈페이지를 구성하는 파일들을 Github에 올려놓고 홈페이지를 돌릴 수 있다는 것이다. 원래는 홈페이지를 만들기위해서는 서버까지 구축했었어야했는데, 요즘은 다양한 서비스에서 서버를 무료(또는 무료)로 제공해주고있다. 그중 하나가 Github인것이다.

"http://{username}.github.io"에 Hello World 페이지 띄우기

STEP 1 : 저장소를 판다.
저장소 이름은 {유저이름}.github.io로 한다. 저장소 이름이 나중에 웹페이지 도메인 네임이 될것이다.

STEP 2 : index.html 파일을 만든다.

STEP 3 : 확인한다.

나만의 도메인으로 바꾸기

STEP 1 : CNAME 파일을 생성하여 바꾸고자 하는 도메인을 넣어둔다.


이때 http://나 https://는 빼고 입력해야한다.

STEP 2 : 도메인 등록 사이트에 가서 A 레코드 설정을 한다.
Github의 DNS Provider IP주소는 아래 두가지이다. 둘중 하나 선택하여 등록해주면 된다.

  • 192.30.252.153
  • 192.30.252.154
위 사진은 닷넷코리아라는 도메인 사이트에서 등록할때의 모습이다.

이렇게 등록을 해주면 시간이 좀 걸릴수도 있다는데(10분정도?), 곧 확인해보면 원하는 주소에 나의 github 사이트가 연결되었음을 알 수 있을것이다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문https://medium.com/@kazmiekr/what-every-ios-developer-should-be-doing-with-instruments-d1661eeaf64f

Introduction

iOS 프로젝트를 마무리할때, 디바이스에 올려 테스트하여 크래쉬가 없이 잘 돌아가면 만족하고 끝낸적이 있을것이다. 그게 과연 올바른 마무리일까? Instruments를 사용하여 프로파일링(profiling)을 하지 않고 끝냈다면 잘 마무리 했다고 할 수 없다고 생각한다. 그 이유는 개발자 디바이스에서 잘 동작한다해서 실유저에서 크래쉬가 안난다는 보장은 없기 때문이다.

Xcode에는 Instruments라는 퍼포먼스 튜닝 어플이 포함 되 있다. 이 프로그램은 다양한 기준으로 개발자의 앱을 프로파일 할 수 있게 해준다. Instruments는 CPU 사용량, 메모리 사용량, 메모리 누수, 파일/네트워크 활성, 베터리 사용량 등을 측정할 수 있는 툴을 포함한다. 이것들은 Xcode에서 바로 시작할 수 있어서 쉽게 켤 수 있다. 그러나 지금 보고있는 프로파일 자료가 뭘 프로파일링 한건지 잘 모를수도 있으며 이런 상황은 개발자들이 이 깊은 잠재력을 가진 툴 사용을 저해할 수 있다.

이 많은 프로파일 툴 중에 어떤걸 선택해야할까? 먼저, 느려진 네트워크 요청이나 버벅거리는 스크롤과 같은 눈어띄는 퍼포먼스 이슈 해결을 위한 선택지들이 있을 수 있다. 그러나 나는 당신이 생각하기에 모든것이 정상적으로 돌아가는것으로 보이는 부분에서 CPU나 메모리 사용량을 체크해보는걸 추천한다.

프로파일을

이 이야기를 하기 전에 한가지 우리가 염두할 것은, 모든 개발자들이 항상 그들의 앱에 프로파일을 하지는 못한다는 것이다. 대부분은 데드라인과 예상 결과물이 존재한다. 어떨땐 앱을 프로파일링이고뭐고 마감을 빠듯하게 지키는것도 힘들 때가 있지만, 우리는 이런 상황까지 고려하여 '어느 시점에 프로파일링을 하면 좋을까'에 대해 얘기해보고자한다.

최소한 만든 앱을 앱스토어에 제출하기 전에 한번은 프로파일링을 해줘야한다. 당신은 앱스토어의 심사를 통과 한 후 유저들이 앱을 사용하면서 안 좋은 일들이 일어나길 바라지는 않을것이다. 프로파일링을 하지 않았다가 문제가 생긴다면, 좋지 않은 리뷰들이 많아질 것이고 다운로드 수는 감소할 것이다.

나는 당신이 주요 기능들을 끝낸 후에, 재빨리 프로파일을하여 모든것이 정상적으로 동작하는지 확인해보는 것을 제안한다. 만일 재빨리 하지 못하면, 잠재적인 이슈들이 점점 드러날테고, 그것이 한참 쌓이면 완전히 새로운 기획으로 바뀔 수도 있으며 이것은 런칭을 심각하게 미루게 될것이다. 팀으로써 당신은 프로파일하는 시간을 개발 플랜의 일부로 살짝 넣도록 해야한다.

익숙하지 않은 프레임워크를 쓰게되면 Instruments 켜는걸 제안한다. 또한 대부분의 iOS 프레임워크와 라이브러리는 끊임없이 바뀌며, 이런 변화는 당신이 익축하지 않은 프레임워크와 작업하는것과 비슷하다. 대부분 개발자들은 바뀌는게 언제인지 느낌이 올때가 있고, 만약 당신이 그런 느낌을 받았다면 얼른 당신이 만든 작업물들에서도 안정적으로 동작하는지 확인하기위해 프로파일링을 돌려야한다. 당신이 추가한 라이브러리에서 당신의 통제를 범위를 벗어나 발생될 있는 메모리 이슈가 있는지 확인하기 위한 프로파일에는 3rd party 라이브러리를 가져다 쓰는것 또한 좋은 방법이다.


Xcdoe에서 프로파일링

Xcode 이전엔 Instruments 안에 묻어둔 많은 정보를 ‘Debug Navigator(Xcode 기능의 일부)’ 포함하기위해 위해 Instruments 밖으로 확장해왔다. 만일 당신이 6 단축키를 누르면, 앱에 대한 퍼포먼스 정보를 있다. 여기서 CPU/Memory/Energy/Disk/Network 엑티비티의 빠른 요약 정보를 있고 즉각적인 이슈를 수도 있다. 여기서 상단에 보이는 ‘Profile in Instruments’ 버튼을 눌러서 Instruments 실행할 수도 있는데, Instruments를 누르면 디버그 세션으로 이동할지 아니면 새것을 새로 시작할지 물어볼 것이다.

CPU 프로파일링

제일 먼저 우리가 프로파일해볼 것은 CPU 사용량이다. CPU 프로파일링을 시작하기위해 우리는 ‘Profile’ 이라는 프러덕트를 선택하고 타겟은 디바이스 선택해야한다. CPU 프로파일링을 할때, CPU 사용량에 대한 정확한 정확한 정보를 얻기 위해 실제 디바이스를 사용하기를 원할것이다. 만약 시뮬레이터로 타겟을 정하면 실제 디바이스 환경과는 많이 다른 머신의 CPU 정보를 얻게된다. 이상적인 테스트로는 당신의 작업물이 가장 느린 디바이스에서 동작하는지 확인한 뒤에 빠른 환경에서 테스트 하는 것일 것이다.

CPU 프로파일링은 인터벌을 줘서 프로세스들이 동작하는 샘플을 얻어냄으로써 측정한다. 디폴트로 샘플은 1ms 단위로 얻어지지만, 원하면 바꿀 수도 있다. 스냅샷들 사이에서 어떤 프로세스들이 아직 돌고있는지 봄으로써, 그것들이 얼마나 길게 작동되고있는지 측정할 있다.

프로파일 빌드가 끝이나면, Instruments 켜지면서 어떤 템플릿으로 프로파일링을 할지 물어본다. 우리는 CPU 사용량 측정을 위해 “Time Profiler” 것이다.

이것은 Timer Profiler 셋업을 위한 초기 Instruments 화면이다. 이제 실제로 프로파일링을 하기위해 녹화 버튼을 누른다. 가끔 몇몇 이유로 녹화 버튼이 비활성되있는 경우가 있다. 그럴때는 오른쪽 상단에 비활성된 이유가 나타나 있을 것이다. 나는 ‘device is offline’이라는 상태에서 멈춘적이 있는데, 보통 디바이스를 재부팅하면 해결된다. 이제 녹화 버튼을 누르면 당신 앱에서 무슨 일이 일어나는지 정보를 보여주기 시작한다.

상단에는 녹화가 진행되는동안 시간이 지남에 따라 당신의 CPU 사용량 그래프가 보이며 하단에는 동작하는 동안의 프로세스들의 Call Tree 보일것이다. 프로세스의 초기 덤프(dump) Call Tree에서 상단에 보이는 자료들은 매우 쓸때없이 많아 보인다. 또한 이것은 모든 시스템 라이브러리의 활성 상태를 보여줘버린다. 당신은 아마 당신이 작성한 코드에만 집중하고 싶을것이다. 다행히 중요한 정보만 빨리 찾도록 쉽게 설정하는 방법이 있다.

오른쪽에 톱니모양처럼생긴 ‘Display Settings’(2) 누르면 ‘Call Tree’를 위한 옵션들이 나온다. 디폴트로는 대부분 옵션이 오프되 있을 것이다. 우리는 우리가 원하는 것만 켜면 된다. 이제 이 옵션들이 뭘 하는 놈들인지 보자.

  • Seperate by Thread — 많이 사용된 스레드 순서로 프로세스를 보여준다.
  • Invert Call Tree — 스택을 뒤집어서 보여준다. 가끔 유용하게 쓰인다.
  • Hide System Libraries — 시스템 라이브러리 프로세스는 숨기고 당신의 코드만 보여준다.
  • Flatten Recursion — 하나의 개체에서 재귀로 호출된 콜들을 보여준다.
  • Top Functions — 함수 호출이 함수로부터 불려진 함수에 의해 추가적인 시간이 쓰이는지 시간순으로 보여준다. 기능은 무거운 메소드가 어떤건지 찾는데  도움을 준다.

필자는 프로파일링할때 대체로 모두 체크하고 정보를 얻는데, 이게 유용하다. 필터링 옵션선택한 Call Tree 보면, 당신의 앱에 메소드가 CPU 얼마나 사용하는지 쉽게 확인할 수 있다.

이제 CPU 입장에서 무거운 메소드들 리스트를 모니터링 하면서 앱의 정확한 지점을 최적화 시켜볼 있다. 몇몇은 건드리기 힘들 수도 있지만 최적화 가능한 것들도 많이 있을 것이다. 필자는 최적화를 어떻게 하는지에대해서는 깊게 보지는 않을 것이지만 여기 몇몇 생각해볼만한 것들이 있다:

  • 무거운 작업은 UI 프로세스가 아닌 것은 다른 쓰레드로 옮긴다.
  • 항상 다시불러올 필요가 없는 이미지, 데이터 등등의 것들은 캐싱한다.
  • 당신은 필요하지 않는 UI 업데이트를 지도 모른다. UI 업데이트를 줄인다.

한가지 팁은 ‘Call Tree’ 섹션의 오른편에 존재하는 ‘Extended Detail’ 섹션이다. 이것은 당신이 선택한 스택 흔적(trace) 있고 라인을 더블클릭하면 정확히 당신 코드를 찾아 보여줄 것이다. 작은 Xcode아이콘을 클릭하면 동작중인 메소드를 Xcode에서 보여줄 것이다.

퍼포먼스 이슈를 해결하여 업데이트를 시킨 후에는 같은 방식으로 profiler 돌리면서 이전 퍼포먼스보다 나아졌는지 체크한다. 과정을 당신이 만족할 까지 반복하면 된다.

메모리 프로파일링

다음으로 프로파일링 것은 메모리 프로파일링이다. 이것은 직접적인 이슈가 아닐때가 많아서 iOS 개발에서는 그냥 지나쳐버리기 쉬운 것중 하나이다. 만일 당신 앱에 메모리 누수가 발생하고 있는데 유저가 지속적으로 사용한다면 메모리는 점점 꽉 찬 뒤, 결국 아웃 오브 메모리 상황이 발생하여 앱이 꺼지게 것이다. 이런 상황은 당신의 앱한테만 좋은게 아니라 유저의 디바이스 입장에서 메모리 부족으로 다른 앱들에게도 안 좋은 영향을 끼치게 될것이다. 그럼 이제 Instruments 이용하여 어떻게 메모리 누수를 방지를 있는지, 그런 상황에서 어떻게 고칠 있는지 알아보자.

시간이 지남에 따라 메모리 사용량이 점점 증가하는걸 원치 않을 것이다.

아래와 같은 상황은 원치 않는다.

아래와 같은 상황을 원한다.

가장 쉽게 메모리 사용량을 확인하는 방법은 Xcode 안에 있는 ‘Debug Navigator’에서 보는것이다. ‘Memory’ 패널을 선택하고 실시간 메모리 사용량을 있다. 여기서는 메모리 사용량이 계속 증가하거나 한번도 낮아지지 않는 문제와 같은 문제들을 바로 확인하는데 도움을 준다.

메모리 사용량을 자세히 보기위해 ‘Profile in Instruments’ 누르면 세션에서 transfer할건지 새로 하나 만들면서 (Instruments)restart할건지 물어볼 것이다. restart 누른다. 필자는 transfer 눌러서 뭔가 정보를 손실하거나 그런 좋지않은 경험이 많다. 그러면 Allocations and Leaks 템플릿을 가지는 Instruments 열리고 이것은 모든 메모리 할당과 모든 발생하는 잠재적인 누수를 눈으로 있다.

이번에도 누수 부분을 깊게 들어가진 않겠지만, 메모리가 할당은 됬으나 해제가 되지않는 것들을 보기 위해 특정 시간 간격 안에서 스냅샷을 찍어볼 것이다. 메모리 누수는 Objective-C C 라이브러리 같은 것을 사용하면 종종 나타난다. 웬만하면 ‘Analyze’ 빌드 옵션을 사용하여 찾을 있으나, 간혹 Analyze 툴이 아무것도 찾지 못할때도 있다. 반면 Swift에서 작업하고 있으면 Objective-C 사용할때보다 누수를 적게 겪을 것이다.

메모리 프로파일러를 쓰면서 내가 찾은 유용한 기능은 두가지이다. 첫째로 순서가 이후에 동시에 이벤트의 순서를 수행한다는 것. 둘째로 메모리 마킹을 생성하는 것이다. 이걸 사용하면 당신은 스냅샷 사이에 메모리의 변화를 분석할 있다. 오른편 ‘Display Settings’ 섹션 위에 있는 ’Mark Generation’ 버튼을 누르면 바로 메모리 스냅샷을 찍을 수 있다. 진행되는 동안 마크를 만드는걸 까먹었어도 언제든지 마크를 추가한 뒤에 단지 클릭하고 표시를 상단에 놓고 움직이면 원하는 지점으로 마크를 이동시킬 있다. 그러고나서 ‘Allocation Type’ ‘All Heap Allocations’으로 바꾸면, 우리가 손대기 힘든 시스템의 정보들은 숨기고 증가량에 따라서 정렬해준다. 이제 당신은 범위동안의 메모리 사용량을 보기 쉽게 확인할 있을것이나, 이것은 실제 당신이 생성한 오브젝트 자체를 확인하기는 어려울 것이다.

이제 메모리 할당에 따른 리스트는 가지고 있으니, 이것이 제대로 나타나있는지 확인하야한다. 사실 초기에 표시되는 데이터들은 쓸모가 없다. 만약 Swift 사용하고 있다면, 모든 Swift 객체들의 이름 앞에 이름이 붙을것이고 거기서 이름으로 필터링하면 당신의 객체를 찾을 있다. 반면 Objective-C 사용하고 있다면 객체를 찾아내는데 특이한 방법을 사용한다. 당신이 찾고 싶은 것의 이름을 알고 있을것이다. 당신의 파일이나 객체 이름에 접두로 어떤 특정 이름을 붙인다면 찾을 있다. 예를들어 당신의 모든 view controller들이 *ViewController 같은 이름을 가진다면 ‘ViewController’ 검색해도 찾을 있을것이다.

여기 필자가 만든 예제에는 4개의 스냅샷이 있고 ViewController라는 이름으로 객체들을 필터링 했다. 그리고 스냅샷 사이에 ServiceViewContoller에서 누수가 일어났다는걸 있다.

여기 글의 목적을 위해 ViewController에다 뭔가 문제가 있는 retain cycle 집어넣으나, 원래는 코드를 뒤져보면서 어떤것이 객체의 누수를 만드는지 일일히 살펴봐야한다. retain cycle 객체가 서로 강한 참조를 하고있어서 서로 할당 해제되는걸 막을때 일어난다. 필자는 retain cycle에대해 깊게 말하진 않을것이나, 이런 경우에 delegate blocks/closures 먼저 확인하는 것을 추천한다. 당신의 delegates (weak)하고 blocks/closures에서 약한 참조를 사용한다는걸 확인하면 좋을것이다.

당신의 객체에서 다른 객체를 참조하고있어서 그 다른 객체가 메모리 해제되지 못하는지 일일히 확인하기 좀 힘들다면, Instruments 사용하여 많은 객체들의 자료를 얻을 있을 것이다. Instruments에서 객체 옆에 작은 화살표를 누르면, 객체의 모든 할당된 객체들을 보여줄 것이며 누가 생성하였는지까지도 나온다. 그다음 작은 화살표를 누르면 retain release count 정보까지 얻을 있다. 만일 count 0 되지 않는다면 메모리 해제가 되지 않았다는 뜻이다. 여기서 팁을 주자면 ‘Responsible Library’ 당신 코드이니 유심히 보고 ‘libsystem_blocks’ 유심히 봐라. 반대로 ‘UIKit’ 스킵해도 된다. 필터링하기위해 아이템들을 검색 박스에 쳐볼 있다. 그러고나면 ‘ Extended Detail’ 보이고 이것들이 어떻게 돌아가고있는지 스택 trace 보이게 될것이다.

결론

글은 Instruments 아주 일부만 보여준 것이다. 그래도 Instruments 시작하는 사람들에게 도움이 되길 바라며 처음부터 바로 잘하지 못해도 괜찮다. 툴을 사용하는데 점점 익숙해지다보면, 당신의 코드가 나은 코드가 될것이다. 당신은 문제를 만든 이슈를 찾는것에 주도적이게 것이며, 프로파일링이 당신의 익숙한 개발 플랜 하나의 플랜으로 자리잡게 될 것이다.

Instruments에대해 배우고 싶다면 필자는 2014 WWDC 비디오(Improving your app with Instruments) 보는것을 적극 추천한다. 여기서는 추가적인 팁엔 트릭과 함께 살펴볼 있다. Instruments에대해 읽고싶으면 애플이 만든 유저 가이드 확인해보길 바란다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

MVC, MVP, MVVM, VIPER에대해 확실하게 잡기

원문https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52#.wtcp3gqzw

UPD: NSLondon에대해 내가 발표한 슬라이드 자료가 이 링크 있다.

iOS에서 MVC 사용한다는게 다소 이상하게 느껴질 있다. 당신은 MV모데VM으로 바꾸려고 생각해본 적이 있는가? VIPER 적용시켜볼 생각을 적은 있으나, 그게 의미있는 것인지 확신이 들지 않는가?

글을 읽어 내려가면 것들에 대한 답을 찾을 있을 것이다. 또한 자유롭게 댓글로 의견을 제기할 있다.

당신은 iOS 환경에서 아키텍처 패턴에 대한 지식을 정리하고 싶을 것이다. 우리는 유명한 것들을 골라 한번 보고, 이론과 비교한 , 작은 예제들과 함께 연습해 것이다. 아래 링크는 당신이 특별히 관심있는 것을 연습할 있다.

디자인 패턴을 마스터하는것은 중독될 있으므로 조심해야한다: 전보다 많은 질문들이 생겨날 것이기 때문에.

- 누가 네트워크 리퀘스트를 소유하여야하나: 모델이냐 컨트롤러냐?
- 새 뷰의
어떻게 모델을 넘겨주나?
- 누가 새로 생긴 VIPER 모듈을 생성해야하나: Router Presenter?

아키텍처를 고르는데 신중해야하는가?

당신이 만약 개발을 하다가 디버깅을 해야하는데 엄청난 양의 클래스와 엄청난 양의 다른 것을 비교해야 하며, 이게 아키텍처가 없는 상황이라면, 당신 클래스의 어떠한 버그를 찾지도 고치지도 못하는 상황을 맞이하게 것이다. 우리는 클래스의 모든 속성을 머릿속에 담아두고 있을 없다. 만약 그짓을 하다보면 중요한 세부적인 요소를 놓힐 수가 있다. 만약 개발하면서 이런 경험을 이미 해보았다면 아래와 같은 것을 겪어봤을 것이다.

  • 클래스가 UIViewController 자식클래스이다.
  • 당신의 데이터들이 UIViewController에서 바로 저장된다.
  • UIView들이 거의 아무 일도 하지않는다.
  • Model 데이터 구조이다.
  • 유닛 테스트로 아무것도 하지 않는다.

그래도 애플의 가이드라인이나 애플의 MVC(링크) 따랐다해도 이러한 상황은 생길 있으니 너무 낙담하지는 마라. 애플의 MVC 뭔가 잘못되었고, 우리는 그걸 바로잡을 것이다.

좋은 아키텍처의 특징 정의해보자:

  • 엄격한 룰에 따라 개체들간의 책임 분리(Distribution) 균형있게 해야한다.
  • 첫번째 말한 특징으로부터 나올 있는 테스트들이 가능(Testability)해야한다. (그리고 걱정마라: 적절한 아키텍처를 고른다면 어렵지 않을것이다.)
  • 사용하기 편해야(Ease of use)하고 유지보수하기 쉬워야한다.

분리해야하나?
분배는 우리가 이게 어떻게 동작하는지 알아낼려고 노력하는 동안 우리의 뇌에서 균등하게 생각하도록 해준다. 만약 당신이 천재라 생각되면 그냥 하던대로 해라. 그러나 능력은 선형적으로 커지니 않을 뿐더러 광장히 빨리 한계에 도달해버린다. 그러므로 가장 빨리 복잡한 것을 극복하는 방법은 단일 책임 원칙으로 수많은 개체들의 책임을 쪼개는 것이다.

테스트 가능해야하나?
이미 유닛테스트에대한 중요성을 알고 있는 사람에게 던지는 질문이 아니라, 기능을 추가한 일때나, 클래스의 몇몇 복잡성을 리팩토링을 하기 위해서 테스트에 실패하는 사람들이 하는 의문이기도하다. 이것은 테스트가 런타임 내에서의 이슈를 찾는데 도와주며, 반대로 실유저에게 이슈가 발생한다면 그걸 고친 앱을 다시 실유저가 다시 사용하기까지 일주일씩이나 걸린다.

사용하기 쉬워야하나?
가장 좋은 코드가 뭔지는 한번 언급해 가치가 있다: 하나도 작성하지 않은 코드이다. 따라서 적은 양의 코드는 버그가 적다. 게으른 개발자 말을 빌려 적은 코드를 작성 하기를 갈망하며 이것은 코드를 설명해야하면 안된다. 또한 당신이 눈을 감고 허우적대며 유지보수하는 솔루션을 원치도 않을 것이다.

필수 MV(X)

요즘은 아키텍처 설계를 할때 수많은 선택지가 있다:

위에서 세개(MVC, MVP, MVVM) 아래 3 카테고리중 하나는 들어가있다:

  • Models데이터나 데이터 접근 레이어(Person 클래스나 PersonDataProvider 클래스와 같이 데이터를 다루고있는) 소유를 책임지는 부분
  • Views레이어에 표현되있는 것을 책임지는 부분(GUI), iOS 환경에서는 'UI' 접두로 붙는다(역자주: UILabel, UIView 등등..).
  • Controller/Presenter/ViewModelModel View 붙여준다. 보통 유저가 View에서 어떤 액션을 취할때 Model 변경하거나 Model 변경되었을 , View 갱신하는 책임을 가지는 부분

개체들을 나눌때 이점:

  • 이전보다 이해할 있다(이미 알고 있다 하더라도).
  • 재사용 가능하다(대부분 View Model 적용 가능하다).
  • 독립적으로 테스트 가능하다.

어서 MC(X) 패턴을 시작하고 나중에는 VIPER까지 해보도록 하자.

MVC

이전에는 어떻게 사용해왔을까?

애플의 MVC 살펴보기 전에 전통적인 MVC 어떻게 사용되었는지 보자.

Traditional MVCTraditional MVC

경우는 View 범위가 정확하지 않다. Model 바뀌고나서 Controller에의해 한번 랜더링(rendering) 된다. 웹페이지에서 다른 페이지로  수 있는 링크를 누른 , 다시 로딩되는 것을 생각해봐라. iOS 앱에서 전통적인 MVC 구현하는것은 가능할지라도 구조적인 문제때문에 효과적으로 처리할 없으며 당신 앱이 그러기도 원치 않는다.— 모든 개체가 둘씩 묶여있고, 개체는 다른 두개에 대해 알고있다. 이것은 각기 그들이 재사용성을 심각하게 줄여버린다. 이러한 이유로 우리는 흔히 쓰는 MVC 작성하는 또한 스킵 하겠다.

전통적인 MVC 최신 iOS 개발에 적합해 보이지 않는다


Apple’s MVC

기대한것..

Cocoa MVCCocoa MVC

원래 Controller Model View 연결시켜주는 역할을 하므로 서로에 대해 알필요가 없다. 그중에 가장 재사용 불가능한 것이 Controller이며, 우리도 그걸 알고있다. 따라서 우리는 모든 특이한 로직을 Model 아닌 Controller 넣어야한다.

이론적으로는 굉장이 전략적으로 보이지만 뭔가 문제가 있다. 당신은 MVC 컨트롤러 덩어리(Massive View Controller) 불리는걸 들은적이 있을지도 모른다. 나아가 View Controller offloading iOS 개발자들에게 중요한 토픽이다. 애플은 전통적인 MVC 조금 개선하여 사용하여서 이런 일이 일어나버린건가?

Apple’s MVC

실체는..

Realistic Cocoa MVCRealistic Cocoa MVC

Cocoa MVC View Controller 덩어리 작성하도록 만들어버린다. 이유는 View들의 라이프 사이클 안에서 뒤엉키는데 그것들을 분리해내기가 어렵기 때문이라고 말한다. 너가 Model*비지니스 로직이나 데이터 변환같은 것을 없애는 능력을 가졌을 지라도 대부분의 View에서 반응하면 액션을 Controller로 보내게 될것이다. 뷰 컨트롤러는 결국 모든 것의 델리게이트(delegate)나 데이터소스(data source)가 될테고, 종종 네트워크 요청과같은 처리도 하고 있을지 모른다. 

이런 종류의 코드를 얼마나 많이 보았는가:

Model 함께 직접적으로 구현된 View cell MVC 가이드라인을 위반한다. 그러나 항상 그렇게 사용하며 사람들은 이게 문제가 아니라고 느낄때가 많다. 좀더 MVC 따르고자 한다면 cell Controller에서 구성하고 View 안에 Model 거치지 않아햔다. 그러나 그렇게해버리면 Controller 커져버리게 될것이다.

Cocoa MVC View Controller 덩어리의 이유이기도하다.

문제는 유닛 테스트(여러분 프로젝트에 있기를 바란다)에까지 나타날 거라는걸 확신할수 없다. 당신의 View Controller View 붙어있고, 이렇게하면 그들의 View 라이프 사이클이나 테스트를 위한 View 만들기가 어려워지기 때문에 테스트가 힘들어진다. 반면 View Controller 코드를 작성하고 있으면 당신 비지니스 로직은 가능한 View 레이아웃 코드로부터 분리될것이다.

간단한 예제를 보자:

MVC 분리하면 현재 View Controller안에서 동작되게 있다.

테스트하기 좋아보이지는 않다. 우리는 greeting 생성을 GreetingModel 클래스에 옮겨 넣을 있다. 그러나 GreetingViewController안에서 UIView 연관되어있는 메소드(viewDidLoad, didTapButton) 호출하지 않은체 상연 로직(예제에는 로직이 많이 없지만) 테스트를 수가 없다.

사실, 로딩테스트는 디바이스를 바꿔가며(iPhone4S, iPad 등등으로) 확인해보는 것에대한 이점이 없다. 그래서 Unit Test target configuration에서 “Host Application” 지우고 시뮬레이터 없이 테스트 해보는것을 추천한다.

View Controller 사이의 상호작용은 Unit Test로써 테스트하기에 좋지 않다.

위에서 말한건, Cocoa MVC 사용하는것은 별로 좋지 않은 선택인것 같아 보인다는 것이다. 그러나 글의 서두에 언급했단 특징들의 용어를 정의했었다.

  • Distribution사실 뷰와 모델은 분리되 있지만, View Controller 붙어있다.
  • Testability거지같은(?)분리 때문에 아마 Model 테스트 가능할 것이다.
  • Ease of use다른 패턴에 비해 코드가 적게 든다. 추가로 많은 사람들이 친숙하게 사용하기도하며 경험해보지 못했던 개발자도 쉽게 접근할 있다.

Cocoa MVC 아키텍처 쪽에 시간을 투자할 시간이 별로 없을때 선택하는 패턴이며, 작은 프로젝트에는 지나친 유지보수 비용이 들어간다는 것을 느낄 있을 것이다.

Cocoa MVC 개발 속도면에서는 최고의 아키텍처 패턴이다.


MVP

전달될거라 약속한 Cocoa MVC(Cocoa MVC’s promises delivered)

Passive View variant of MVPPassive View variant of MVP

사진이 애플의 MVC 굉장히 비슷하지 않는가? 이것의 이름은 MVP(Passive View Variant)이다. 그럼 애플의 MVC MVP 같다는 걸까? 그렇지 않다. MVC에서는 View Controller 서로 붙어있지만 MVP에서 중간다리 역할을 하는 Presenter View Controller의 라이프 사이클에 아무런 영향을 끼치지도 않으며, View 쉽게 테스트가능한 복사본(moked) 만들 있다. 그러므로 Presenter에는 레이아웃 관련 코드가 없고 오직 View 데이터와 상태를 갱신하는 역할만 가진다.

만약 UIViewController View라고 말했으면 어떨까.

사실 MVP 입장에서는, UIViewController 자식클래스에 Presenter 아닌 View들이 있다. 이러한 구분은 좋은 테스트 용이함을 제공하지만, 수작업의 데이터나 이벤트 **바인딩 따로 만들어야하기때문에 개발 속도에대한 비용도 따라 온다. 아래 예제에서 확인할 있다:

Important note regarding assembly(중요 요약 모음)

MVP 세개의 다른 레이어를 가짐으로써 이런 문제 집합이 처음으로 나타난 패턴이다. 그러므로 뷰가 Model에대해 알기를 원치 않기 때문에, 현재 View Controller(View 것이다) 모아서 동작시키는건 옳지 않으므로 다른곳에서 동작시켜야한다. 예를들어, 우리는 앱에서 범용적인 모아서 수행하거나 View-to-View 보여주기위한 Router 돌릴 있다. 이슈는 MVP 뿐만아니라 아래 모든 패턴들에게도 나타나는 문제이기도하다.

이제 MVP 특징 보자.

  • DistributionPresenter Model 책임을 거의 분리했고 View 빈껍데기가 셈이다( 예제에서는 Model 빈껍데기 같았지만..)
  • Testability최고로 좋다. View 재사용가능 덕분에 대부분의 비지니스 로직을 테스트 있다.
  • Easy of use위에서 비현실적인 예제에서는 MVC에비해 코드의 양이 2배정도 많이 들지만 MVP 아이디어는 굉장히 명료하다.

iOS에서 MVP 테스트하기엔 좋지만 코드가 길어진다.


MVP

With Bindings and Hooters

MVP 다른 버전(MVP Supervising Controller) 있다. 이러한 다양한 MVP들은 Presenter(Supervising Controller) View로부터 액션을 처리하고 View 적합하게 변경하는 동안 View Model 직접 바인딩을 포함한다(?).

Supervising Presenter variant of the MVPSupervising Presenter variant of the MVP

그러나 우리가 이미 이전에 배웠듯, 막연하게 책임을 나누는건 좋지않은데다, View Model 합쳐버린다. 이것은 Cocoa 데스크탑 개발에서 어떻게 동작하는지와 비슷하다.

전통적인 MVC와같이, 결함이 있는 아키텍쳐의 예제를 찾기 힘들었다.

MVVM

마지막이자 MV(X) 종류의 최고 종류

MVVM은 최근에 나온 MV(X) 종류이다. 그러므로 이전의 MV(X) 문제들을 해결하여 나오기를 기대해보자.

이론적으로는 Model-View-ViewModel이 굉장히 좋아보인다. ViewModel은 이미 우리에게 친숙할테고, View Model 이라불리는 중계자 또한 마찬가지일 것이다.

MVVMMVVM

MVP 비슷하다:

- MVVM View Controller View라고 일컫는다.
- View Model 서로 연결 되어있지 않다.

추가로 MVP Supervising버전에서 처럼 binding 있다; 그러나 여기서는 View Model 관계가 아닌 View View Model 사이의 관계이다.

그래서 실제 iOS에서 View Model 뭘 의미할까? 그것은 기본적으로 UIKit인데 그로부터 View 독립된 표현이거나 상태이다. View ModelModel에서 변경을 호출하고 Model 자체를 갱신한다. 따라서 View나 View Model 사이에서 바인딩을 하며, 적절히 처음것이 갱신된다.

Bindings(바인딩)

MVP 파트에서 간당하게 언급한적이 있다. 그러나 여기서 좀 더 이야기 해보자. 바인딩은 OS X 개발을 위한 박스(역자주: 프레임워크나 툴을 말하는듯 합니다)에서 나왔으나 iOS 툴박스에서는 보지못한다. 물론 KVO나 notification을 가지고 있긴 하지만 그것이 바인딩만큼 편리하지는 않다.

그러므로 

- 바인딩 기반 라이브러리인 KVO에는 RZDataBinding 혹은 SwiftBond 이런게 있다.
- The full scale functional reactive programming beasts like ReactiveCocoa, RxSwift or PromiseKit. (번역하지 못했습니다ㅠ)

사실 요즘엔 MVVM을 들으면 바로 ReactiveCocoa를 말하기도하며, 반대도 그렇다(역자주: 뭐라고??????). 비록 간단한 바인딩으로 MVVM을 만드는게 가능하기는 하나 ReactiveCocoa (혹은 siblings)으로는 최고의 MVVM을 만들수 있게 해준다.

Reactive 프레임워크에는 쓰디쓴 진실이 하나 있다: 큰 책임엔 큰 에너지가 필요하다. Reactive를 사용하게되면 굉장히 혼잡해지기 쉬워진다. 다른말로 설명하자면, 문제가 하나 생기면 앱을 디버깅하는데 시간이 굉장히 많이 걸리며, 아래와 같은 콜 스택을 보게 될것이다.

Reactive DebuggingReactive Debugging

우리의 예제에서는 FRF 프레임워크나 KVO까지도 배보다 배꼽이 식이다. 대신에 showGreeting 메소드를 이용하여 갱신하기 위한 View Model 명백하게 물어 것이고 greetingDidChange 콜백 함수를 위해 작은 프로퍼티를 사용할것이다.

이제 돌아와서 특징들을 나열해보겠다:

  • Distribution우리의 작은 예제에서는 명료하게 나타나지 않았지만, 사실 MVVM View MVP View보다 책임이 많다. 왜냐면 두번째 것이 Presenter 포워드(forward)하고 자신를 갱신 하지는 않은 그 때, 바인딩을 세팅함으로써 View Model에서 처음 것의 상태를 갱신한다.
  • TestabilityView Model View에대해 전혀 모르며, 이것이 테스트하기 쉽게 해준다. View 또한 테스트 가능하지만 UIKit 의존이면 그러고 싶지 않게 원하게 될것이다.
  • Easy of use우리 예제에서는 MVP 비슷한 양의 코드나 나왔으나 View에서 Presenter으로 모든 이벤트를 포워드하고 View 갱신하는 실제 앱에선 바인딩을 사용했다면 MVVM 코드 양이 적을 이다.


MVVM 앞에서 말한 장점들을 합쳐놓은것 같아서 굉장히 매력적이다. 그리고 View입장에서 바인딩을 하기 때문에 View 갱신하는데 추가적인 코드를 필요로 하지도 않는다. 그럼에도불구하고 테스트에도 굉장히 좋은 수준이다. (역자주: 완전 극찬이군요)


VIPER

iOS 설계에 레고 조립 경험을 적용하다

VIPER 마지막 지원자다. 이것이 특별히 흥미로운 이유는 MV(X) 카테고리로 부터 나온 녀석이 아니기 때문이다.

이제부터 당신은 책임의 단위가 매우 좋다고 인정하게 될것이다. VIPER 분리된 책임이라는 아이디어에서 생겨난 다른 iteration 만드며, 이번 시간에는 다섯 레이어를 것이다.

VIPERVIPER

  • Interactor데이터 개체나 네트워킹과 연관되어있는 비지니스 로직을 가지고, 서버로부터 그들을 받아오거나 개체 인스턴스를 만드는것을 좋아한다. 이러한 목적으로을 위해서 당신은 VIPER 모듈의 일부로써 몇몇 Services Managers 사용해야 것이나, 다소 외부 의존도가 있을것이다.
  • Presenter—Interactor에서 발생되고 비지니스 로직과 관련있는 (그러나 UIKit과는 관련없는) UI 가진다.
  • Entities일반적인 데이터 객체이다. (데이터 접근 레이어(data access layer) Interactor 책임이기 때문에 Entities 아니다.)
  • Router—VIPER 모듈 사이의 연결고리(seques) 책임을 가진다.

기본적으로 VIPER 모듈은 스크린(screen)이나 당신 어플리케이션의 모든 ***사용자 스토리(user story) 있다인증을 생각해보면 스크린이나 여러개가 하나에 연관되어 있을 있다. 얼마나 작은 “LEGO” 블럭어여야 할까?—전적으로 당신에게 달려있다.

MV(X) 종류와 비교하면, 우리는 책임의 분리가 다르다는걸 확인할 있다:

  • Model(data interation) 로직은 데이터 구조로써 Entities 함께 Interactor 이동된다.
  • 오직 Controller/Presenter/ViewModel Presenter 이동하는 UI 표시 책임을 갖지만, 데이터를 변경할 능력은 없다.
  • VIPER 명시적으로 Router에의해 결정된 네비게이션 책임을 해결한 패턴이다

iOS 어플리케이션 입장에서는 각기 방법으로 라우팅 하는게 도전이라고 수있다. MV(X) 패턴들은 이러한 이슈가 발생하지 않는다.

토픽이 MV(X) 패턴을 반영하지 못했으므로, 예제 또한 라우팅이나 모듈간의 interaction 반영하지 않았다

이제 다시 돌아와 특징들을 살펴보자:

  • Distribution틀림없이 VIPER 책임 분배의 최고봉이다.
  • Testability분리가 잘되있는만큼 테스트에도 좋다.
  • Easy of use마지막으로 여러분이 이미 추측한것처럼 두배 정도의 유지보수 비용이 들것이다. 매우 작은 책임을 위해 수많은 클래스 인터페이스를 작성해야하는 점이다.

그래서 레고는 뭐였나?

VIPER 사용하는 동안 레고로 엠파이어 스테이트 빌딩(위키:엠파이어 스테이트 빌딩은 1931년부터 1972년까지 세계 최고층 건물이었다.) 쌓는 기분이 들것이자, 이것이 유일한 문제이기도하다. 아마 당신 앱에 VIPER 적용시키기에 이를수도 있고 좀더 간편한것으로 고려해도 좋다. 몇몇 사람들은 이걸 아예 무시하고 대포에다가 화살을 쏘아대는 경우도 있다. 지금은 비록 엄청나게 높은 유지보수 비용이 들지만, 그들이 미래에는 그들의 앱에 VIPER 필요할지도 모른다는걸 알고있을거라 생각한다. 만일 당신도 생각이 같다면 Generamba(VIPER 골격을 제공해주는 ) 한번 사용해보길 바란다. 개인적으로는 이건 새총 대신에 자동 대포 조준 시스템을 사용하는 느낌이긴하다.


결론

우리는 몇몇 다른 아키텍처 패턴을 살펴보았고, 무엇이 당신을 괴롭히는지 찾아냈기를 바란다. 그러나 여기에 완벽한 해답은 없고 아키텍처를 선택하는게 당신의 특별한 상황에서 문제의 비중을 등가교환하게 된다는걸 알게되었음을 의심하지 않는다

그러므로 앱에 다른 아키텍처를 섞어 사용하는것은 자연스러운 일이다. 예를들어 MVC 시작했지만 어떤 화면에서만 MVC 관리하기 어려워지는 상황이 생기면 부분만 MVVM으로 바꿀 있다. 이런 아키텍처들은 서로 공존할 있기때문에, 다른 화면이 MVC 골격으로 동작하면 바꿀 필요가 없다



Make everything as simple as possible, but not simpler 
이론은 가능한 간단해야하지만지나치게 간단해서는 안된다
— Albert Einstein




*비지니스 로직 (business logic)

**바인딩 (binding)

***사용자 스토리 (user story)



iOS 아키텍처 관련 번역글



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : medium.com/@mandrigin/ios-app-performance-instruments-beyond-48fe7b7cdf2?source=userActivityShare-d07a45aa48c6-1455001286


iOS 퍼포먼스: Instrument 이상

유저들은 기다리는걸 굉장히 싫어한다. 그들은 앱이 뭔가 초기화 한다는것을 전혀 모른 상태에서 자신들의 업무 처리를 최대한 빠르게 하고싶어한다. 그러므로 앱이 모두 즉각적으로 시작할 있다면 인터페이스가 흐르듯 부드럽게 넘어갈 있다. 퍼포먼스가 나오는 앱은 소프트웨어 마켓에서 경쟁력있는 특징중 하나이다. 개발자로서 앱이 퍼포먼스있게 동작하게 하는것을 자랑스러워하는 것을 원하는것도 이유이다.

그러나 많은 이들이 겪듯 퍼포먼스 최적화는 다루기 어려운 문제이다. 대부분 문제를 직관적으로 접근하기 어렵다. 각기 정리된 측정법 없이는 앱이 느려지는 이유를 알아내는건 매우 어렵다.

당신의 퍼포먼스를 최적화하기 위해서는 데이터에 기반한 결론을 내려야한다. 장에서는 당신의 앱에 서로 다른 부분에서 퍼포먼스 측정 데이터를 어떻게 얻어낼 있는지 보여줄것이다.

파트에서는 아래의 것들을 다룰 것이다.

  • 앱에서의 CPU, GPU, 메모리&베터리 사용량
  • 반응성(리스폰시브니스)
  • 앱시작동안의 시간
  • 유저로부터 퍼포먼스 데이터를 얻는

바로 시작해보자!

CPU, GPU, 메모리&베터리 사용량

첫번째 할일은 CPU, GPU, 메모리를 과하게 사용하는 비효율적인 코드를 찾아내는 일이다. 애플은 일을 하기위한 좋은 (Instruments) 제공한다.

우리가 주로 측정해야할 부분은 아래 4 정도이다.

  • CPU ("Time Profiler" 툴 이용)
  • GPU (“Core Animation” 툴 이용)
  • 메모리 사용량 (“Allocations” 툴 이용)
  • 베터리 소모량 (“Energy diagnostics” 툴 이용)

WWDC 비디오는 당신 앱을 분석하기위한 최고의 정보를 제공한다.
아래는 시작하면서 몇 개 골라보았다:


반응성(Responsiveness)

퍼포먼스 측정(이해 "측정"으로 줄여서 말하겠습니다)에 있어서 다음으로 중요한 것은 UI 반응성이다. 터치 헨들링은 메인 쓰레드에서 발생한다. 메인쓰레드에서 시간이 걸리는 작업을 하면, 앱은 버벅거리게 될것이다.

몇몇 동작은 CPU 사용하지 않는 주제에 시간을 잡아먹기도 한다. 만약 메인쓰레드에서 동기화 콜을 불렀다면, 콜이 얼마나 시간이 걸리는지 알아내는것이 문제를 해결하는 방법일것이다.

시간을 측정하기 위해 로그를 찍어볼 수도 있다.

한가지 다른 방법은 Viber 개발자들이 만든 솔루션으로 나타내는 것이다. 솔루션은 메인 쓰레드 하나를 400ms보다 많이 멈추지 않게 지켜보고, 체크하는 특별한 스레드를 가지고있다.

Testing Responsiveness (from Viber’s presentation at NSSpain)


Testing Responsiveness (from Viber’s presentation at NSSpain)


많은 정보는 발표자료(PDF, 7MB)에서 확인할 있다.

데이터를 이용하여 너무 많은 시간이 걸린 (메인싸레드가 멈추는데에는 400ms정도가 적당한 최대치이며, 보면 많은 정보를 얻을 있다.) 찾아내고, 이것을 최적화 시킬지 메인쓰레드 밖으로 보내던지 해야한다.

시작 시간

다음으로 중요한 측정은 앱의 시작하는데 걸리는 시간이다. 전형적인 유저는 당신의 앱을 오직 몇분만 사용한다. 시작시간은 앱의 이미지에 좋지않은 영향을 준다.
여기 시작의 2가지 경우가 있다.

  • Cold 시작 : 당신의 프로세스가 동작하지 않고, OS 의해 실행된다.
  • Warm 시작 : 당신의 앱은 최소화되나 죽지않는다. 이것은 백그라운드로부터 다시 불러온다.


색션에서는 리소스를 많이 잡아먹는 Cold 시작에 초점을 맞출것이다
아래에 iOS 앱의 시작 순서가 나와있다.


The Application Startup Phases (from the documentation)

1. 시작하는데 걸리는 시간을 측정한다.

우리는 main()에서부터 applicationDidBecomeActive:까지 시간이 얼마나 걸리는지 측정해야한다.

앱의 기능을 보여주면서 시간을 잡아먹게 하지 말아야한다. cold 시작 시간을 1 미만으로 떨어뜨리려고 노력하는 것이 좋다.

2. 시작하는 순서에서 부분별로 측정한다.

보통 시작시간의 전체만 아는 것은 충분하지않다. 어떤 부분에서 시작시간을 느리게 만들었는지 아는것 또한 중요하다.
밑에 보이는 것들이 가장 중요한 부분들이다

  • -[AppDelegate application:didFinishLaunchingWithOptions:] - 콜백은 런칭이미지(혹은 스토리보드) 보여질때 호출된다. 곧바로 메소드로부터 return되면 실제 UI 로딩을 시작한다.
  • -[UIViewController loadView] - 앱에 커스텀 뷰를 불러와야한다면, 여기서 뷰를 초기화하게된다.
  • -[UIViewController viewDidLoad] - 뷰가 불러와졌고, 마지막 초기화의 시간이다.
  • -[AppDelegate applicationDidBecomeActivate:] - UI 이미 초기화되어있지만, 콜백이 끝날때까지 블럭되있다. 메소드는 백그라운드로부터 restore될때 호출된다.

몇몇 매소드가 너무 시간이 많이 걸린다면, 최적화시켜야한다.

3. “under pressure” 시작 시간을 측정한다.

전형적으로 현실과 테스트 환경은 다르다는걸 알아야한다.

당신의 앱은 안타깝게도 "현실세계"에서 작동된다. 유저는 종종 다른 앱에서 당신의 앱을 열기도 한다. “다른 굉장히 무거운 앱일 수도 있다. 당신의 앱이 시작될때 다른 무거운 앱은 백그라운드로 가면서 데이터를 저장하려고 있으며, 그런 상황에서의 시간 측정은 굉장히 중요하다.

그런 테스팅(무거운 다른 앱에서 내 앱을 열어보는)을 통해 예측하지 못한 결과를 만들 수도 있기 때문에, 이전에는 코드가 완벽하게 안정적이다가도, 저런 상황에서는 느려질 수도 있다는걸 명심해야한다.

4. 앱이 이미 시작되었지만 여전히 소용없다.(무슨 의미인지 모르겠습니다)

만약 당신 앱이 곧바로 UI 불러오는것이 무의미한 일이라면, 런칭화면이 끝나지 않을 것이다. 비록 UI 불러와지고 반응이 왔었어도, 불러오는데 준비를 위한 데이터가 필요하며, 또한 역시 시작 시간을 재어봐라.

당신의 유저로부터 측정값을 수집해야한다.

모든 측정은 테스트 환경에서 가능할 것이다. 그것은 반드시 필요하지만 너무 완벽하게까지 할필욘 없다. 만약 당신 앱이 인기있는 앱이라면, 만약 유저들이 해외시장을 기반으로 한다면, 몇몇 유저들은 당신이 예상하지 못한 아주다른 환경을 사용할 수도 있다.

추측하기에 다를 있는 환경들이다.

  • 네트워크 상태
  • 하드웨어
  • 소프트웨어(OS 버전, Jailbreak(탈옥...)
  • 기기의 남은 용량
  • 기타 등등

분명 당신 개발실에서 측정한 것은 모두 안정적인 상태일지라도, 별점하나로 컴플레인("앱이 너무 느림ㅡㅡ") 리뷰를 받을 수도 있다. 어떻게 대처하면 될까?
퍼포먼스 측정의 집합을 정의하고(혹은 KPI) 실유저로부터 얻어와야한다. 이것은 대부분 통계 패키지와 함께 사용할 있다.

아래는 당신의 유저로부터 얻을 있는 KPI 예시들이다.

  1. cold 시작시간
  2. warm 시작시간
  3. 마디별 시작시간
  4. 반드시 서버로부터 다운받아야할 것들의 소요시간
  5. 메인쓰레드가 400ms보다 오래 블럭되는
  6. 메모리 워닝이 일어나는
  7. FOOMS(링크)
  8. UI 블럭되거나 쓰잘때기없는 동작의 길이 

결론

쉽게 설명하자면 퍼포먼스 측정은 Instruments.app 프로그램을 열면서 시작하며, 앞에서 본 것 말고도 다양한게 있다. 몇몇 소개된 방법은 구현하기 쉽지만 어떤건 시간과 노력이 필요할 것이다. 어쨋든 솔루션들은 당신의 퍼포먼스 이슈를 찾고 해결을 위한 모니터링하는데 도움을 줌과 동시에 즐겁게 만들것이다.

별점5 리뷰를 받기를 바란다!


+) 이해하면서 의역을 부분이 많이 있습니다. 잘못 이해한 부분이 있다면 지적해주시면 감사합니다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

invalid Toolchain. New apps and app updates must be built with the public (GM) versions of Xcode 6 or later, and iOS 8 SDK or later. Don't submit apps built with beta software.

앱 개발을 완료하고 앱 검수를 위해 앱을 업로드한다. 그다음 "심사를 위해 제출"을 누를 때 위와 같은 오류가 뜬다면 나의 경우 아래와 같이 해결했다.

원문 : http://stackoverflow.com/questions/32174954/submitting-app-from-building-in-xcode-6-4/32233429#32233429

Apps that you submit should be developed using the latest version of Xcode from the Mac App Store and should be built for publicly available versions of iOS, OS X, and watchOS — except when GM seeds are available. Now Mac App Store's Xcode is 6.4 and OS X Yosemite is Build 14F27. If you user xcode 6.4 on OS X El Capitan, you should follow the steps:

  1. Using Xcode, then archive your project
  2. Open organizer, find your .xcarchive file find .xcarchive file
  3. Right click the xcarchive file, choose [Show package Contents]
  4. Find Products/Applications/XXX.app/Info.plist
  5. then change [BuildMachineOSBuild] value to 14F27, just like this: example
  6. Now, you can go to Xcode->organizer, then 【Submit to App Store】




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

이번에 두번째 계산기를 개발하다가 만들게된 콤마찍기 알고리즘(?)입니다. 구상하기는 생각보다 복잡했고 시간이 많이 걸렸는데 짜는데 시간은 얼마 안걸렸네요.. (허망) 
아무튼 짠다고 고생했는데 혼자 썩히기엔 아까워서 공유합니다.

사용법

  • 기본적으로 convertCommaFormula: 를 사용하면 문자열을 반환해줍니다.
  • 추가적으로 convertCommaFormula:location: 을 사용하면 현재 커서 위치를 파라미터로 넣고 현재 커서 위치까지 반환(NSDictionary)해 줍니다. 
저는 convertCommaFormula:location: 메서드를 구현해서 사용했었고 편의를 위해 convertCommaFormula: 를 뽑아보았습니다.

NSString *text = @"482193423+1284+41-(327123.4+2)";
NSString *resultText = [NSString convertCommaFormula:text];
NSLog(@"result : %@", resultText); 
// result : 482,193,423+1,284+41-(327,123.4+2)


코드

NSString+ConvertComma.h

#define COMMA_INDEX 3

@interface NSString (ConvertComma)

+ (NSString *) convertCommaFormula:(NSString *)formula ;
+ (NSDictionary *) convertCommaFormula:(NSString *)formula cursorlocation:(NSInteger)location ;

@end


NSString+ConvertComma.m

@implementation NSString (ConvertComma)


+ (NSString *) convertCommaFormula:(NSString *)formula {
    return [NSString convertCommaFormula:formula cursorlocation:0][@"text"];
}

+ (NSDictionary *) convertCommaFormula:(NSString *)formula cursorlocation:(NSInteger)location {
    
    NSString *beforeCursorText = [formula substringWithRange:NSMakeRange(0, location)];
    NSString *removedCommaBeforeCursorText = [beforeCursorText stringByReplacingOccurrencesOfString:@"," withString:@""];
    NSString *removedText = [formula stringByReplacingOccurrencesOfString:@"," withString:@""];
    
    NSMutableString *mBeforeText = [NSMutableString stringWithString:removedCommaBeforeCursorText];
    NSMutableString *mOriginText = [NSMutableString stringWithString:removedText];
    // 12|74
    
    if (![LayoutInfo shared].isNotComma) {
        NSInteger numbercount = 0;
        for (NSInteger i=mOriginText.length-1; i>=0; i--) {
            char c = [mOriginText characterAtIndex:i];
            if ('0'<=c && c<='9') {
                numbercount++;
            } else if (c=='.') {
                numbercount = 0;
                // 다시 되돌아가면서 콤마 지우기
                for (NSInteger j=i; j<mOriginText.length; j++) {
                    char cc = [mOriginText characterAtIndex:j];
                    if (cc==',') {
                        [mOriginText replaceCharactersInRange:NSMakeRange(j, 1) withString:@""];
                        if (j<mBeforeText.length) {
                            [mBeforeText replaceCharactersInRange:NSMakeRange(j, 1) withString:@""];
                        }
                        j--;
                    } else if ('0'<=cc && cc<='9') {
                        continue;
                    } else {
                        if (i==j) continue;
                        break;
                    }
                }
            } else {
                numbercount = 0;
            }
            
            
            if (numbercount==COMMA_INDEX+1) {
                numbercount-=COMMA_INDEX;
                // i+1번째에 ,추가
                
                [mOriginText replaceCharactersInRange:NSMakeRange(i+1, 0) withString:@","];
                if (i+1<mBeforeText.length) {
                    [mBeforeText replaceCharactersInRange:NSMakeRange(i+1, 0) withString:@","];
                }
            }
        }
    }
    
    return @{@"text":mOriginText, @"cursorindex":@(mBeforeText.length)};
}


@end




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

안녕하세요. 아이폰 앱 개발자 카나피오입니다. 저는 지난 3년간 아이폰 앱 개발을 하면서 모았던 유용한 링크를 공유합니다. 대부분 실제로 사용하고 있는 링크를 선별했고, 필요 없는 링크는 제외했습니다. 또한 최대한 자료를 무료로 제공받을 수 있는 링크만 뽑아보았습니다. 혹시 좋은 링크가 있다면 댓글에 달아주세요! 서로 공유하면 좋으니까요.


디자인 참고
Dailygood Design
디자인 관련 링크 모음 사이트. 유명한 디자인 사이트를 모아놓은게 아니라 필수적인 디자인 사이트를 모아놓았다. 디자인 사이트를 모아놓은 사이트조차 예쁘다. 나는 즐겨찾기 해놓고 종종 들어간다.

Dribbble
앱이나 웹의 UI를 참고할 수 있는 디자인 웹 커뮤니티 사이트. 내가 가장 애용하는 디자인 참고 사이트이기도 하다. 보통 앱 개발을 시작할 때 이 사이트를 켬으로써 화면 기획을 시작하기도 한다. UX보다는 UI 디자인이 많다. 예쁘다.

Pinterest
영감을 얻기 위한 사이트. 앱, 웹, 제품 등등 온갖 디자인들이 있다. 더 나아가 영감을 얻을 수 있는 다양한 작품들도 있다. 카테고리도 다양하게 있어서 자신의 취향에 맞게 팔로우하여 볼 수 있고 자신이 저작권에 상관없이 자료들을 모을 수도 있다.

Behance
어도비사가 만든 디자인 커뮤니티 사이트. 어도비의 지원을 빵빵하게 받는 만큼 자료가 많다. (근데 웹사이트가 좀 느리다ㅠ)

Vimeo
영상 디자인 참고 사이트. 작품성 있는 영상 자료들이 많이 올라가있다. 유튜브와 비슷하지만 용도는 조금 다르다. 훨씬 질 좋은 영상 자료들이 올라가있고 고품질을 지향한다. 나는 앱개발 영상을 제작할 때 주로 사용하며, 앱개발시 모션 그래픽을 참고하기도 한다.


디자인 리소스
Noun Project
아이콘 모음 사이트. 온갖 아이콘이 많이 있다. 하악하악


개발
Google Play Console
구글 개발자 페이지.

iTunes Connect
애플 개발자 페이지.


통계
App Annie
개발자 계정을 연결하여 자신의 앱(또는 다른 앱)의 실시간(1시간 정도의 시간차가 있습니다.) 순위권이나 다운로드 수, 수익, 사용자 리뷰, 앱스토어 피쳐드 등등의 퍼블리싱 정보를 얻을 수 있다.

구글 어널리틱스
구글에서 제공하는 서비스 분석기. 자신의 앱에 코드를 심어 놓으면 실시간 사용자나 누적 사용자, 활성 사용자 등 다양한 사용자 정보의 통계를 확인 할 수 있다. 화면 전환이나 이벤트 등을 등록해놓고 사용자가 얼만큼의 빈도로 이벤트를 발생시키는지 알 수 있다.


참고
아이콘 메이커
1024*1024 크기의 아이콘을 넣으면 개발에 쓰이는 아이콘 크기별로 뽑아준다. 이게 겁나 편하다. 안드로이드, 아이폰, 아이패드, 애플워치 등등 해당 디바이스의 아이콘을 다 뽑아준다.

iOS 폰트 리스트

애플 앱스토어 리뷰 시간
http://appreviewtimes.com/
애플 앱스토어의 앱 리뷰 대기 시간을 평균 내어주는 사이트.


추가
유용한 사이트 모음
https://github.com/etdot/bookmark
좀 더 다양한 사이트 모음. 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,


맥에서 부산대 웹메일 연동하는 방법에 대해 간단하게 소개해드리려고합니다. 
그냥 스샷 따라오면 됩니다.

왜이렇게 어렵게 되있는지 아직 이해할 수 없지만.. 일단 울며 겨자먹기식으로 성공했습니다. 부산대 정보전산원쪽에서 이 부분을 쉽게 좀 처리해주면 좋겠는데 말이죠..

저는 아이폰과 맥 둘다 연결을 해보았고, 이 포스팅은 맥 연동 포스팅입니다. 아이폰과 맥의 연동 방법은 비슷했습니다. 다만.. 아이폰은 스샷찍는걸 까먹어서 맥용이라도 간단하게 올려볼까 합니다. 

아무튼 아이폰도 비슷하게 따라오시면 연동하실 수 있을거라 생각합니다.

"다른 메일 계정 추가..." 를 선택


전체 이름 : 아무거나 집어넣으세요
*이메일 주소 : 부산대 웹메일 이메일 주소를 넣구요
*암호 : 암호 넣으면 됩니다.


하고나면 위와 같은 창이 뜨는데 귀찮지요..

*계정 종류 : POP
*메일 서버 : webmail.pusan.ac.kr
사용자 이름 : 내이름!!
*암호 : 메일 계정의 암호 넣으면 됩니다.

(계정 종류를 POP으로 선택한다는 것에 유의하세요~)


로그인 하나봅니다..


방금전에는 받는거 설정한거구요. 이제 보내는거 설정합니다.

SMTP 서버 : send2mail.pusan.ac.kr
사용자 이름 : 웹메일 아이디
암호 : 웹메일 비밀번호

(여기서는 "send2mail.pusan.ac.kr" 꼭 이렇게 적어주셔야해요. 장난같지만 장난이 아니라 중요한 정보에요. 부산대 측에서 정한 보내는 메일서버입니다.)


이런거 떠도 그냥 "계속" 눌러가면서 진행하면 됩니다.




아마 다 하고나서 메일앱을 켜보면 부산대 메일 연동이 완료되있을 겁니다.

뭐이래 귀찮아??!!


'자유 블로그' 카테고리의 다른 글

블로그 css style 옵션들..  (0) 2014.10.25

WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : https://blog.risingstack.com/fundamental-node-js-design-patterns/

Translated by canapio
Help by soojin

디자인패턴에 대해 이야기할 때 당신은 singleton, observer, factory들을 생각할 것이다. 이 글은 단지 그것들에 대해서만 이야기하는것은 아니고 dependency injection이나 middleware와 같은 다른 일반적인 패턴과 함께 다룰것이다.

디자인 패턴이란?

디자인 패턴은 흔히 발생하는 문제를 재사용가능하게 일반화하여 해결한다.

Singleton

singleton 패턴들은 해당 "클래스"의 인스턴스 갯수를 한개로 한정한다. Node.js에서는 require을 사용함으로써 꽤 쉽게 싱글톤을 만들 수 있다.

//area.js
var PI = Math.PI;

function circle (radius) { 
  return radius * radius * PI;
}

module.exports.circle = circle; 

당신의 응용프로그램에서 싱글턴 객체를 얼마나 사용하든 상관없이; 오직 하나의 객체로 존재하게 될 것이다.

var areaCalc = require('./area');

console.log(areaCalc.circle(5));

require의 동작 덕분에, 싱글톤들은 NPM모듈들 사이에서 가장 일반적인 Node.ja 디자인 패턴들일 것이다

Observer

한 객체는 상태가 바뀔때 dependents나 observer의 리스트를 자동으로 유지하고 그것들을 알린다. Observer 패턴을 구현하기 위해서는 EventEmitter를 끌어 사용해야한다.

// MyFancyObservable.js
var util = require('util');  
var EventEmitter = require('events').EventEmitter;

function MyFancyObservable() {  
  EventEmitter.call(this);
}

util.inherits(MyFancyObservable, EventEmitter);  

이것이 그 방법이다; 우리는 단지 옵저버가 가능한 객체를 만들었다! 이것을 유용하게 만들기 위해서는 몇가지 기능을 추가하면 된다.

MyFancyObservable.prototype.hello = function (name) { 
  this.emit('hello', name);
};

잘 했다. 이제 우리의 observable은 이벤트를 발생시킬 수 있다. 이제 사용해보자!

MyFancyObservable.prototype.hello = function (name) { 
  this.emit('hello', name);
};

Factory

팩토리 패턴은 생성자 대신 제네릭한 인터페이스를 만들어야 하는 creational pattern이다.
이 패턴은 만들려는 프로세스가 복잡할 때 굉장히 유용하게 쓰인다.

function MyClass (options) {  
  this.options = options;
}

function create(options) {  
  // modify the options here if you want
  return new MyClass(options);
}

module.exports.create = create;  

펙토리는 테스팅 또한 쉽게 만든다. 가령 이 패턴을 이용해 모듈에 dependency를 넣을 수 있다.

Dependency Injection

Dependency injection은 의존객체에 하나 이상의 dependency를 주입하거나 참조로 전달하는 소프트웨어 디지인 패턴이다.     

예를들어 데이터베이스에 의존적인 UserModel을 생성해보자.

function userModel (options) { 
  var db;

  if (!options.db) {
    throw new Error('Options.db is required');
  }

  db = options.db;

  return {
    create: function (done) {
      db.query('INSERT ...', done);
    }
  }
}

module.exports = userModel;

이제 이걸 이용해서 인스턴스를 만들 수 있다.

var db = require('./db');

var userModel = require('User')({  
  db: db
});

왜 이게 유용한가? 이것은 테스팅을 엄청나게 쉽게 만들어준다 -당신이 유닛테스트를 만들 때, 이 모델에 가짜 db 인스턴스를 쉽게 넣어줄 수 있다.

Middleware / pipeline

Middleware는 강력하지만 아주 심플한 컨셉이다: 한 유닛이나 한 함수의 결과값은 다음을 위한 인풋이다. 만약 당신이 이미 ExpressKoa를 사용했다면 이 컨셉을 이미 사용해보았다.

Koa가 어떻게 그것을 하는지 확인해보자:

app.use = function(fn){  
  this.middleware.push(fn);
  return this;
};

기본적으로 이 코드는 middleware를 추가하면 단순히 middleware 배열에 추가한다. 지금까지는 잘 되고 있다. 그러나 서버에 요청을 하면 어떨까?

var i = middleware.length; 
while (i--) { 
  next = middleware[i].call(this, next);
}

마법이 아니다 - 당신의 middleware는 줄줄이 호출된다. 

Streams

stream은 특별한 pipeline으로 생각할 수 있다. 이것은 객체가 아닌 bytes이지만 많은 양의 데이터 흐름을 처리하는 데 좋다.

process.stdin.on('readable', function () {  
    var buf = process.stdin.read(3);
    console.dir(buf);
    process.stdin.read(0);
});

$ (echo abc; sleep 1; echo def; sleep 1; echo ghi) | node consume2.js 
<Buffer 61 62 63>  
<Buffer 0a 64 65>  
<Buffer 66 0a 67>  
<Buffer 68 69 0a>  

Example by substack

stream에 대해 더 공부하고 싶으면 substack의 Stream Handbook을 확인해보자.

Further reading



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,


먼저 이클립스에 cocos2d-x 환경을 세팅한다. 그리고 cocos2d-x 프로젝트를 하나 만든 다음, 그것을 안드로이드 스튜디오에서 열어볼 것이다. 

아래와 같이 13기가의 용량을 비워두고 환경 세팅을 시작했다. cocos2d-x프로젝트를 만들어 기기에 돌려보기까지 13기가를 꼬박 다 쓰고도 5기가를 추가로 다시 마련했다.

그리하여 환경세팅은 겨우겨우 끝냈지만 아직 빌드라던지 새 프로젝트를 만든다던지 하기 전이었다.

400메가가 남은 상태에서 도저히 진행이 불가능하여서 iMoive와 가비지벤드, iPhoto를 지우고 다시 진행했다. 

내가 새벽에 내내 작업을 해서 정신이 혼미했지만.. 안드로이드 스튜디오가 깔려 있는 상태에서 나머지 환경세팅을 하는데 약 20기가 정도가 들었다. 빌어먹을 NDK..
그리고 여분의 15기가를 준비하여 프로젝트를 생성, 빌드하는데 무리없도록 만들었고 가슴이 아픈 메모리 정리였다ㅠ

결론적으로 나는 총 35기가 정도의 용량으로 환경세팅을 완료했다. (확실하게 측정한 값은 아닙니다. 참고만 해주세요...)

아래는 간단하게나마 환경세팅을 하는 순서를 나열하겠다.
혹시 이미 설치된게 있다면 알아서 넘어가면 된다.

글쓴이가 기본 환경 경로로 잡은 곳은 /Users/사용자이름/Document 이다.
여기에 eclipse와 Cocos2d-x, ant, ndk를 다운 받았고, android-sdk는 eclipse폴더 안에 넣어두었다.


맥에서 Cocos2d-x 환경 세팅하기

1. 맥에 이클립스 설치 (http://toplogic.tistory.com/40)


2. 맥에 이클립스에 안드로이드(android-sdk) 설치 (http://toplogic.tistory.com/41)
$ sudo find / -name "*ndk*"
"*ndk*"의 뜻은 ndk를 포함한 모든것을 찾아달라는 뜻이다.


3. 대망의 NDK 다운 (https://developer.android.com/ndk/downloads/index.html#download)

- 글쓴이는 r10e 버전을 다운받았다.
- 다운 받고 나면 아래와 같이 압축을 풀어준다. (압축 푸는데 시간 좀 걸린다. 용량도 엄청 잡아먹는다.)
ndk$ chmod a+x android-ndk-r10c-darwin-x86_64.bin
ndk$ ./android-ndk-r10c-darwin-x86_64.bin


4. ANT를 다운 (http://ant.apache.org/bindownload.cgi)


5. ./bash_profile을 설정한다.
- $ vi /Users/사용자이름/.bash_profile 을 쳐서 ./bash_profile 파일에 들어간다.
- vi 에디터 사용법을 모른다면 대강 익히고 돌아오자..

- 위 스크린샷과 같이 경로를 지정해주면 되는데, 앞에서 다운 받았던 것들의 경로를 넣어주면 된다. (tip : 폴더를 드래그해서 터미널에 드롭하면 절대 경로를 알아낼 수 있다!)
- android-sdk, ndk, ant의 경로를 오타 없이 잘 넣자.


6. 이제 cocos2d-x를 다운받자! (http://www.cocos2d-x.org/download/version#Cocos2d-x)
- 글쓴이의 경우 v3.0을 다운받았다. (http://cdn.cocos2d-x.org/cocos2d-x-3.0.zip)


7. 다운받은 Cocos2d-x의 압축을 풀고, 터미널로 압축을 풀었던 폴더에 들어간다.


8. .bash_profile파일에 지정해놨던 경로들을 Cocos2d-x에 적용시킨다. 아래 명령어를 치면 된다.
$ ./setup.py
- 만약 뭐 경로가 하나라도 없다면 없는 경로가 있다고 알려줄 것이다.
- 경로가 잘 세팅 되있다면 아래 명령어를 치면 된다고 뜰 것이다. 아래 명령어를 치면 이제 경로 적용이 완료된 것이다.

$ source ~/.bash_profile


여기까지 잘 따라왔다면 mac에서 cocos2d-x 환경 세팅이 완료된 것이다.
아흑.. 너무 힘들었다.. 하지만 아직 프로젝트를 생성해보기 전까지 방심해서는 안된다. 젠장 첩첩산중이지만 슬슬 끝이 보인다. 


맥에서 Cocos2d-x 프로젝트 생성하기

1. 프로젝트를 생성하고 싶은 경로에 들어가서 아래 명령을 입력한다.

$ cocos new MyGameTest -p com.your_company.mygametest -l cpp -d ./mygametest
- 프로젝트 이름은 MyGameTest이고 페키지 이름은 com.your_company.mygametest이다 그리고 mygametest폴더를 만들어서 그 안에 프로젝트를 생성한다.


2. finder에서 프로젝트 폴더 안에 들어가보면 아래와 같이 나온다. 프로젝트 생성이 된거다.


아이폰 프로젝트를 Xcode로 열고 실행시켜보기

0. Xcode가 설치되있어야합니다.

1. 위 경로에서 proj.ios_mac 폴더에 들어가서 Xcode 프로젝트를 실행시킨다.

2. Xcode가 켜지면 빌드&런을 해서 시뮬레이터(혹은 단말기)에 앱이 올라가는지 보면 된다.
- 빌드하는데 시간이 생각보다 많이 걸린다.. ㅠ

이렇게 실행이 되면 성공이다.


안드로이드 프로젝트를 Android Studio에서 실행시켜보기

0. 안드로이드 스튜디오를 설치해놓아야한다.

1. 유튜브 링크에 들어가서 그대로 따라한다. 이미 프로젝트 생성까지는 했으므로 안드로이드 스튜디오에 프로젝트를 import하는 부분부터 하면 된다. (https://youtu.be/VLeGy1foMQA?t=7m51s)

2.  쭉 따라서 하면 된다. 아래와 같이 된다면 성공이다.





추가로) 이 포스팅에서는 안드로이드 빌드를 터미널에서 해보진 않았지만, 터미널에서 안드로이드 빌드를 해보는 과정에서 아래와 같은 에러가 뜨는 경우가 있다. 


$ cocos run -p android
...
...
building apk Android platform not specified, searching a default one...
Can't find right android-platform for project :"/Users/nownabe/projects/HelloCocos/proj.android".
The android-platform should be equal/larger than 10


이 문제는 이 링크(http://qiita.com/nownabe/items/496285423c74b47dcd42)를 통해서 해결할 수 있다. 비록 일본어로 되어있지만 크롬으로 켜서 번역기 돌리고 보면 나름 이해가 된다. 중간부분에 해결 방법을 설명해놓았다. 안드로이드의 버전 문제인듯 하다.







WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : http://www.raywenderlich.com/14865/introduction-to-pixel-art-for-games

 Glauber Kotaki 

픽셀아트는 요즘 게임쪽에서 아주 인기있는중인데, 그 이유는 아래 세가지 정도라고 생각한다.

  • 외관(Looks). 픽셀아트는 보기 훌륭하다! 한 스프라이트에 각 픽셀에대해 만드는 것에대핸 이야기를 말한다.
  • 향수를 일으킨다(Nostalgia). 픽셀아트는 플레이어들이 자라면서 즐겼던 닌텐도, Super 닌텐도, Genesis(나의 경우!)등의 엄청난 향수를 불러일으킨다.
  • 배우기가 편하다.(Ease of learning). 픽셀아트는 배우기에 가장 쉬운 타입의 디지털 게임중 하나로써, 특별하게 당신이 아티스트이기보다는 프로그래머 타입에 가깝다면 더욱이 쉽다.

이제 픽셀아트를 만져볼 생각이 있는가? 그럼 나와 같이 따라오면 내가 가장 간단하게 만들면서 게임에 바로 사용할 수 있는 쿨내 풍기는 캐릭을 어떻게 만드는지 보여주겠다!


그리고 추가로, 캐릭 만드는 법을 보여주고 난 다음에는, iPhone game에 적용시키는 법을 Ray가 알려줄 것이다!


이 강좌를 따라오려면 Adobe Photoshop이 필요하다. 만약 포토샵이 없으면, 여기 어도비에서 제공하는 무료 다운로드 링크(http://www.adobe.com/cfusion/tdrc/index.cfm?product=photoshop)를 이용하면 된다.


픽셀속으로 빠져들어 읽어내려가보자!

픽셀 아트가 뭔가?

시작하게전에 “픽셀아트”이 뭔지 부터 깔끔하게 잡고 가자. “픽셀아트”은 당신이 생각하는것 만큼 명확한 뜻이 있진 않다. 그래서 의견이나 스타일의 문제이나 이번 강좌에서 정의를 나름 지어질 수 있길 바란다 :]

가장 쉽게 “픽셀아트”을 정의하는 방법은 무엇이 “픽셀아트"이 아닌가를 생각하면 된다. 무엇이든 generates 픽셀이면 픽셀아트가 아니라 할 수 있다. 아래 예제가 있다.

GradientGradient

Gradients : 두 컬러를 고르고 공간의 간격 사이에 두 색을 계산하여 넣는다. 보기에 굉장히 좋아보이지만 pixel art라고 할 수 없다!

BlurBlur

Blur tools : 픽셀들을 확인하여 이전의 이미지의 새로운 이미지를 만들기위해 되풀이/수정한다. 이것 또한 pixel art라 할 수 없다.

Anti-aliasAnti-alias

Anti-alias tool (기본적으로 서로 다른 색갈의 픽셀들을 보기 ‘부드럽게’하려고 새로운 픽셀을 생성해내는 것). 
당신은 이제 저것들을 피해야한다. 

몇몇은 자동적으로 색을 뽑아내주는 것 또한 pixel art가 아니라고 말하기도하며, 레이어 blending effects(알고리즘을 이용하여 두 레이어의 픽셀을 합치는 것) 또한 아니라는 것을 의미한다. 그러나 요즘의 하드웨어부터는 100만 컬러를 사용할 수 있으므로, 이것은 무시할 수 있다 - 아직, pixel art에서는 작은 컬러 사용이 좋은 연습이 될 수 있다.

line tool이나 paint bucket tool과 같은 다른 툴들 또한 자동으로 픽셀을 계산하여 넣어준다. 하지만 당신은 픽셀 경계에 안티 에일리싱을 하지 않게 설정함으로써, 모든 픽셀을 채울 수 있게 해주며, 이것은 pixel art스럽다고 할 수 있다.

끝으로, pixel art는 스프라이트에 각 픽셀의 좋은 배치에대한 모든것을 말하며, 대부분 한땀한땀 제한된 색의 팔레트로 작업한다. 이제 한번 도전해보자! 


시작하며

첫 pixel art를 만들어보기 전에, 당신은 pixel art는 쉽게 크기변환을 할 수 없다는 사실을 알아야한다. 만약 스케일을 줄인다면 그것은 어떤 덩어리로 보일 것이다. 아니면 스케일을 크게한다면 두배정도 올리기 전까지 보기 괜찮을 것이다.

이 문제를 피하기 위해서는 시작하기 전에 게임 캐릭터/적/게임 요소들의 크기를 얼만큼 할건지 정확하게 생각하여 잡아놓아야한다. 당신이 타겟으로 잡은 디바이스의 화면 사이즈를 기반으로 눈으로 보기에 얼마나 큰 “pixels”을 사용할지 정할 수 있다.

예를 들어 해상도가 480x320 픽셀의 iPhone 3GS에서 두배 사이즈의 게임이 되길 원한다고 해보자(“난 내 게임의 픽셀이 굉장히 북고풍으로 보이길 원한다!”). 그러면 당신의 작업 해상도는 그 절반의 사이즈(240x160 픽셀)로 해야한다.

포토샵에서 새 캔버스를 열고(File > New ...) 저 사이즈로 설정한 다음 한번 확인해봐라. 그리고 당신의 캐릭터 사이즈를 정하면 된다. 


32x32 pixel이다!

나는 32x32 픽셀을 그냥 막 고른 것이 아니라 이 해상도에 적합해 보여서 골랐다. 그러나 32x32픽셀은 2의 제곱수이고 이것은 게임엔진에서 다루기 편할 수 있다.(타일 사이즈들이 2의 제곱수이고, 텍스처가 2의 제곱수로 체워지기 때문의 이유 등등)

Tip : 비록 사용하는 게임엔진이 어떤 사이즈라도 적용이 가능할지라도, 짝수의 이미지 크기는 굉장히 좋은 습관이다. 이 방법으로, 이미지의 스케일이 커져야한다면, 이 짝수의 크기가 나눠졌을때 깔끔하게 보기 좋게 나타날 것이다.


너의 첫 캐릭을 만들어보자

pixel art는 최고의 모양새나 명료함 그리고 그래픽적으로 판별하기 쉬운 것으로써 잘 알려져있다. 당신은 캐릭터의 얼굴, 눈, 머리카락, 몸통 등의 부분을 고작 몇 픽셀로 표현할 수 있다. 그러나 개발자 사이즈는 더 복잡하다. 더 작은 캐릭은 모든것에 맞추기 어렵다.

더 많은 예제로 만들어서 캐릭에 어떻게 더 작게, 보기 쉽게 할지 정한다. 나는 항상 눈을 선택한다. 그 이유는 (마법적이게도) 캐릭에게 생명을 불어넣는 가장 좋은 방법중 하나기 때문이다.

포토샵에서, Pencil tool을 고른다. 만약 찾지 못했다면 Brush Tool을 꾹 눌러서 스크롤을 내리면 Pencil Tool이 나온다(아마 두번째에 있을 것이다). 그리고 brush size를 1로 설정해야한다(tool options bar에서 클릭하여 크기를 변경하거나 ‘[‘단축키를 누르면 된다)

당신은 또한 Erase tool 또한 필요하다. 클릭하여(혹은 단축키 ‘E’) “Mode:Pencil”로 바꿔 설정한다. (이게 브러쉬의 안티에일리싱을 막아준다)

그리고 픽셀링을 시작해보자! 아래와 같이 두 눈썹과 눈을 이미지에 그린다.


키읔! 난 픽셀링한다!! (역자주:미국 개그가 물씬 느껴지는 멘트내요)


당신은 벌써 lineart(당신이 자연스럽게 그리듯이 라인을 그리고 캐릭의 모양을 잡으면된다.)를 시작할 수 있다. 그러나 더 연습을 위한 방법으로 실루엣을 먼저 만들어보자. 좋은 점은 이 단계에서 완벽하게 할 필요 없이 표현할 것의 크기와 자세를 잡아보는 것이다.

회색으로 아래와 같이 해보자:

이 단계에서는 완벽하게 할 필요 없다는 것을 명심하자

빈 공간을 남겨두는 것 또한 주의하자. 당신은 빈 캔버스를 채울 필요가 없고, 나중에 다른 프레인에 더 많은 공간이 필요하게 될 것이다. 그리고 모두 같은 캔버스 크기를 유지하는게 굉장히 유용할 것이다(역자:무슨 말인지 이해하지 못했습니다..)

실루엣 작업이 끝나고, lineart작업을 할 차례이다. 이제 좀 더 조심스럽게 픽셀을 건드려야한다. 옷이나 방어구같은 디테일한건 아직 건드리지말고 작업하자. 만약 그것이 필요하다면 새 레이어를 만들어서 디테일한것을 작업하며, 실루엣이 작업된 레이어가 수정되지 않게 한다.

pencil tool로 작업하는데 엄청 느리게 느껴진다면 Line tool을 사용하는것도 괜찮은 방법이다 - 대신 Pencil 만큼 정확하진 않다는걸 기억하라. 그리고 아래 보이는 것과 같이 설정할 필요가 있다.

사각형을 누르고 있음으로써 스크롤을 내려 Line tool을 선택하자.

tool settings bar로 가서 세번째 아이콘 (“Fill pixels”)을 고르고, 굵기(Weight)를 1로 한뒤 “Anti-alias” 체크를 해제한다. 아래와 같이 보이면 된다.

Tip : 내가 아래쪽 발부분에 테두리선을 만들지 않았음을 주의하라. 발은 다리와 구별하는 필수적인 요소가 아니므로써 반드시 필요한게 아니고, 그럼으로써 당신은 한 줄의 픽셀을 절약할 수도 있다.


색과 그림자를 적용시키기

이제 색을 입힐 준비가 되었다. 일부로 아주 적당한 색을 고르려 하지는 않아도 된다. 색은 나중에 굉장히 쉽게 바꿀 수 있으니 말이다. 대신 모든 것이 고유의 색을 가지게 만들어라. 이제 Swatch 탭(Window > Watches)에서 기본 색들을 사용할 수 있다.

돌아와서 당신의 창조물을 아래와 같이 색을 입힌다. (그냥 자유롭고 크리에이티브하게 당신의 색을 입혀라!)

대조적인 좋은 색감은 당신의 작업물의 가독성을 더 좋게해준다!

나는 아직 옷이나 머리카락의 어떠한 아웃라인도 만들지 않았음을 알고 있어라. 당신이 할 수 있는한 많은 픽셀을 절약하는 것을 항상 기억해라!

아참, 각 칼라 픽셀을 조심스럽게 배치하는데 시간을 낭비하지 말라. 작업 속도를 높히기 위해서는 색에 대한 라인은 잡고 Paint bucket tool을 이용해서 공간을 채우는 방법이다. 당신은 이 툴 또한 설정해줘야한다. 툴바에서 이 툴을 선택하고(단축키 ‘G’를 눌러도 된다) Tolerance을 0으로 바꾸고 anti-alias 체크를 해제한다.

Tip : 만약 Magic Wand tool을 사용해본 적이 없다면, Paint bucket과 같은 설정을 적용해주어라 - tolerance나 anti-alias를 없핸다.


다음 작업에서는 기초적인 빛과 그림자에 대한 상식이 필요하다. 만약 자세히 모른다면 여기링크에 빠른 가이드가 있고, 여기 하나더 완성된 링크가 있다. 만약 지금 당장 배우고 싶지 않다면 그냥 이 단계를 스킵하고 “Spicing up your palette”(모든것이 끝난뒤 넣는 명암은 스타일을 정하고, 당신의 게임의 느낌과 장래를 정한다)로 가도 된다.

혹은 심플하게 내 예제와 비슷하게 명암을 넣어도 된다!


전체에서 같은 밝기를 사용한다.

당신이 할 수 있는/원하는 만큼 모양을 주려고 해보라. 그러면 보통 결과물이 보기에 더 풍성하게 보일 것이다. 예를들어 이제 당신이 코, 눈쌀, 머리볼륨, 깊이감, 접힌 바지들을 볼 수 있다. 또한 약간의 발광점을 추가하여 더 괜찮게 보이게 하면 된다.


명암에 같은 밝기를 유지하라


당신의 팔레트에 양념을 치자

많은 사람들은 기본 팔레트 색을 사용한다. 그러나 그것 때문에 많은 게임들이 서로 비슷해 보이기도 한다.

포토샵은 엄청 다양한 색을 가진 표준 팔레트를 가지고있다. 그러나 그것에 너무 의존해서는 안된다. 하단의 툴바에서 메인 팔레트를 클릭하여 당신의 고유 색을 만드는게 최고이다.

그런다음, Color Picker Window에서 오른쪽 바에 색을 고르면서 찾아보고 메인 영역에서 밝기(흰색<->검정색)를 선택하고 채도(선명한<->흐릿한)를 선택한다.

당신이 색을 고르고 나면 OK를 누르고 Paint Bucket tool을 다시 설정해라. 아, 혼란스러워하지말고 그냥  “Contiguous”박스의 체크를 해제하면된다. 이제 새로운 색으로 색칠할때, 모든 픽셀들이 같은색으로 칠해지게 될 것이다.

이것은 왜 색 갯수를 줄이는게 중요한지에대한 이유이고, 같은 요소(셔츠, 머리카락, 헬맷, 방어구 등등)를 작업할때 항상 같은 색을 사용해야한다. 그러나 각 다른 영역에는 다른 색을 사용해야함을 잊지말자, 그렇지 않으면 다시 색칠해야할 수도 있다!


같은 색의 픽셀들을 모두 칠하기 위해 “Contiguous”의 체크를 해제한다.

당신이 만족할 때 까지 색을 바꿔보자. 그리고 당신 캐릭터에 섹시한 색이 될 때까지 해보면 된다! 당신은 아웃라인 또한 다시 채색할 수 있다. 그냥 배경과 함께 적절히 알맞게 만들면 된다.

마지막으로 배경색을 테스트해보자. 캐릭터 레이어 밑에 새 레이어를 만들어서 다양한 색을 넣어보면 된다. 이 작업은 캐릭터가 보여질 밝거나 어둡거나 따뜻하거나 시원한 배경들에게 알맞은지 확인하는 것은 굉장히 중요하다.


포토샵에서 픽셀을 수정하는 팁

당신도 보았다시피 나는 내가 사용하는 모든 툴의 anti-alias을 껐다. Elliptical marquee 나 Lasso tool과 같은 다른 툴들은 이 기능을 끄지 않았음을 명심하자.

그것은 일부분의 사이즈를 바꾸거나 회전을 시킬때, 더 나아가 애니메이션을 돌릴때도 편하게 작업할 수 있다. 이렇게 하여, marquee tool (단축키 ‘M’)을 이용하여 영역을 지정하고, 오른쪽 마우스 눌러서 ‘Free Transform’을 선택한다(혹은 단축키 Ctrl+T). 그러면 리사이징이나 회전을 쉽게 할 수 있을 것이다.

그러나 포토샵은 Transform 기능을 사용하여 수정한 모든것에 자동적으로 anit-alias을 해버린다. 수정물을 확인하기 전에 Edit > Preferences > General (Ctrl+K)에 들어가서 “Image Interpolation”을 “Nearest Neighbour”으로 바꾸어라. 한마디로 이것은 새 포지션과 사이즈를 어림잡아 계산해주며 새 컬러나 투명도를 적용시키지 않고 당신이 선택한 색들이 알아서 변경되지 않게 막아준다.


iPhone Game에 Pixel Art 넣기

레이먼이 왔습니다. 나는 이 강좌에 쏙 들어와서 어떻게 Cocod2D 아이폰 게임 프레임워크로 Pixel art를 적용시킬 수 있는지 보여줄 것입니다.

만약 Cocos2D가 처음이거나 아이폰 개발이 처음이라면 다른 수많은 강좌보다 Cocos2D and iPhone tutorials 이 강좌를 먼저 보고 오길 추천합니다. 그리고 당신이 Xcode와 Cocos2D가 깔려있고 기본 지식이 있다면, 읽어주세요! :]

iOS\cocos2d v2.x\cocos2d iOS template, 이름은 PixelArt로하고 iPhone for device family를 선택하여 새 프로젝트를 만듭니다.

그리고 당신의 프로젝트에다가 당신이 최종적으로 만든 pixel art 캐릭터를 드래그해넣습니다.

다음 HelloWorldLayer.m을 열고 아래 init메소드로 대체합니다.

1
2
3
4
5
6
7
8
9
-(id) init { 
    if( (self=[super init])) { 
        CCSprite * hero = [CCSprite spriteWithFile:@"sprite_final.png"]; 
        hero.position = ccp(96, 96); 
        hero.flipX = YES; 
        [self addChild:hero]; 
    } 
    return self; 
}

우리는 스프라이트를 화면의 왼쪽 사이드에 놓고 그가 오른쪽을 보는것처럼 만들것입니다.

컴파일하고 돌려보면 당신이 예상한대로 스프라이트는 화면에 나타날것입니다. 

그러나 이 강좌에서 처음 말했던것을 기억하세요. 우리는 일부로 크기를 키워야합니다. 그리고 각 픽셀들이 추가 블록을 주어 픽셀아트 느낌이 나도록 보이게 하고 싶다.

따라서 init 메소드에 아래 라인을 추가하자:

1
hero.scale = 2.0;

쉽지요? 컴파일과 실행... 잉? 스프라이트가 블러처리가 되버렸다! 

이건 스케일이 바뀔 때 Cocos2D의 기본 세팅인 anti-aliases art 때문이다. 픽셀아트에서는 이걸 사용하지 말아야한다. - 우리는 딱 떨어지는 엣지가 필요하다.

운좋게도, 아래 라인 하나만 더 추가하면 굉장히 쉽게 해결할 수 있다.

1
[hero.texture setAliasTexParameters];

이 설정은 image의 스케일을 변화시켜도 anit-aliasing이 일어나지 않게 Cocos2D를 세팅하고, 이것은 “pixel-like”하게 보이게 만든다. 컴파일과 실행하여... ㅇ어로올1!! 된다!!

pixel art를 사용할 때 이점을 말하자면 - 우리는 실제 화면에 보이는 그림 사이즈보다 작은 이미지 파일을 사용하면 된다, 이건 텍스쳐 용량을 엄청나게 줄여준다. 그리고 우리는 레티나 디스플레이를 위한 이미지를 따로 준비할 필요가 없다!


이제 뭘 해야하나?

나는 당신이 이 튜토리얼을 즐기고 pixel art에대해 조금이나마 배워갔기를 바란다.

헤어지기전에 여기 마지막 팁들을 주겠다.


  • 항상 anti-alias, gradients나 너무 많은 컬러를 사용하는것을 피해야 한다는 것을 기억하자. 이것은 당신이 알지 못하고 하는것보다 알고 하는것이 당신 자신에게 굉장히 좋을 것이다.
  • 만약 당신이 북고풍을 구현하기를 REALLY 원한다면, 8-bit나 16-bit와 같은 완전 극한의 오래된 콘솔 작품을 한번 구경해 보아라.
  • 거기엔 ‘복고풍’을 떠나서 엄청나게 많은 스타일이 있다. 하드웨어가 좋아지면서, 리소스들은 더 좋고 더 다양한(섹시한) 색을 사용할 수 있게 되었다. 이것은 오래된 하드웨어에서 어떻게 픽셀아트가 만들어지는지 몰라도 되게 되었으며,  더 다양하고 새로운 아티스트들을 이끌었다. Newer consoles like Game Boy Advance, Nintendo DS, Playstation 1 and some mobile phones have games with these styles, so look into those!
  • 몇몇 스타일에서는 어두운 테두리가 없다; 어떤건 빛이나 그림자가 없다. 이것은 스타일에 따라 다르다! 이 강좌를 통해 알게된 작업물에 어떻게 명암을 주는지 아는것은 훌륭한 일이지만, 당신이 마음 가는대로 스타일링하는것을 마음속에 새겨두자.
  • 만약 좀 더 자신이 있다면, 다음 용어들로써 더 세세한 강좌를 찾아볼 수 있을 것이다: isometric, dithering, anti-alias, celout and subpixel animation.

Pixel art는 시작할 때 쉬워서 쉽게 느껴질 수 있지만 사실 엄청난 시간이 걸려야 좋은 pixel art 가 만들어진다.

스킬을 올리는 가장 좋은 방법은 연습, 연습 또 연습이다. - 그리고 pixel artists에게 조언을 받는것까지!

나는 당신이 pixel art 포럼에 당신의 작업물을 올려 다른 작가로부터 조언을 받을것을 강력히 추천한다. 작게 시작하고, 많이 연습하여 피드백을 받으면 당신은 엄청난 pixel art 게임을 탄생시킬 준비가 된 것이다!

만약 pixel art에 대해 다른 질문이 있다면, 아래 토의 포럼에 가입하고 당신들이 미래에 굉장한 pixel art를 만들 수 있는 모습을 보게되면 좋겠다! :]


translated by canapio


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
사실 5분은 오버고..
암튼 엄청 빠르게 서버 푸시 시스템을 구축할 수 있다는 뜻입니다. 아래 링크에 들어가서 따라하시면 됩니다.


위 링크에서 필요한 내용이나, 추가할만한 내용을 뽑아봤습니다. 



nodejs 서버사이드 iOS 푸시

iOS용부터 설명하겠다. 링크 여기 강좌에 들어가면 정말 쉽게 푸시 구현하는 방법을 설명해 놓았다. 3분안에 푸시를 쏘아볼 수 있다! 내가 위 링크의 포스팅보다는 잘 쓸 자신이 없으므로 나는 배포하는 부분을 좀 이야기하겠다. 테스트만 해볼 분들은 링크만 참고하면 되지만 배포할때는 아래 코드로 수정해야한다.

1) 푸시 개발용, 푸시 배포용 
// Developer 
var options = { 
    gateway : "gateway.sandbox.push.apple.com", 
    cert: './keys2/cert_production.pem',
    key: './keys2/key_production.pem',
    production: false
};

// AppStore 배포, Adhoc 배포
var options = { 
    gateway : "gateway.push.apple.com",//"gateway.sandbox.push.apple.com", 
    cert: './keys2/cert_production.pem',
    key: './keys2/key_production.pem',
    production: true
};

바뀐 부분은 두 라인인데, gateway부분과 production부분이다. gateway 값을 바꿔주고 productiontrue로 수정해야한다. 이렇게하면 AppStore배포나 Adhoc배포에서 푸시가 날아간다. 그러니 저렇게 바꿔서 테스트를 해보고 싶으면 Adhoc으로 배포하여 푸시테스트를 해보면 된다. (필자는 Developer, Adhoc, AppStore Distribute 상황에서 모두 테스트해보았고 그 결과를 말하는 것이다.) 

2) 푸시 여러개 한번에 보내기
위에서 소개한 강좌에 들어가면 푸시를 한번에 하나밖에 보내지 못한다. 아래 소스는 푸시 날리는 부분에 푸시아이디값을 Array로 만들어 한꺼번에 여러개 날릴 수 있게 해준다. 

var myDeviceArray = [ ]
for (var i=0; i<results.length; i++) {
     var token = results[i]._id;//'앞에서 Xcode로 build 하면서 획득한 아이폰 디바이스 토큰을 입력한다.'
     var myDevice = new apn.Device(token);
     myDeviceArray.push(myDevice);
}
try {
     apnConnection.pushNotification(note, myDeviceArray);
} catch (e) {
     console.log("apn exception : " + e);
}


apnConnection.pushNotification(note, myDeviceArray);
이 부분이 푸시를 실제 날리는 부분이고, 애플에게 푸시를 쏘아달라고 요청하게된다.

여기서 원래 myDeviceArray가 아닌 myDevice라는 객체를 넣었었는데, 필자는 여기에 myDevice의 배열을 넣었다. 이렇게하면 한번에 20개, 30개씩 푸시를 날릴 수 있다. 




nodejs 서버사이드 android 푸시

안드로이드는 iOS보다 휠씬 간단하게 구현이 가능하다. 안드로이드는 인증서 이런게 없기때문에 그냥 코드 구현을 하고 키값만 넣어주면 된다. 
그런데 자료를 찾다보니 앞서 소개한 iOS푸시구현 블로그와 동일한 저자가 안드로이드푸시 저자가 동일하고, 정리가 정말 잘되있다.. 링크를 따라가면  구현할 수 있을 것이다. 

여기서는 링크에서 서버쪽 코드만 빼내서 설명하겠다.

우리가 사용할 nodejs모듈은 https://github.com/ToothlessGear/node-gcm이다. node파일이 위치한 디렉토리에 들어가서 npm install node-gcm 명령을 치면 된다.

상단에 node-gcm를 불러오고

var gcm = require('node-gcm');


// or with object values
var message = new gcm.Message({
     collapseKey: 'demo',
     delayWhileIdle: true,
     timeToLive: 3,
     data: {
          lecture_id:"notice",
          title:"제목입니다",
          desc: "설명입니다",
          param1: '첫번째파람',
          param2: '두번째파람'
     }
});

var server_access_key = '/*안드로이드 개발자가 넘겨준 서버키*/';
var sender = new gcm.Sender(server_access_key);
var registrationIds = [ ];     // 여기에 pushid 문자열을 넣는다.

registrationIds = ['/*안드로이드 단말기에서 나온 푸시 아이디*/'];

/*
for (var i=0; i<push_ids.length; i++) {
     registrationIds.push(push_ids[i]);
}
*/

// 푸시를 날린다!
sender.send(message, registrationIds, 4, function (err, result) {
     // 여기서 푸시 성공 및 실패한 결과를 준다. 재귀로 다시 푸시를 날려볼 수도 있다.
     console.log(result); 
});
이상 서버사이드 아이폰, 안드로이드 푸시 알림이였습니다. 


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
이번 포스팅에서는 iOS에서 Objective-C로 푸시를 구현하는 방법에 대해 설명하겠다. 그렇지만 단순히 푸시를 받는것이 다가 아닌 푸시를 받았을 때 다음 이벤트 처리도 앱에서 어떻게 처리할지에대한 고민도 함께할 것이다. 이 부분은 애플에서 제공하는 방법이 생각보다 복잡하게 되있는것 같으므로 포스팅에 남기기로 하였다.

먼저 푸시를 받는 방법에 대해 설명하..  검색해보니 바로 원하는 링크가 안나와서 푸시 받는법부터 설명해보겠다.



1. 푸시 아이디를 얻어내는법

푸시 아이디를 얻고자 하는 시점에서 다음을 호출하면 된다. 
// 현재 푸시가 On인지 Off인지 알아내는 함수
BOOL pushEnable = NO;
if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
    pushEnable = [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
} else {
    UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
    pushEnable = types & UIRemoteNotificationTypeAlert;
}

// 푸시 아이디를 달라고 폰에다가 요청하는 함수
UIApplication *application = [UIApplication sharedApplication];
if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
    NSLog(@"upper ios8");
    // iOS 8 Notifications
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
    [application registerForRemoteNotifications];
} else {
    NSLog(@"down ios8");
    // iOS < 8 Notifications
    [application registerForRemoteNotificationTypes:
     (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
}


// AppDelegate.m 파일에서 아래 함수를 추가한다. 아래 함수는 푸시 아이디를 받아내는 함수이고, 푸시아이디는 아래 함수를 통해서만 받을 수 있다.
// AppDelegate.m
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
     NSString* newToken = [[[NSString stringWithFormat:@"%@",deviceToken]
                           stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
     NSLog(@"DeviceToken : %@", newToken );
}


위 코드를 보면 이것저것 복잡해보인다. 여기서 중요한 라인만 설명을 하자면

UIApplication *application = [UIApplication sharedApplication];
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[application registerForRemoteNotifications];
위 세 라인이다.
1. 앱 싱글톤 델리게이트를 받아와서 2. 푸시받을 타입을 정한다. 그리고 3. 실제로 푸시 아이디를 달라고 요청한다.
registerForRemoteNotifications가 호출되는 순간 appdelegate.m에 
첫째, 앱을 설치하고 처음으로 registerForRemoteNotifications를 호출하는 상황이면 사용자에게 푸시를 허용할지 말지에 대한 경고창이 뜬다. 여기서 허용을 하면 AppDelegate.m에 구현된application:didRegisterForRemoteNotificationsWithDeviceToken: 함수가 호출되면서 deviceToken파라미터에 푸시아이디가 담기게 된다. 반대로 사용자가 푸시 허용을 하지 않는다면 application:didRegisterForRemoteNotificationsWithDeviceToken: 함수는 호출되지 않는다. 그게 다다. 사용자가 푸시알림 허용 안했다고해서 뭐 어떻게 더 바로 할 수 있는게 없다. 푸시알림 무한 유도를 막기위함의 애플의 생각이 아닐까 싶은데, 이 부분때문에 고민을 좀 많이했다. 그래서 준비한게 BOOL pushEnable;이고 푸시가 현재 허용되있는지 아닌지 대강 판단해준다. 푸시가 꼭 필요한 어플일 경우는 pushEnable를 받아내서 확인할 수 있다. (하지만 이것도 사용자가 한번도 푸시 설정을 안했는지, 실제 푸시 거부를 한건지 알 방법은 없다. 단지 현재 푸시 설정 On/Off 여부만 알 수 있다.)
둘째, 앱을 설치하고 푸시 허용을 해놓은 상태이며(최초 registerForRemoteNotifications를 호출 해본 상태), registerForRemoteNotifications를 호출했다면 AppDelegate.m에 application:didRegisterForRemoteNotificationsWithDeviceToken:함수가 실행되면서 푸시아이디를 받아올 수 있다. 푸시 아이디를 서버에 등록할 때 사실상 푸시 아이디는 바뀔 수가 있다. 그렇기에 나같은 경우는 푸시아이디와 디바이스아이디를 함께 서버에 보내서 디바이스 아이디 기준으로 푸시 아이디를 저장한다. 아무튼 두번째의 경우는 무조건 푸시아이디를 받아올 수 있는 상황이다.

세번째, 앱을 설치하고 푸시 허용을 해제해놓은 상태이며(마찬가지로 최초 registerForRemoteNotifications를 호출 해본 상태), registerForRemoteNotifications를 호출했다면 아무일도 일어나지 않는다. 그렇기때문에 이 경우는 미리 pushEnable를 받아내서 푸시가 가능한지 아닌지를 판단해놓을 필요가 있다. pushEnableNO이면 푸시 허용 Off인 상태인거다. 그러면 푸시 설정을 해라고 경고를 띄우면 된다. 


2. 서버에서 푸시를 쏘았을때 폰에서 받은 위 호출되는 함수
서버에서 푸시를 쏘자마자 알아내는 방법은 하나밖에 없다. -> 앱이 실행되는 중일때이다.
앱이 만약 꺼저있거나, 백그라운드에서 돌고있으면 푸시가 왔는지 안왔는지 앱에서는 모른다. 앱이 다시 포그라운드로 왔을때 푸시가 왔으면 ‘푸시받는함수'가 호출된다. 하지만 웃긴것이, 앱이 백그라운드에도 없고 메모리에 올라가있지 않을때 푸시가 왔을때, 바탕화면에서 푸시를 받아서 푸시를 눌러 앱에 들어가면 ‘푸시받는함수’가 호출되지 않는다. 이 예외적인 부분을 처리하는 방법에 대해 설명하겠다.
 
// AppDelegate.m
// 앱이 런칭되서 메모리에 올라갈때 실행되는 함수이다.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    UILocalNotification *notificationUserInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if (notificationUserInfo) {
//        NSLog(@"app recieved notification from remote%@",notification);
        NSMutableDictionary *mNotificationUserInfo = [NSMutableDictionary dictionaryWithDictionary:(id)notificationUserInfo];
        mNotificationUserInfo[@"appLaunch"] = @"yes";
        [self application:application didReceiveRemoteNotification:mNotificationUserInfo];
    }else{
//        NSLog(@"app did not recieve notification");
    }
    return YES;
}

여기서
// AppDelegate.m
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

    [PushController shared].userInfo = userInfo;

    if (userInfo[@"appLaunch"]) {
        [PushController shared].waitingViewDidLoad = YES;
    } else {
        if (application.applicationState==UIApplicationStateActive) {
            [[PushController shared] pushInActiveStatus];
        } else {
            // if (application.applicationState==UIApplicationStateInactive) {
            [[PushController shared] presentPushPostTableViewController];
        }
    }
}
위의 PushController는 일단 신경쓰지 말고 푸시를 받았을 때 라이프 사이클만 신경쓰자. 
저기서 특이한 점은 
if (userInfo[@"appLaunch”])
를 이용하여 첫째 상황인지 먼저 구분하고
if (application.applicationState==UIApplicationStateActive) ;
를 이용하여 둘째상황과 셋째상황을 구분했다는 점이다.

첫째 상황에서는 아직 ViewController가 생성이 되기 전의 라이프사이클이다. 그렇기때문에 ViewControllerviewDidLoad를 호출하기 전까지 푸시정보를 잠시 가지고 있어야한다. 
둘째 상황에서는 application.applicationState==UIApplicationStateInactive에 해당하는 상태인데, 현재 앱이 백그라운드에서 포그라운드로 가고 있다는 뜻으로 사용자가 앱을 켜놓고 바탕화면에서 푸시를 눌렀을 때를 말한다. 이때는 바로 푸시 행위를 진행하면 된다.
셋째상황은 application.applicationState==UIApplicationStateActive의 상황으로써 앱이 이미 포그라운드에서 돌고 있었다는 뜻이고 이때는 자동으로 푸시 기능을 실행시켜버리는게 아니라 상단에 팝업창이 떠서 푸시가 온것처럼 만들어주면 사용자가 인지하기 쉬울 것이다. (안드로이드는 기본으로 제공되고, 아이폰은 카톡에서 볼 수 있는 기능이다) 상단에서 팝업이 잠시 내려왔을때 팝업을 누르면 푸시 기능을 수행하게 만들었는데, 이 기능은 PushController에 있다. PushController는 UI작업을 좀 했기 때문에 라인이 좀 되서 첨부해두겠다.

아무튼 위와같이 처리하면 푸시를 받는 일을 놓히지 않고 처리할 수 있다. 
 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

공지알림서비스 for PNU CSE


가격
무료

첫 버전 출시
2015년 6월

설명
부산대학교 정보컴퓨터공학부를 위해 만들어진 공지 알림 서비스입니다. 이 프로젝트는 부산대에 소속되지 않고 오로지 부산대 정컴인의 편의를 위해 개발된 무료 서비스입니다.

특징
- 약 15명 정도의 서로 다른 교수님 홈페이지가 지원됨
- 약 50여개의 서로 다른 URL을 모아서 보여줌
- 약 300개의 글을 모아 잘 정리되어 이 앱에서 보여줌
- 학사의 공지 또한 지원
- 푸시 알림 지원
- 안드로이드, 아이폰, 아이패드 지원

할말
본 서비스는 학과에서 만난 학생들과 함께 한 강의에서 만들어진 팀 프로젝트로써 2015년 1학기에 강의가 끝남과 동시에 서비스를 오픈하였습니다. 그리고 팀원들 모두 큰 흥미를 느끼고 프로젝트를 없애지 않고 이어서 개발하기로 결정하였고, 힘이 닫는대까지는 서비스가 운영, 유자, 보수 될 것입니다. 안드로이드 개발자 , 웹 개발자 팀원에게는 항상 감사한 마음입니다. 앞으로 꾸준히 서비스가 유지될 수 있도록 많은 유저들의 서비스 이용이 있으면 좋겠습니다.


스크린샷






다운로드 링크
Play Store : https://play.google.com/store/apps/details?id=com.pnucse.csenotice.Hugang
App Store : https://itunes.apple.com/app/id1000141391

소개페이지 : http://14.49.37.33:8003/DSTWebManager

피드백 : canapio.dst@gmail.com




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
path없이 바로 /opt/nosql/mongodb/bin에 있는 명령을 실행하기위해 준비
# export PATH=/opt/nosql/mongodb/bin:$PATH

mongod 명령은 MongoDB 시스템을 위한 기본 데몬 프로세스이다. 데이터 요청, 데이터 접근, 백그라운드에서 동작할 수 있게하는 등의 기능을 수행한다.

1) 테스트를 위한 mongod 돌리기
1. mongod 명령 수행
# mongod

2. 새로운 터미널을 열어서 mongo 명령 수행
# mongo
>

3. 종료
mongo 종료 : 
> exit
mongod 종료 :
# cmd+c (혹은 # ctrl+c)


2) 서비스를 돌리기 위한 mongod를 데몬으로 돌리기(백그라운드에서 돌리기)
1. mongod 명령을 데몬으로 수행. 데몬으로 수행하면 fork를 하여 백그라운드에서 돌고 있다.
# mongod --fork --logpath /var/log/mongodb.log

2. mongo 명령 수행
# mongo
>

3. 종료
mongo 종료는 위와 동일
mongod 종료 :
# mongod --shutdown




MongoDB 클라이언트에 들어온 모습



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
OS : centos 32bit
mongodb : 3.0.2


1. 아래 링크에 들어가서 해당 OS의 MongoDB 다운로드 URL 준비한다.

2. 리눅스에 curl 명령을 실행하여 MongoDB 압축파일을 다운받는다.
# curl -O [1번에서가져온URL]
예) # curl -O https://fastdl.mongodb.org/linux/mongodb-linux-i686-3.0.2.tgz

3. 압축을 푼다 (# ls를 해보면 해당 디렉토리에 들어있는 파일을 볼 수 있다)
# tar -zxvf 압축파일이름
예) # tar -zxvf mongodb-linux-i686-3.0.2.tgz

4. 압축을 푼 폴더를 /opt/nosql 으로 옮긴다.
# mkdir /opt/nosql
# mv mongodb-linux-i686-3.0.2 /opt/nosql/mongodb

5. db가 저장될 폴더를 만든다.
# mkdir -p /data/db

6. mongodb 디렉터리로 들어가서 mongod를 실행시킨다.
# cd /opt/nosql/mongodb

(7. mongodb 클라이언트를 실행시켜본다.)
# export PATH=/opt/nosql/mongodb/bin:$PATH  // monodb 클라이언트를 바로 실행할 수 있게 해줌
# mongdb
>


빠르게 훑어보는 nodejs, mongodb 연동 : http://bcho.tistory.com/889


참고 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
1. 아래 링크에 들어가서 해당 OS의 nodejs 다운로드 URL 준비한다.

2. nodejs 압축파일을 다운받고 압축을 푼다.
# wget http://nodejs.org/dist/v0.12.2/node-v0.12.2-linux-x86.tar.gz
# tar xfz node-v0.12.2-linux-x86.tar.gz

3. node 폴더를 옮긴다.
# mv ./node-v0.12.2-linux-x86 /usr/local/node

4. /etc/profile 파일에 들어가서 수정(:a)모드로 바꾸어 export를 두개 만들고 저장(:wq)한다. vi편집기 사용. (vi 편집기 사용법)

# vi /etc/profile

5. 방금 수정한 profile을 적용시킨다.
# source /etc/profile

6. node 명령을 실행해본다.

# node -v


연관 글

> [Node.js, MongoDB] Node.js 설치 및 실행

[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행

[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)


(번역) Express.js 4, Node.js and MongoDB REST API 강좌


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
1. 구글 드라이브 web host 폴더 만들기
1) 구글 드라이브 가입하기
2) 구글 드라이브 컴퓨터에 설치하기
3) 배포용 폴더 만들기 (본인의 경우 Google 드라이브>2015 학교>수업>안드로이드실험>프로젝트>ios_APP의 경로로 설정했다)
4) 배포용 폴더를 웹호스팅 공개로 설정


빨간색 네모박스에 있는 키값을 꼭 복사해놓는다. (내 폴더 키는 소중하니까 모자이크처리)
맨 마지막에 URL 만들때 필요하다!

다음으로 고급을 누른다.

변경을 누른다.

'웹에 공개’를 누르고 저장을 누르면 웹 호스팅 폴더가 완성됬다!

5) Finder에서 확인해보면 폴더 모양이 공유된거처럼 생겼다.



2. Adhoc용으로 ipa파일 만들기
1) AdHoc용 Provisioning Profiles을 만들고 더운받아서 실행한다.
-> 아마 Xcode가 실행될 것이다.

2) Product>Archive하기 전에 세팅을 한다.
-> Adhoc으로 Archive(필드와 비슷한 것)해야하기 때문에 세팅을 한다.
- Project>General에서 Team설정 : None에서 자신의 계정으로 설정
- Project>Build Setting에서 Code Signing>Code Signing identity를 iPhone Distribution:XXXXX로 설정
- Project>Build Setting에서 Code Signing>Provisioning Profile을 1번에서 만든 AdHoc Provisioning Profile로 설정

3) Product>Archive를 실행


주의사항 : 시뮬레이터가 선택되있으면 Archive가 비활성되있다. 위 스샷처럼 iOS Device를 선택해주자.

4) 빌드가 끝나면 새로운 창이 뜨면서 어떤식으로 배포할 것인지 정하는 프로세스가 나올것이다. export를 하여 Adhoc을 고르고 구글 드라이브와 연동되있는 폴더에 ipa파일을 생성하면 된다.

3. BetaBuilder를 이용하여 URL만들기

1) choose IPA…버튼을 눌러서 .ipa파일을 가져온다.

2) 하단에 Full Web Deloyment Path를 입력한다. 
-> 구글 드라이브의 경우 https://googledrive.com/host/XXXXXXXXXXXXX
XXXXXXXXXXX부분에는 1.4번 구글드라이브 폴더에서 가져온 ipa파일이 들어있는 해당 디렉토리의 키값이다.


3) Generate Depolyment Files… 버튼을 눌러서 .ipa파일이 들어있는 폴더에 Generate 시킨다.


4. 링크 배포하기
UDID를 등록한 핸드폰(혹은 패드)에서 위 링크를 타고 들어가면 html이 열릴 것이다.
'Tap Here to install’을 누르면 다운이 시작될 것이다.

아래 스샷은 맥에서 연 사진이지만, 단말기의 브라우저에서 열어야한다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원본 : http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1

위 링크의 강좌를 2포스트에 걸쳐서 번역을 진행할 것이다. 본 포스트는 위 원문 포스팅의 절반을 번역한 것이다.


Update note : Swift를 사용한 iOS8 기반의 Grand Central Dispatch tutorial 최신 튜토리얼이 있다!

비록 Grand Central Dispatch(혹은 줄여서 GCD)가 얼마동안 주변에 있어 왔다고 해서, 모두가 그것을 잘 아는건 아니다. 이것은 이렇게 이해될 수 있다; 동시에 하는것은 편법이고, C기반 API의 GCD는 Objective-C의 세계로 자연스럽게 뽀죡한 끝으로 푹 찌르는 것처럼 보인다. 깊이있게 두 파트의 시리즈를 가진 Grand Central Dispatch 강좌를 배워보자.

이 두 파트에서, 첫번째 강좌에서는 GCD가 뭘하는지와 GCD 기능 기반의 몇몇 예시를 보여줄것이다. 두번째 파트는 GCD가 가진 더 많은 기능적인 이점을 배울 것이다. 



GCD란?
GCD는 libdispatch를 위한 마케팅적인 이름이다, 애플 라이브러리는 iOS와 OSX의 멀티코어 하드웨어에서 코드 동시 실행을 위한 기능을 제공한다. 이것은 아래 이점들을 따른다:
  • GCD는 무거운 처리를 살짝 뒤로 미루고 백그라운드에서 처리할 수 있게 도와주므로써  당신 앱의 대응력을 높혀준다.
  • GCD는 쓰레드를 락 거는것 보다 더 쉬운 동시 실행 모델을 제공하고 동시 실행시 생기는 버그들을 피할 수 있게 도와준다.
  • GCD는 싱글톤으로써 일반적인 패턴의 높은 퍼포먼스와 함께 당신의 코드를 아름답게 만들어 줄 것이다.
이 강좌는 블록코딩과 GCD가 어떻게 동작하는지 잘 알고있다는 가정하에 진행한다. 만약 GCD를 처음 접한다면 Multithreading and Grand Central Dispatch on iOS for Beginners를 먼저 필수로 보고 오면 좋겠다.


GCD 용어
GCD를 이해하기위해, 여러분은 쓰레딩과 동시실행어대한 몇몇 개념을 명확하게 할 필요가 있다. 이것들은 둘다 모호하고 헷갈리는 것이지만 잠시 다시 GCD의 관점에서 그것들을 가볍게 생기시켜 봐야한다.
(역자 주 : 원문에서는 GCD 용어라고 해놓았지만, 구글에 "쓰레드 용어"로 검색하면 더 많은 정보를 얻을 수 있다)

Serial vs. Concurrent
이 용어는 작업들이 서로관에 연관이되어 실행되는 경우를 설명하는 용어이다. 연속적으로 실행되는 작업들은 항상 한번에 실행된다. 작업들은 한번에 동시에 실행되어야한다.

비록 이 용어가 넓은 응용프로그램이지만, 이 강의의 목적상 당신은 Objective-C block 작업 수행에만 초점을 맞출 수 있다. block이 뭔지 모른다고? 여기 이 강좌(How to Use Blocks in iOS 5 Tutorial)를 참고해라. 사실 당신은 또한 함수포인터와 함께 GCD를 사용할 수 있지만, 대부분의 케이스에서는 이것이 사용에 실질적이고 편법적이다. Block은 매우 편하다!



Synchronous vs. Asynchronous
GCD에서, 이 용어는 함수가 연관된 다른 작업을 끝났을때 함수는 실행하기위해 GCD에게 물어보는 것을 뜻한다. 동기화(synchronous) 함수는 명령된 일이 모두 끝난 후에 리턴한다.

반면 비동기화(asynchronous) 함수는 즉시 리턴하고, 일이 다 끝났다고 알려주지만 실제 일이 끝날때까지 기다리지는 않는다. 그러므로 비동기화 함수는 다음 함수에서 처리되는 실행의 현재 쓰레드 block이 아니다.

조심하자 - 현재 쓰레드의 동기화 함수 “blocks”을 읽을때나 함수가 “blocking”함수 이거나 blocking operation인경우 혼동하지 말자! blocks라는 동사는 어떻게 한 함수가 현재 쓰레드에 영향을 미치는지이고 명사 block과는 아무 연관이 없다. 명사 block는 Objective-C에서 이름없는 함수의 용어이고 GCD에 보내는 일들을 정의한다.

Critical Section
이것은 한번에 두 쓰레드가 도는 상황에서 반드시 동시에 실행되지 않는 코드 조각이다. 동시 프로세스에 의해 접근된다면, 이것은 코드가 (변수와 같은) 공유된 자원을 동시에 건드릴 수 있기 때문에 잘못된 값이 들어가거나 할 수 있다.

Race Condition
---

Deadlock
만약 그 두가지(혹은 그 이상)의 일이 서로의 처리가 끝나기를 기다리고 있을때 이것을 deadlock 되었다고 부른다(대부분의 쓰레드에서 나타날 수 있다). 첫번째 처리는 두번째 처리가 끝나기를 기다리기 때문에 끝날 수 없다. 그러나 두번째 처리 또한 첫번째 처리가 끝나기를 기다리기 때문에 두번째 처리도 끝날 수가 없을 것이다.

Thread Safe
쓰레드 세이프 코드는 여러 문제(데이터 오염, 크래쉬 등등)를 피하면서 멀티쓰레딩이나 동시처리로부터 안전하게 콜이 가능하다. 쓰레드 세이프가 되지 못한 코드는 한번에 한 콘텍스 안에서 동작한다. 쓰레드 세이프 코드 중 하나는 NSDictionary이다. 당신은 멀티 쓰레드 이슈 없이 저것을 이용할 수 있다. 반면 NSMutableDictionary는 쓰레드 세이프가 아니다. 이것은 오직 한번에 한 쓰레드안에서 접근할 수 있다.

Context Switch
context switch는 당신이 한 싱글 프로세스에서 다른 쓰레드를 실행하여 전환할 때의 저장 및 복구 실행 상태의 프로세스이다. 이 프로세스는 멀티쓰레딩하는 앱을 만들때 아주 일반적인 방법이지만, 이것은 추가적인 비용이 따른다.

Concurrency vs Parallelism

참고 한글 자료 : http://skyul.tistory.com/263
Concurrency와 parallelism은 종종 함께 거론된다. 그래서 짧게 그 두개를 비교하여 설명할 것이다.

concurrent 코드의 나눠진 부분은 “동시에” 실행될 수 있다. 그러나 그것이 어떻게 처리되는지는 시스템이 정하기 나름이다.

멀티코어 디바이스는 병렬적으로 같은 시간에 멀티 쓰레딩을 실행시키지만, 싱글코어 디바이스는 이것을 수행하기 위해 쓰레드를 실행시키고, 컨텍스 스위칭을 동작하며, 다른 쓰레드나 프로세스를 작동시킨다. 이것은 아래 그림처럼 마치 병렬적으로 수행되게 보이는데 충분히 빨리 수행된다.

비록 당신이 GCD에서 동시수행을 코드에 사용했었어도, 얼마나 병렬수행이 필요한지 GCD가 정하기 나름이다. 병력수행은 동시에 일어나는 것을 요구한다. 그러나 ‘동시’는 보장된 병렬을 제공하지 않는다.

중요한 포인트는 ‘동시’는 사실 구조에 관한 것이다. 당신이 GCD를 코드에 사용할 생각이 있을 때, 당신은 동시에 일어날 수 있는 일의 부분을 당신 코드 구조체에 노출한다. 게다가 한개는 반드시 동시에 일어나지 않는다. 만약 당신이 이 주제에대해 더 깊게 알고싶으면 this excellent talk by Rob Pike를 확인해보아라.

Queues

GCD는 코드의 블럭들을 다룰 수 있게
dispatch queues를 제공한다; 큐(queue)들은 당신이 GCD에 제공한 테스크를 관리하거나 FIFO 명령에 의한 테스크를 실행시킨다. 첫 테스크를 큐에 추가하는데 이점은 첫번째 테스크가 큐에서 시작되고 두번째로 추가된 테스크가 두번째에서 시작할것이며, 차례로 될 것이다. 

모든 dispatch queues는 당신이 멀티쓰레드에서 동시에 접근하려해도 스스로 쓰레드 세이프하다. 이러한 GCD의 이점은 어떻게해서 dispatch queues가 당신의 코드의 부분에서 쓰레드-세이프한 것을 제공하는지 이해할때 나타날 것이다. 이것의 핵심은 옳은 dispatch queue 종류와 옳은 dispatching function을 골라서 당신의 일의 queue에 보내기 위함이다.

이 섹션에서 특별한 GCD queue가 제공하는 두가지 종류의 dispatch queue를 보게 될 것이다, 그리고 GCD dispatching function과 함께
큐에 어떻게 추가하는지 알려주는 형상화한 예제를 돌려볼 것이다.

Serial Queues

serial queues에서 데스크는 한번에 한회만 실행된다, 각 테스크는 이전 실행되는 테스크가 끝나고나야지만이 시작된다. 뿐만아니라, 아래 그림과같이 우리는 한 블럭의 끝나는 점과 다음 블럭의 시작하는 점의 시간차이를 알지도 못한다.

이 테스크의 동작 타이밍은 GCD의 컨트롤 아래에 이루어진다; 당신이 알고 있는 GCD의 이점은 한번에 한번만 수행한다는것과 queue에 추가되어서 명령을 받으면 테스크를 실행한다는 것이다.

Seiral queue에서는 절때 동시에 두 테스크가 실행될 수 없으나, 동시실행에서 같은 섹션을 접근할 위험은 없다; 이 테스크를 오직 수행한다는 점에서 race condition으로부터 섹션이 위험해지는것을 막는다. 그러므로 위험한 섹션에 접근 할 수 있는 유일한 방법은 dispatch queue에 테스크를 담아서 보내는 방법이다, 그리고 위험한 섹션이 안전하다는 것을 검증받을 수 있다.



Concurrent Queues

concurrent queues에 테스크들은 추가하라는 명령에서 시작하는 보장을 가지고... 그것은 당신이 완전히 보장됬다는 것을 의미한다! 요소들은 어떤 명령들도 끝낼 수 있고 당신이 다음 블럭이 언제 시작될건지 모르거나 많은 블럭이 얼마동안 시간을 잡아먹으면서 동작하는지 몰라도된다. 이것이 GCD의 모든것이다.

아래 그래프는 GCD에서 4개의 동시수행을 동작하는 테스트 샘플이다.


Block 1, 2, 3 모두가 어떻게하면 빨리 연이어서 실행될지 주목하라, Block0이 시작된 후 블럭1이 시작되는 동안 일어날 것이다. 또한 Block3은 Block2 다음에 시작되지만 Block2보다 빨리 끝난다.

block이 언제 시작되는지의 결정은 완전히 GCD에서 한다. 만약 block의 실행 시간이 다른것과 겹칠때, 다른 코어에서 실행시킬지, 한개만 사용할것인지 혹은 코드를 다른 block에 콘텍스 스위칭을 할지 정하는건 GCD가 하기 나름이다.

단지 흥미로운 점은 GCD가 각자 다른 queue 타입으로부터 정해서 적어도 5가지의 각 queue들을 제공한다는 점이다.


Queue Types

처음으로, 이 시스템은 당신이 main queue라고 알고있는 특별한 종류의 큐를 제공한다. 다른 queue들과 같이, 이 큐에서도 한번에 테스크를 수행한다. 그러나 이것은 모든 테스크를 메인 스레드에서 수행할 것이라는 뜻이며, 당신의 UI를 유일하게 업데이트 할 수 있는 스레드이기도하다. 이 queue는 UIView에 메세지를 보내거나 노티피케이션을 보내는 것에 사용되어야하는 유일한 queue이다.

시스템은 또한 여러 동시 queues를 제공한다. 이것은 우리가 알고있는 Global Dispatch Queues라고 불리는 놈이다. 이것은 다른 우선순위를 가진 4개의 global queue가 있다: backgroundlowdefault, and high

마지막으로, 당신은 커스텀화된 종류나 동시 queues 또한 생성할 수 있다. 이 말은 당신이 적어도 5개의 queue들을 마음대로 생성 소멸시킬 수 있다는 뜻이다: main queue, 4개의 global dispatch queues, 추가로 당신이 커스텀화시켜 만든 queues 까지!

그리고 dispatch queue들의 큰 그림이 있다!

GCD의 예술적인 면은 queue에 당신의 일을 적당한 queue dispatching function을 골라 보내는 역할을 한다는 것이다. 이것을 경험하기 가장 좋은 방법은 권장해놓은 길을 따라서 예제를 실행해보는 것이다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,


종종 좋은 것들은 한곳에 머무르지 않고, 그래서 모카(Mocha)와 Superagent를 이용한 테스트와 Mongoskin과 Express.js를 사용하여 Node.js와 MongoDB를 이용한 JSON REST API 서버를 만드는 튜토리얼을 Express.js 4버전이 출시됨과 함께 만들게 되었다. 최신의 Express.js 4, Node.js, MongoDB를 다루는 튜토리얼을 만나보자.
새 튜토리얼을 위한 소스코드는 github.com/azat-co/rest-api-express (master branch)에서 확인할 수 있다. 이전의 Express 3.x버전을 위한 튜토리얼 코드는  아직 작동하고 express3 branch에 있다.



Express.js4와 MongoDB REST API Tutorial은 아래 파트로 나뉘어 구성되어있다.
1. Node.js와 MongoDB REST API 개요
2. 모카와 Superagent를 이용한 REST API 테스트
3. NPM-ing Node.js Server Dependencies
4. Express.js 4.x Middleware Caveat
5. Express.js와 MongoDB (Mongoskin) 구현
6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅
7. Express.js와 Node.js의 결론 및 확장성

만약 당신이 자장소와 그것이 무엇을 동작하는지로부터 코드의 동작에 관심이 있다면, 여기 REST API server가 어떻게 다운로드되고 동작하는지 간단한 설명이 여기있다.
$ git clone git@github.com:azat-co/rest-api-express.git
$ npm install
$ node express.js


$ mongod 와 함께 MongoDB를 시작한다. 그다음, 새 터미널창을 띄워서 모카 테스트를 실행한다. :

$ mocha express.test.js


아니면, 모카를 전역으로 설치하지 않은 경우. :

$ ./node_modules/mocha/bin/mocha express.test.js



1. Node.js와 MongoDB REST API 개요
Node.js, Express.js, MongoDB(Mongoskin) 튜토리얼은 모카와 SuperAgent를 사용해 테스트를 해나갈것이다. 이것은 Node.js의 JSON REST API 서버를 만들면서 테스트 주도 개발(Test-Driven Development)을 필요로 한다. 
서버 응용프로그램은 Express.js 4버전대 프레임워크와 MongoDB를 위한 Mongoskin 라이브러리를 필요로한다. 이 REST API 서버에서 우리는 CRUD(create, read, update and delete)기능을 실행하고 app.param( ), app.use( )와 같은 Express.js middleware 방식의 메소드를 실행할 것이다.

가장 처음에 할 것은, MongoDB를 설치하는 것이다. 이 링크를 따라가면 할 수 있을 것이다.
우리는 아래의 버전의 라이브러리를 사용하게 될 것이다.
  • express: ~4.1.1
  • body-parser: ~1.0.2
  • mongoskin: ~1.4.1
  • expect.js: ~0.3.1
  • mocha: ~1.18.2
  • superagent: ~0.17.0

만약 버전이 맞지 않다면 코드가 동작하지 않을 수도 있다 :-(



2. 모카와 Superagent를 이용한 REST API 테스트
시작하기 전에, 우리가 만들게될 REST API 서버에 HTTP 요청을 만드는 기능 테스트를 한번 적어보자. 만약 모카를 사용할 줄 알거나, 바로 Express.js 앱 구현을 해보고 싶은 사람들은 마음대로 해도 된다. 당신은 물론 터미널에서 CRUL테스트를 할 수도 있다.
우리는 이미 Node.js, npm, MongoDB를 설치했다고 가정하고, 새 폴더를 만들어보자.
$ mkdir rest-api
$ cd rest-api


우리는 모카, Expect.js, SuperAgent 라이브러리를 사용하게 될 것이다. 그것들을 설치하고, 프로젝트폴더에 들어가서 이 명령을 실행해라.

$ npm install mocha@1.18.2 --save-dev
$ npm install expect.js@0.3.1 --save-dev 
$ npm install superagent@0.17.0 --save-dev


Note: 당신은 물론 (명령에 -g를 넣고) 모카를 전역으로 설치할 수도 있다. 
이제 아까 그 폴더에 express.test.js파일을 만들자. 아래 6가지를 진행할 것이다.
  • 새 객체를 생성한다.
  • 객체의 ID를 가져온다.
  • 객체의 모든 정보를 가져온다.
  • ID를 이용해 객체를 업데이트한다.
  • ID를 이용해 객체를 제거한다.
HTTP 요청은 Super Agent’s와 연관된 (단지 테스트케이스를 넣기만 하면되는) 함수들을 사용하면 굉장히 식은죽먹기이다. 
이 강좌는 Express.js 4, MongoDB, Mocha를 사용하여 REST API를 만드는것이 목적이기 때문에 테스트케이스(test suits)에 대해 깊게 들어가지는 않겠다. 코드를 복붙하시오!

아래 코드는 express.test.js파일이다.
var superagent = require('superagent')
var expect = require('expect.js')

describe('express rest api server', function(){
  var id

  it('post object', function(done){
    superagent.post('http://localhost:3000/collections/test')
      .send({ name: 'John'
        , email: 'john@rpjs.co'
      })
      .end(function(e,res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.eql(1)
        expect(res.body[0]._id.length).to.eql(24)
        id = res.body[0]._id
        done()
      })    
  })

  it('retrieves an object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        done()
      })
  })

  it('retrieves a collection', function(done){
    superagent.get('http://localhost:3000/collections/test')
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(res.body.length).to.be.above(0)
        expect(res.body.map(function (item){return item._id})).to.contain(id)        
        done()
      })
  })

  it('updates an object', function(done){
    superagent.put('http://localhost:3000/collections/test/'+id)
      .send({name: 'Peter'
        , email: 'peter@yahoo.com'})
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')        
        done()
      })
  })
  it('checks an updated object', function(done){
    superagent.get('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body._id.length).to.eql(24)        
        expect(res.body._id).to.eql(id)        
        expect(res.body.name).to.eql('Peter')        
        done()
      })
  })    
  
  it('removes an object', function(done){
    superagent.del('http://localhost:3000/collections/test/'+id)
      .end(function(e, res){
        // console.log(res.body)
        expect(e).to.eql(null)
        expect(typeof res.body).to.eql('object')
        expect(res.body.msg).to.eql('success')    
        done()
      })
  })      
})

테스트를 하기위해, $ mocha express.test.js 커맨드를 날릴 것이다. (모카를 전역으로 설치하지 않은 경우 $ ./node_modules/mocha/bin/mocha espress.test.js)


3. NPM-ing Node.js Server Dependencies
이 튜토리얼에서 우리는 Mongoskin을 사용할 것이다. 또한, Mongoskin은 Mongoose나 shema-less보다 훨씬 가볍다. 자세한 내용은 Mongoskin comparison blurb를 확인해보기 바란다.
Express.js는 Node.js HTTP module 핵심 객체로 감싸져있다. Express.js 프레임워크는 Connect middleware의 상위층을 기반으로 만들어져있고, 어마어마하게 많은 편리함을 제공한다. 몇몇 사람들은 ...

만약 당신이 이전 섹션(Text Converage)에서 rest-api 폴더를 만들었다면, 어플리케이션 모듈을 설치하기 위해 아래 명령만 입력하면 된다. :
$ npm install express@4.1.1 --save
$ npm install mongoskin@1.4.1 --save


4. Express.js 4.x Middleware Caveat

슬프게도 NPM express만 하는 것으로는 Express.js와 함께 최소한의 REST API 서버를 구축하는게 불가능하다. 왜냐하면 4.버전대의 middlewares은 번들이 아니기 때문이다!(the middlewares are not bundled) 개발자들은 Express.js 4.x.왼쪽에 있는 express.static를 제외한 분리된 모듈들을 설치해야한다. 그리고 들어오는 정보를 파싱하기위해 body-parser를 추가해야한다:
$ npm install body-parser@1.0.2 --save


5. Express.js와 MongoDB (Mongoskin) 구현

제일 처음에 우리의 express.js 안에 우리의 dependencies를 정의해야한다. :
var express = require('express'),
  mongoskin = require('mongoskin'),
  bodyParser = require('body-parser')


3.x대 이후 버전(물론 v4도 마찬가지), Express.js은 앱 인스턴스의 객체를 간소화해서 가져온다, 아래 라인은 서버객체를 우리에게 제공할 것이다.(번역자:그냥 import정도로 생각하면 될듯):

var app = express()


요청의 바디로부터 파람들을 추출하기위해, 우리는 아래와같은 모양의 bodyParser() middleware를 사용할 것이다.:

app.use(bodyParser())


Middleware(여기, 다른 포럼)는 Express.js에서 강력하고 편리한 패턴이고 구성요소를 연결하며 코드의 재사용을 증진시킨다.

HTTP 요청의 바디객체 파싱의 넘사벽으로부터 구해주는 bodyParser()메소드와 같이, Mongoskin은 딱 한줄의 코드로 MongoDB 데이터베이스에 접속하는게 가능하다.:
var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true})


Note: 만약 당신이 원격으로 데이터베이스에 접속하고 싶다면(MongoHQ와 같은 것들..), 당신의 username, password, host and port의 값들을 스트링으로 치환하라. 여기 URI 스트링의 포맷이 있다:
mongodb://[username:password@]host1[:port1][, host2[:port2], …[, hostN[:portN]]][/[database][?options]].

app.param() 메소드는 또다른 Express.js middleware이다. 이것은 기본적으로 “요청 핸들러의 URL페턴에 어떤 값이 있으니 매 시간마다 뭔갈 처리해라”는 것을 말하고 있다. 우리의 경우 요청 패턴이 collectionName 스트링에 콜론이 점두사로 있을 때, 우리는 특정 콜랙션을 선택한다. 그러면 다음 요청 핸들러에서 사용할 수 있는 요청 객체(widespreadreq)의 프로퍼티(콜랙션이나 다른것일 수도 있다.)로써 콜랙션을 저장한다. (번역자:뭔소린지 모르겠다)
app.param('collectionName', function(req, res, next, collectionName){
  req.collection = db.collection(collectionName)
  return next()
})


단지 유저지향적으로, 메시지와 함께 루트 라우트를 넣자.:

app.get('/', function(req, res) {
  res.send('please select a collection, e.g., /collections/messages')
})


이제 진짜 할 일을 시작한다. 다수의 요소들중에 리스트를 어떻게 가져오는지 있다 (첫번째 파라메터는 빈 오브젝트{}이고 임의의 라는 뜻이다). 이 결과는 _id에(두번째 파라메터) 의해 정렬된 10개 제한으로 낼 것이다. find()메소드는 커서를 반환하고 우리는 toArray()를 불러 JavaScript/Node.js용 배렬로 만든다. :

app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


URL 패턴 파라메너에서 :collectionName 스트링에대해 언급한적이 있나? 이것과 이전 app.param() middleware는  req.collection 객체를 우리에게 준다. 이 객체는 우리의 데이터베이스에서 특정 콜랙션을 가르키고 있다.

우리는 단지 MongoDB에서 전체적인 페이로드를 지나온 이후로 마지막 시점에 만들어진 이 객체는 조금 이해하기 쉽게 해준다. 이 메소드는 서버나 다른 것들의 데이터베스가 어떠한 데이터 스트럭쳐도 받아드릴수 있기 때문에 종종 free JSON REST API라 불린다. Parse.com과 다른 백엔드 서버 제공자는 free JSON 접근을 만들어낸다. 우리 Express.js 앱에서는 이것을 위해 req.body를 사용할 것이다.
app.get('/collections/:collectionName', function(req, res, next) {
  req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){
    if (e) return next(e)
    res.send(results)
  })
})


함수들을 구하는 findByIdfindOne과같이 생긴 단일 객체는 find()보다 빠르다. 그러나 그것들은 조금 다른 인터페이스를 사용한다 (그것들은 커서 대신에 진짜 오브젝트를 반환한다). 그러므로 그것을 기억하고 있어라. 추가적으로, 우리는 Express.js 마법에 의해 req.params.id 경로의 :id 부분으로부터 ID를 가져올 것이다.

app.get('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.findById(req.params.id, function(e, result){
    if (e) return next(e)
    res.send(result)
  })
})


PUT 요청 핸들러는 update()가 증가된 객체를 반환하기때문에  더 흥미로운 것을 가져온다.
또한 {$set:req.body}는 값을 저장하는 기능을 가진 특별한 MongoDB 기능이 (보통 달러표시로 시작한다).

두번째 {safe:true, multi:false} 파라메터는 MongoDB에 callback 함수가 실행되기 전까지 동작을 멈추고 오직 한가지(첫번째) 아이탬만 처리하라고 알리는 옵션을 가진 객체이다.
app.put('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


마지막으로 DELETE HTTP 함수는 app.del()에 의해 실행된다. 요청 핸들러에서, 우리는 그것이 그 동작을 하는 것처럼 보이는 removeById()를 사용한다. 그리고 커스텀 JSON success 메시지를 제거과정에서 내보낼것이다.:

app.del('/collections/:collectionName/:id', function(req, res, next) {
  req.collection.remove({_id: req.collection.id(req.params.id)}, function(e, result){
    if (e) return next(e)
    res.send((result===1)?{msg:'success'}:{msg:'error'})
  })
})


Note: delete는 JavaScript의 연산자이고 대신에 Express.js는 app.del을 사용한다.

아래의 경우 서버 3000포트를 시작하는 마지막라인이다.
app.listen(3000)


단지 이 경우 뭔가 잘 실행되지 않을 수 있다. 여기 express.js 파일의 풀 소스가 있다.

var express = require('express') , mongoskin = require('mongoskin') , bodyParser = require('body-parser') var app = express() app.use(bodyParser()) var db = mongoskin.db('mongodb://@localhost:27017/test', {safe:true}) app.param('collectionName', function(req, res, next, collectionName){ req.collection = db.collection(collectionName) return next() }) app.get('/', function(req, res, next) { res.send('please select a collection, e.g., /collections/messages') }) app.get('/collections/:collectionName', function(req, res, next) { req.collection.find({} ,{limit:10, sort: [['_id',-1]]}).toArray(function(e, results){ if (e) return next(e) res.send(results) }) }) app.post('/collections/:collectionName', function(req, res, next) { req.collection.insert(req.body, {}, function(e, results){ if (e) return next(e) res.send(results) }) }) app.get('/collections/:collectionName/:id', function(req, res, next) { req.collection.findById(req.params.id, function(e, result){ if (e) return next(e) res.send(result) }) }) app.put('/collections/:collectionName/:id', function(req, res, next) { req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.del('/collections/:collectionName/:id', function(req, res, next) { req.collection.removeById(req.params.id, function(e, result){ if (e) return next(e) res.send((result===1)?{msg:'success'}:{msg:'error'}) }) }) app.listen(3000)

코드를 저장하고 당신의 에디터를 닫아라, 우리의 소박한 Express.js REST API 서버가 완성되었다.


6. Express.js 4 랩을 실행하고 모카를 이용한 MongoDB 테스팅

이제 MongoDB가 설치되고 실행됬다는 가정($ mongod)하에 터미널에서 실행시켜볼 수 있다(modgod와 다른 창을 띄워라).
$ node express.js


그리고 다른 창에서 아래 명령을 쳐라(처음 창은 닫으면 안된다):

$ mocha express.test.js

혹은 모카를 전역으로 설치 하지 않은 경우.:

$ ./node_modules/mocha/bin/mocha express.test.js


만약 모카나 BDD 사용을 원치 않다면, CURL는 언제나 당신을 위해 있다. :-)

예를들어 POST 요청을 만들기 위해 CURL데이터이다. :
$ curl -X POST -d "name=azat" http://localhost:3000/collections/test13


그리고 결과는 아래처럼 나오게 될 것이다.:

{"name":"azat","_id":"535e180dad6d2d2e797830a5"}]


우리는 REST API 서버를 사용하기때문에 쉽게 이 객체를 확인할 수 있다.:

$ curl http://localhost:3000/collections/test13
Using CURL with Express 4 and MongoDB REST API


GET요청 또한 브라우저에서 동작할 수 있다. 예를들어, http://localhost:3000/collections/test13 링크를 당신의 로컬 서버가 포트 3000에서 돌아가고 있을때 열 수 있다.
혹은 서버의 결과를 신뢰하지 못한다면, MongoDB($ mongo)를 이용하여 데이터베이스를 확인할 수도 있다.:
> db.test13.find()


Note: 만약 데이터베이스의 이름을 test라는 이름 대신에 다른 이름으로 바꾸고 싶으면, 명령 앞에 > use your_database_name 을 써라.

이 튜토리얼에서, 우리 테스트들은 실제 동작하는 어플리케이션 코드보다 길다. 몇몇에게는 테스트-기반-개발(Test-Driven Development)을 포기하고싶게 만들지도 모르겠지만, 당신이 어떠한 크고 복잡한 응용프로그램을 개발할 때에도 좋은 TDD 습관이 당신의 개발 시간을 줄여줄것이라 믿는다. 



7. Express.js와 Node.js의 결론 및 확장성
Express.js 4와 MongoDB/Mongoskin 라이브러리는 간단하게 몇 줄 만에 REST API 서버를 구축하기에 정말 좋다. 후에, 당신이 라이브러리를 확장하고자 한다면, 그것들은 또한 당신의 코드를 구성하는 방법을 제공할 것이다.
NoSQL 데이터베이스는 MongoDB와같이 스키마를 정의할 필요 없고 어떠한 다양한 형태의 데이터를 넘기거나 저장할 수 있는 좋은 free-REST APIs이다.
express.test.js, express.js and package.json의 풀소스코드는 github.com/azat-co/rest-api-exrpess에 있다.
Express.js나 다른 자바스크립트 라이브러리에대해 더 공부하고 싶다면, 아래 Azat의 책들을 보라.
  • Practical Node.js: Building Real-world Scalable Web Apps
  • Express.js Guide: The Comprehensive Book On Express.js
..
..
..

연관 글

[Node.js, MongoDB] Node.js 설치 및 실행

[Node.js, MongoDB] MongoDB 리눅스에 설치 및 실행

[Node.js, MongoDB] MongoDB 돌리기 (+백그라운드에서 돌리기)


> (번역) Express.js 4, Node.js and MongoDB REST API 강좌


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,


-(void) setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners cornerRadius:(CGFloat)cornerRadius{
    UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
                                                  byRoundingCorners:corners
                                                        cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
    
    CAShapeLayer* shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];
    view.layer.mask = shape;
}


view.frame = CGRectMake...하고나서
[self setMaskTo:view byRoundingCorners:...];
을 호출해주어야지 정상적으로 라운딩된 뷰를 볼 수 있다. 


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

1. 일단 뷰를 생성해 둔다.

// view 생성
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor colorWithWhite:0.f alpha:1.f];
view.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:view];

x, y : 100, 100
w, h : 100, 100
배경색 : 검정 


2. 테두리

// 테두리
view.layer.borderColor = [UIColor colorWithRed:1.f green:0.f blue:0.f alpha:1.f].CGColor;
view.layer.borderWidth = 1.f;

빨간색의 너비가 1인 테두리를 만든다


3. 둥근 모서리

// 둥근 모서리
view.layer.cornerRadius = 10.f;

 

아래와 같이 둥근 모서리를 이용한 원 형태의 뷰도 만들 수 있다.

UIImageView *iv = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"profile.jpg"]];
iv.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:iv];


iv.clipsToBounds = YES;
iv.layer.cornerRadius = iv.frame.size.width/2.f;



4. 스케일 변경 및 애니메이션

// 2배로 크게
view.transform = CGAffineTransformMakeScale(2.0, 2.0);

각 너비와 높이의 스케일 값을 넣는다
애니메이션 효과를 원한다면 아래와 같이 하면 된다.

// 애니메이션
[UIView animateWithDuration:3.f animations:^{
    view.transform = CGAffineTransformMakeScale(2.0, 2.0);
}];



5. 각도 변경 및 애니메이션

// 180도 회전
view.transform = CGAffineTransformMakeRotation(M_PI);

M_PI는 미리 정의된 3.141592... 파이값이다. 저 값만 바꿔주면 되며 
애니메이션 효과를 원한다면 아래와 같이 하면 된다.

// 애니메이션
[UIView animateWithDuration:3.f animations:^{
    view.transform = CGAffineTransformMakeRotation(M_PI);
}];





CALayer

The CALayer class manages image-based content and allows you to perform animations on that content. Layers are often used to provide the backing store for views but can also be used without a view to display content. A layer’s main job is to manage the visual content that you provide but the layer itself has visual attributes that can be set, such as a background color, border, and shadow. In addition to managing visual content, the layer also maintains information about the geometry of its content (such as its position, size, and transform) that is used to present that content onscreen. Modifying the properties of the layer is how you initiate animations on the layer’s content or geometry. A layer object encapsulates the duration and pacing of a layer and its animations by adopting the CAMediaTiming protocol, which defines the layer’s timing information.

CALayer 클래스는 컨텐츠를 기반으로한 이미지를 관리하거나 컨텐츠의 애니메이션 기능을 수행하는 역활을 한다. 레이어는 보통 뷰들의 역할을 보완하는데 사용되기도 하지만 뷰 없이도 콘텐츠를 나타내는데 사용될 수 있다. 레이어의 주된 기능은 당신이 제공한 콘텐츠를 보여주는 역할을 관리하는 것이다. 그러나 레이어 그 자신이 배경색이나 테두리, 그림자와 같은 시각적인 속성을 가지고 있다. 추가적으로 시각적으로 콘텐츠를 관리하기 위해, 레이어는 화면에 보여지고 있는 기하학적인 정보들(좌표, 크기, transform등등)을 가지고있다. 레이어의 속성을 변화시키는 것은 콘텐츠의 레이어나 기하학적으로 애니메이션을 시작할 수 잇는 방법이다. 레이어 오브젝트는 레이어의 지속시간이나 페이스를 담고있고 이것은 CAMediaTiming를 적용시킴으로써 레이어에 정의된 시간적인 정보에 따라 애니메이션 시킬 수 있다.




If the layer object was created by a view, the view typically assigns itself as the layer’s delegate automatically, and you should not change that relationship. For layers you create yourself, you can assign a delegate object and use that object to provide the contents of the layer dynamically and perform other tasks. A layer may also have a layout manager object (assigned to the layoutManager property) to manage the layout of subviews separately. 

만약 뷰에 의해 레이어 오브젝트가 생성되었다면, 뷰는 일반적으로 레이어 델리게이트에 자동으로 지정되있을 것이고 당신은 이 지정을 바꿀 수 없다. 당신이 직접 만든 레이어의 한해서, delegate 오브젝트에 어싸인 할 수 있고 그 델리게이트 오브젝트는 콘텐츠 레이어의 동적인 행동이나 다른 일들을 제공하는 것들을 사용할 수 있다. 레이어는 또한 차별적으로 subview들의 레이아웃을 관리하기 위한 (
layoutManager 속성에 어싸인된) 레이아웃 메니저 오브젝을 가진다.







WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

1. 블럭 코딩
나는 애니메이션 효과를 쓸 때는 블럭코딩을 주로 사용하는 편인데, 보기도 편하고 사용하기 좋다.
사용법은 아래와 같이 사용하면 된다.

[UIView animateWithDuration:3.f animations:^{
    // 애니메이션 ..
}];


2. CGRect 변경 (좌표 및 사이즈) 애니메이션

view.frame = CGRectMake(0, 0, 100, 100);

[UIView animateWithDuration:3.f animations:^{
    view.frame = CGRectMake(100, 100, 50, 50);
}];



3. 투명도 변경 애니메이션

view.frame = CGRectMake(100, 100, 100, 100);

[UIView animateWithDuration:3.f animations:^{
    view.alpha = 0;
}];







WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,



구글 애널리틱스가 뭔지부터 설명하겠다. 이놈은 구글에서 만든 "서비스 분석기”로 웹이나 앱에서 사용자가 서비스(웹이나 앱등을)를 어떻게 실행했는지 얼마나 실행했는지를 분석해서 통계를 내주고 도식화해주는 그런 놈이다. 그러니까 쉽게 예를 들어보면 '하루에 몇명이 접속했는지’, ‘실시간으로 몇명이 접속해있는지’ 이런 정보들을 제공한다. 또한 이런 방대한 기능에 비해서 앱에 적용해 사용하기가 쉽다! (아이고 감사합니다) 한번 구글 애널리틱스를 사용해본 개발자라면 다음부턴 항상 사용하게 될 것을 장담한다.

좀 더 자세한 설명을 보고싶다면 구글애널리틱스 자세히 알아보기 여기 들어가면 구글에서 자세히 설명해놓았다.

이번 포스팅에서는 iOS앱에서 간단히 적용시키는 것에 초점을 맞춰서 글을 써내려갈 생각이다.



구글 애널리틱스 사이트에 내 앱 등록하기

1. 구글 아이디 만든다 (웬만하면 있지요들? 근데 개발자용 하나 따로 만들어 놓으면 편합니다)

2. 구글 애널리틱스 홈페이지가서 가서 구글 애널리틱스 가입을 합니다. (구글 계정이 필요) 오른쪽 상단에 계정만들기 눌러서 진행하면 됨

3. 서비스 등록
- 처음 가입했다면 바로 아래와 같은 창이 뜰것이고

- 이미 가입한 상태라면 

상단에 “관리”라는 탭이 있다. 그걸 눌러서 

“새 속성 만들기”를 누르면 새로운 서비스를 등록할 수 있다.

4. 이것저것 작성하고나서 "추적 ID가져오기” 버튼을 누르면 추적 아이디가 생긴다.
예) DB-271282139-1 이런 모양으로 생겼다. 기억해두고 나중에 코드에 삽입해야한다.


구글 애널리틱스 SDK를 앱에 심기
1. 구글 애널리틱스 SDK를 다운받는다. 다운로드 링크
zip파일로 되있고 3.10버전(14년11월15일 기준)이라고 되있다.


2. 프로젝트에 추가해야할 .h파일들과 .a파일이다.
libGoogleAnalyticsServices.a
그 외 .h파일들을 집어 넣는다.


이렇게 GoogleAnalytics폴더 하나 만들어서 필요한 파일들 넣어주면 된다.

3. 이제 필요한 프레임 워크를 넣을거다

아이고 친절하다

  • CoreData.framework
  • libAdIdAccess.a
  • AdSupport.framework
  • SystemConfiguration.framework
  • libz.dylib
  • libsqlite3.dylib


환경 세팅은 끝났다.
코드 작성하러 가자

4. 코드 작성

AppDelegate.m에 임포트 시킨다.

#import "GAI.h"
#import "GAIDictionaryBuilder.h"
#import "GAIFields.h"

AppDelegate의 application:didFinishLaunchWithOptions:메서드에 다음 코드를 추가한다.
(그냥 초기화, 초기세팅 정도의 기능을 하지만 사실 없어도 동작하더라..)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Optional: automatically send uncaught exceptions to Google Analytics.
    [GAI sharedInstance].trackUncaughtExceptions = YES;
    // Optional: set Google Analytics dispatch interval to e.g. 20 seconds.
    [GAI sharedInstance].dispatchInterval = 5;
    // Optional: set Logger to VERBOSE for debug information.
    [[[GAI sharedInstance] logger] setLogLevel:kGAILogLevelVerbose];
    // Initialize tracker.

    return YES;
}


그리고 이제 실제로 서버에 Hit을 날려줄 코드를 작성한다.
나같은 경우 ViewController.m에 viewDidLoad에다가 넣었다.

#define GOOGLE_ANALYTICS_KEY @"DB-271282139-1"

아까 내가 기억해놓으라고 했던 추적ID를 이제 쓸 차례가 왔다.

- (void) viewDidLoad {
    [super viewDidLoad];

    id<GAITracker> tracker = [[GAI sharedInstance] trackerWithTrackingId:GOOGLE_ANALYTICS_KEY];
    [tracker set:kGAIScreenName value:@"테스트화면1"];
    [tracker send:[[GAIDictionaryBuilder createAppView] build]];
}


마무리
이렇게 하면 이제 앱이 켜지고 저 화면이 켜질때마다 알아서 구글 애널리틱스에 hit을 시킨다. 위치정보나 시간정도 이런거 주서다가 보내주는 모양이다. 나중에 구글 애널리틱스 홈페이지 들어가서 통계자료를 보면 되고,
실제 앱이 잘 연동되었는지 확인을 하려면 앱을 실행시켜놓고 구글애널리틱스 홈페이지 들어가서 전체 모바일 데이터 (All Mobile Data)를 눌러서 실시간 왼쪽탭의 메뉴를 눌러보면 1명이 활성화 된것을 확인 할 수있다. 오오 신기하다.

왜 구글 애널리틱스인가?
일단 수많은 이유가 있겠지만
첫째 공짜이고 
둘째 공짜이고 
셋째 공짜이고 
넷째 연동하기 너무 쉽고 
다섯째 통계를 다양한 관점에서 잘 분석해 놓았다.(앱이 강제 종료 되는것도 알아서 통계가 나옴)

그리고 최종적인 목적은 내 서비스를 다음에 어떤식으로 개발해야할지 업데이트 해야할지 방향이 서기 때문이다.




아무튼 글을 마치겠습니다. 오류 지적이나 여러가지 의견은 댓글로 달아주시면 감사하겠습니다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

준비할것

카카오톡 API 홈페이지 들어가서 준비를 한다.
- 나머지 프레임 워크도 추가한다.

#import <MessageUI/MessageUI.h>
#import <KakaoOpenSDK/KakaoOpenSDK.h>
#import <Social/Social.h>
#import <Accounts/Accounts.h>


MFMessageComposeViewControllerDelegate 델리게이트를 등록한다. 메시지 전송 팝업을 컨트롤 할때 사용한다.

@interface ViewController () <MFMessageComposeViewControllerDelegate> {

}
@end


아래 코드는 실제 동작하는 코드

- (void) shareWithIndex:(NSInteger)buttonIndex text:(NSString *)text image:(UIImage *)image imageURLString:(NSString *)imageurl/*카톡 이미지 공유에서 쓰임*/ url:(NSURL *)url { if (buttonIndex==0) { // 문자 메세지 [self shareMessageWithText:text image:image url:url]; } else if (buttonIndex==1) { // 카카오톡 if ([KOAppCall canOpenKakaoTalkAppLink]) { // 카카오톡 공유 [self kakaoWithText:text image:image imageURLString:imageurl url:url]; } else { // 카카오톡 설치 [self openInstallKakaoAlert]; } } else if (buttonIndex==2) { // 페이스북 [self shareWithServiceType:SLServiceTypeFacebook Text:text image:image url:url]; } else if (buttonIndex==3) { // 트위터 [self shareWithServiceType:SLServiceTypeTwitter Text:text image:image url:url]; } } #pragma mark - 메시지 - (void) shareMessageWithText:(NSString *)text image:(UIImage *)image url:(NSURL *)url { if(![MFMessageComposeViewController canSendText]) { UIAlertView *warningAlert = [[UIAlertView alloc] initWithTitle:@"메시지 보내기 기능을 지원하지 않습니다." message:@" " delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [warningAlert show]; return; } else { MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init]; if([MFMessageComposeViewController canSendText]) { controller.body = [NSString stringWithFormat:@"%@\n\n%@", text, url]; // controller.recipients = recipients; controller.messageComposeDelegate = self; NSData *data = UIImageJPEGRepresentation(image, 0); [controller addAttachmentData:data typeIdentifier:@"image/jpg" filename:@"thumbnail"]; [self presentViewController:controller animated:YES completion:nil]; } } } #pragma mark - 메시지 전송 delegate - (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { [controller dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - 카카오톡 - (void) kakaoWithText:(NSString *)text image:(UIImage *)image imageURLString:(NSString *)imageurl url:(NSURL *)url { // 카카오톡 KakaoTalkLinkAction *androidAppAction = [KakaoTalkLinkAction createAppAction:KakaoTalkLinkActionOSPlatformAndroid devicetype:KakaoTalkLinkActionDeviceTypePhone marketparam:nil execparam:@{@"kakaoFromData":[NSString stringWithFormat:@"{seq:\"%@\", type:\"%@\"}", self.dataInfo[@"contentsSeq"], self.dataInfo[@"contentsType"]]}]; KakaoTalkLinkAction *iphoneAppAction = [KakaoTalkLinkAction createAppAction:KakaoTalkLinkActionOSPlatformIOS devicetype:KakaoTalkLinkActionDeviceTypePhone marketparam:nil execparam:@{@"kakaoFromData":[NSString stringWithFormat:@"{seq:\"%@\", type:\"%@\"}", self.dataInfo[@"contentsSeq"], self.dataInfo[@"contentsType"]]}]; NSString *buttonTitle = @"앱으로 이동"; NSMutableArray *linkArray = [NSMutableArray array]; KakaoTalkLinkObject *button = [KakaoTalkLinkObject createAppButton:buttonTitle actions:@[androidAppAction, iphoneAppAction]]; [linkArray addObject:button]; /*[NSString stringWithFormat:@"%@ (%@)",[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"], LOC(@"msg_invite_kakao", @"경영전문대학원 MBA 모바일 주소록 앱")]*/ if (text) { KakaoTalkLinkObject *label; label = [KakaoTalkLinkObject createLabel:text]; [linkArray addObject:text]; } if (imageurl && image) { KakaoTalkLinkObject *kimage = [KakaoTalkLinkObject createImage:imageurl/*self.dataInfo[@"thumbnail1"]*/ width:image.size.width height:image.size.height]; [linkArray addObject:kimage]; } [KOAppCall openKakaoTalkAppLink:linkArray]; } - (void) openInstallKakaoAlert { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"카카오톡이 설치되어 있지 않습니다." message:@"카카오톡을 설치하겠습니까?"// @"Do you want to install the KakaoTalk?" delegate:self cancelButtonTitle:@"취소" otherButtonTitles:@"확인", nil]; alert.tag = 141; [alert show]; } #pragma mark - Alert View Delegate - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (alertView.tag==141) { if (buttonIndex==[alertView cancelButtonIndex]) { // cancel } else { // 카카오톡 링크로 이동 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://itunes.apple.com/kr/app/id362057947"]]; } } } #pragma mark - 페이스북 트위터 - (void) shareWithServiceType:(NSString *)serviceType Text:(NSString *)text image:(UIImage *)image url:(NSURL *)url { if ([SLComposeViewController isAvailableForServiceType:serviceType]) { SLComposeViewController *mySLComposerSheet = [SLComposeViewController composeViewControllerForServiceType:serviceType]; if (text) [mySLComposerSheet setInitialText:text]; if (image) [mySLComposerSheet addImage:image]; if (url) [mySLComposerSheet addURL:url]; [mySLComposerSheet setCompletionHandler:^(SLComposeViewControllerResult result) { switch (result) { case SLComposeViewControllerResultCancelled: NSLog(@"Post Canceled"); break; case SLComposeViewControllerResultDone: NSLog(@"Post Sucessful"); break; default: break; } }]; [self presentViewController:mySLComposerSheet animated:YES completion:nil]; } else { [[[UIAlertView alloc] initWithTitle:@"실패" message:@" " delegate:nil cancelButtonTitle:@"확인" otherButtonTitles:nil] show]; } }


페이스북, 트윗 등등 다른 기본 소셜 공유기능을 사용하려면

SOCIAL_EXTERN NSString *const SLServiceTypeTwitter NS_AVAILABLE(10_8, 6_0);
SOCIAL_EXTERN NSString *const SLServiceTypeFacebook NS_AVAILABLE(10_8, 6_0);
SOCIAL_EXTERN NSString *const SLServiceTypeSinaWeibo NS_AVAILABLE(10_8, 6_0);
SOCIAL_EXTERN NSString *const SLServiceTypeTencentWeibo NS_AVAILABLE(10_9, 7_0);
SOCIAL_EXTERN NSString *const SLServiceTypeLinkedIn NS_AVAILABLE(10_9, NA);

상수 스트링을 사용하면 된다. 이 포스팅에서는 SLServiceTypeFacebook, SLServiceTypeTwitter 만 사용했다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : http://www.dspdimension.com/admin/dft-a-pied/
Posted by Bernsee on September 21, 1999


Step 6 : DFT(Discrete Fourier Transform)

사인 변환에서 푸리에 변환까지 과정을 더 '일반화' 시킴으로써 간단하다. 사인 변환에 측정된 각 진동수를 위한 사인 파형을 사용하는 반면, 푸리에 변환에서는 사인 코사인 파형을 둘다 사용했다. That is, for any frequency we are looking at we ‘compare’ (or ‘resonate’) our measured signal with both a cosine and a sine wave of the same frequency. 만약 우리 신호가 사인 파형과 굉장히 닮았다면, 우리 변환의 사인 부분은 큰 진폭을 가질것이다. 만약 코사인 파형과 닮았다면, 코사인 부분이 커지게 될 것이다. 사인 파형과 정반대라면, 이것은 영에서 시작하여 1로 올라가는게 아니라 -1로 떨어지르것이다, 사인 부분은 음수의 값이 큰 진폭을 가질 것이다. 사인 코사인 위상은 받은 진동수에서 임의의 사인 형태로 보여질 수 있는것과 같이 +와 - 둘이 함께 보여질 수 있다.

//
// Listing 1.2: The direct realization of the Discrete Fourier Transform***:
//

#define M_PI 3.14159265358979323846

long bin, k;
double arg, sign = -1.; /* sign = -1 -> FFT, 1 -> iFFT */

for (bin = 0; bin <= transformLength/2; bin++) {
    cosPart[bin] = (sinPart[bin] = 0.);
    for (k = 0; k < transformLength; k++) {

        arg = 2.*(float)bin*M_PI*(float)k / (float)transformLength;
        sinPart[bin] += inputData[k] * sign * sin(arg);
        cosPart[bin] += inputData[k] * cos(arg);   

     }
}

우리는 여전히 푸리에 변환에 의해 뭔가 유용한 것을 얻어내야하는 과제를 남겨놓고 있다. I have claimed that the benefit of the Fourier transform over the Sine and Cosine transform is that we are working with sinusoids. 그러나 우리는 어떠한 정령 파도 보지 않았다, 오로지 싸인과 코싸인만 있다. 으음 이것은 추가적인 처리를 요구한다.

//
// Listing 1.3: Getting sinusoid frequency, magnitude and phase from 
// the Discrete Fourier Transform:
//

#define M_PI 3.14159265358979323846

long bin;
for (bin = 0; bin <= transformLength/2; bin++) {

    /* frequency */
    frequency[bin] = (float)bin * sampleRate / (float)transformLength;
    /* magnitude */
    magnitude[bin] = 20. * log10( 2. * sqrt( sinPart[bin]*sinPart[bin] + cosPart[bin]*cosPart[bin]) / (float)transformLength);
    
    /* phase */
    phase[bin] = 180.*atan2(sinPart[bin], cosPart[bin]) / M_PI - 90.;

}

DFT 아웃풋의 위 코드를 실행한 한 뒤에, 우리는 정형파의 합으로써 인풋 신호의 계산을 끝날 것이다. k-th 정형파는 frequency[k], magnitude[k] 그리고 phase[k]에 의해 설명된다. 단위는 Hz (Hertz, periods per seconds), dB (Decibel) 그리고 ° (Degree)이다. 이전에 계산한 1.3 Listing의 싱글 정형파의 사인 코사인 부분을 변환한뒤, 이제 항상 양수를 가지는 k-th 정형파의 DFT bin "magnitude" 진폭이라 부를것이고 이것을 기억하고 있어라. 우리는 진폭이 -1.0이든 1.0이든 위상이 +혹은 -180도 다른 magnitude가 1.0이라고 말 할 수 있다. 관련 문헌에서는, magnitude[ ] 배열을 측정된 신호의 Magnitude Spectrum라 부르고, phase[ ] 배열을 프리에 변환에 의해 계산된 측정된 신호의 Phase Spectrum이라 부른다.

데시벨에 bin magnitude를 측정하기 위해 참조함으로써, 우리의 인풋 파형은 DFS(digital full scale)의 0dB 크기를 갖는 [-1.0, 1.0] 범위의 값을 가질 것으로 예상 할 수 있다. DFT의 어플리케이션에 흥미를 보임으로써, 예를 들어 listing 1.3은 DFT를 기반으로한 스펙트럼 분석을 도출하는데 사용될 수 있다.





WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : http://www.dspdimension.com/admin/dft-a-pied/
Posted by Bernsee on September 21, 1999


Step 5 : Apple과 Oranges에 대해

만약 아직 당신이 따라오고 있다면, 푸리에 변환에 대한 여행이 거의 끝나간다. 우리는 얼마나 많은 사인 파형이 필요로 하는지에 대해 배웠고, 필요로 하는 수는 우리가 보고있는 샘플 수와 밀접한 관게가 있다는것, 낮은 주파수와 높은 진동수의 경계에 있는 것과 어쨋든 우리의 레시피를 완성시키기 위해서는 각 부분의 파형의 진폭을 결정할 필요가 있다는 것을 배웠다. 아직 완벽하게 해결한 것은 아니지만 샘플들을 어떻게 조리할지 결정할 수는 있을 것이다. 쉽게말하면, 우리가 측정한 샘플들과 우리가 알고 있는 진동수의 사인파형을 비교함으로써 사인파형의 진폭을 알아내고, 그것들이 어떻게 '같은지'에 대해 알아내보았다. 만약 그것들이 완벽히 같다면 그 사인파형은 반드시 같은 진폭이고 만약 참고하고있는 사인파형과 우리의 신호가 맞지 않다면 아닐 같은 진폭이 아닐 것이다. 그런데 어떻게 우리가 알고 있는 사인파형과 샘플 신호를 효과적으로 비교할 것인가? 다행히도 DSPer은 이 부분을 미리 만들어 놓았다. 사실 숫자를 곱하거나 더하는 아주 쉬운 부분이다. - 우리는 알고있는 진동수와 유닛진폭(이것은 계산기나 우리 컴퓨터의 sin( )함수로부터 나온 오직 한개의 진폭을 의미한다.)의 '참조하는' 사인파형을 계산하고, 우리의 신호 샘플들을 곱한다. 곱해진 결과값을 더하고나서, 우리가 다루고있는 진동수로부터 사인파형의 진폭을 얻어낸다.

이것을 C로 나타내면 아래와 같이 된다.

#define M_PI 3.14159265358979323846

long bin, k;
double arg;
for (bin = 0; bin < transformLength; bin++) {
    transformData[bin] = 0.;
    for (k = 0; k<transformLength; k++) {

        arg = (float)bin * M_PI * (float)k / (float)transformLength;
        transformData[bin] += inputData[k] * sin(arg);
    }
}

이 코드는 inputData[0...transformLength-1] 에 저장된 샘플 포인트를 transformData[0...transformLength-1]에 사인 파형의 진폭들의 배열 형태로 변환한다. According to common terminology, we call the frequency steps of our reference sine wave bins, which means that they can be thought of as being ‘containers’ in which we put the amplitude of any of the partial waves we evaluate. 일반적으로 쓰이는 용어를 빌리지면, 우리가 계산한 부분 파형의 어떤 진폭을 담은 'containers' 로써 생각될 수 있음을 의미하며, 참조하는 사인 파형 bin들의 진동수 단계라고 부른다
DST(Discrete Sine Transform)는 사인파형 부분들의 진폭을 구하기 위해 우리의 신호가 어떻게 생겼는지 모르거나 아니면 우리가 효과적인 방법을 사용할 수 있다고 가정하는 그러한 일반적인 흐름을 따른다. (예를들어 우리가 미리 우리의 신호가 알고있던 진동수의 한 ㅏ인 파형이라고 안다면, 우리는 즉시 사인파형의 큰 범위를 계산함 없이 진폭을 구할 수 있을 것이다. 푸리에이론에 기반한 이 효과적인 접근은 “Goertzel” 알고리즘을 찾을 수 있게 해주었다.)




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

키보드 올라오는 Delegate받기

- (void)viewDidAppear:(BOOL)animated {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(keyboardOnScreen:) name:UIKeyboardWillShowNotification object:nil];
    [center addObserver:self selector:@selector(keyboardHideScreen:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [center removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

#pragma mark - 키보드
-(void)keyboardOnScreen:(NSNotification *)notification {
    //...
}
-(void)keyboardHideScreen:(NSNotification *)notification {
    //...
}



- (void) viewDidAppear:(BOOL)animated ; 는 화면이 나타나기 바로직전에 호출되는 함수이고

- (void) viewDidDisappear:(BOOL)animated ; 는 화면이 없어지기 바로 직전에 호출되는 함수이다.

viewDidAppear에 "키보드 올라오면 말해주세요" 용도의 노티피케이션을 설정하고

viewDidDisappear에 "아까 등록한 노티피케이션을 지워주세요"라고 설정한다.




#pragma mark - 키보드
-(void)keyboardOnScreen:(NSNotification *)notification {
    NSDictionary *info  = notification.userInfo;
    NSValue      *value = info[UIKeyboardFrameEndUserInfoKey];
    
    CGRect rawFrame      = [value CGRectValue];
    CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; /*키보드 프레임*/

    NSDictionary *userInfo = notification.userInfo;
    NSNumber *durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey];  /*키보드가 올라오는 동안의 시간*/
    NSTimeInterval animationDuration = durationValue.doubleValue;
    NSNumber *curveValue = userInfo[UIKeyboardAnimationCurveUserInfoKey];       /*키보드 애니메이션 옵션 효과 (ease)*/
    UIViewAnimationCurve animationCurve = curveValue.intValue;
    
    [UIView animateWithDuration:animationDuration delay:0.f options:animationCurve<<16 animations:^{
        // ...
    } completion:nil];
}

-(void)keyboardHideScreen:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    NSNumber *durationValue = userInfo[UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration = durationValue.doubleValue;
    NSNumber *curveValue = userInfo[UIKeyboardAnimationCurveUserInfoKey];
    UIViewAnimationCurve animationCurve = curveValue.intValue;


    
    [UIView animateWithDuration:animationDuration delay:0.f options:animationCurve<<16 animations:^{
        // ...
    } completion:nil];
}


// ... 부분에 애니메이션 코드를 넣어주면 키보드에서와 동일한 애니메이션 ease가 연출된다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

원문 : http://www.dspdimension.com/admin/dft-a-pied/
Posted by Bernsee on September 21, 1999


Step 4 : 요리법(cooking recipes)에 대해

이전 단락에서는 컴퓨터의 어떤 신호가 혼합된 사인파형으로부터 만들어 질 수 있다는 것을 확인했다. 우리는 진동수에 대해 생각해보았고 어떤 최대, 최소 진동수의 사인파형이 우리가 분석하는 신호를 완벽하게 복원하기 위해 필요로하는지도 생각해보았다. 우리가 봐온 샘플의 수가 가장 낮은 사인 파형이 필요로하는 가장 최하 부분을 결정하는데 얼마나 중요한지 봐왔지만, 우리는 아직 실제 사인파형이 최대 몇개의 결과물을 만들어내는지 논의되지 않았다. 사인 파형에의해 만들어진 신호를 구성하기 위해, 우리는 그 중 한 부분을 확인해볼 필요가 있다. 사실은, 주파수는 우리가 알아야할 유일한 것이 아니다. 우리는 또한 사인 파형들의 진폭을 알 필요가 있다. 즉. 인풋 신호를 다시 만들기위한 각 사인 파형의 갯수. 진폭은 사인파횽의 최대 높이이다. 그러니까 0으로부터 최댓값 사이의 거리를 맗나다. 높은 진폭은 음량이 클것이고, 우리도 그렇게 들을 것이다. 그러므로 신호에 저음이 많다면 높은 주파수의 사인파형 보다 낮은 사인파형들이 많이 합쳐져 있을 것으로 예상할 수 있다. 일반적으로 저음의 낮은 주파수의 사인파형은 높은 사인 파형들보다 큰 진폭을 가지고 있다. 우리의 분석에서, 우리는 레시피(recipe)를 완성하기위해 각 사인파형의 진폭을 정해야 할 것이다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

이미지를 보여주는 앱을 만들다가 UIImageView는 GIF를 지원하지 않는걸 깨달았다. 여러가지 오픈소스를 사용해봤지만 FLAnimatedImage 이게 제일 맘에 든다. 빠르고 빠르고 빠르고 빠르다. 메모리도 적게 먹는다. 심지어 가장 최근에 만들어진듯하다. 만세다.

Github : FLAnimatedImage


사용법

FLAnimatedImage, FLAnimatedImageView파일 두개 프로젝트에 추가하고나서 아래 코드처럼 사용하면 된다.

#import "FLAnimatedImage.h"
#import "FLAnimatedImageView.h"


    FLAnimatedImage * /*__block*/ animatedImage2 = nil;
    NSURL *url2 = [NSURL URLWithString:@"http://raphaelschaad.com/static/nyan.gif"];
    NSData *data2 = [NSData dataWithContentsOfURL:url2];
    animatedImage2 = [[FLAnimatedImage alloc] initWithAnimatedGIFData:data2];
    
    FLAnimatedImageView *gifImageView = [[FLAnimatedImageView alloc] init];
    [gifImageView performSelectorOnMainThread:@selector(setAnimatedImage:) withObject:animatedImage2 waitUntilDone:NO];






WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,