Slug sash zats cover

Swift로 iOS 메소드를 원하는대로 수정하기

우리가 쓰는 코드는 완벽하지 않습니다. 하지만 앱을 충돌 시키는 버그의 원인이 Apple의 코드의 코드에 있는 경우 일반적으로는 버그 리포트 정도밖에 할 수 있는 것이 없습니다. 하지만, Sash Zats는 이 강연에서 우리와 같은 Apple의 코드에 접근할 수 없는 개발자가 Swift에서 swizzling을 이용하여 private 프레임워크 중 버그에 대한 패치를 수정하는 방법을 시연해 줍니다.

우리가 사용하는 프레임워크의 대부분은 아직 Objective-C로 작성되어 있기 때문에 메서드를 교체(swizzle)하거나 원래의 구현에 수정을 추가할 수 있습니다. Sash는 원래 소스 코드에 접근할 수 없는 상황에서 버그를 찾거나 타인의 코드를 리버스 엔지니어링하고 자신의 코드를 추가하기 편리한 도구를 데모하고, Swift에서 함수 포인터를 이용하여 C 언어 함수를 패치하는 방법을 설명합니다.


서론 (0:00)

안녕하세요. 저는 Sash입니다. 저는 어떻게 proprietary code 베이스를 다루며, 버그나 충돌을 서드파티의 코드에서 어떻게 수정할지에 대해 논의하려 합니다.

간단히 제 소개를 하자면, 이 강연을 하게 되기 전 지난 주말에 머리를 깍고 면도도 했습니다. 지난 2년간 매우 머리를 기르고 있었습니다. 5년 전 러시아에서 이스라엘로 옮겼고 이어 5개월 전에 미국에 왔습니다. 이와같이 저는 변화를 좋아합니다. 그리고 새로운 것을 시도하는 것을 즐깁니다.

Swift 와 Swizzling (0:55)

Swift가 출시되기 약 1년 전, 저는 Apple의 새로운 언어를 가지고 놀 수 있다는 것에 설레이고있었습니다. Swift라는 새로운 툴과 함께 실험하고 싶었고 그 안에많은 훌륭한 기능과 새로운 기능이 포함되어 있습니다. 우선 Swift 배경에 주목 해 봅시다. Swift를 잘 보면 익숙한 것이라고 생각을 하게 됩니다. 왜냐하면 Swift는 플랫폼에 의존적이고, Cocoa와 Cocoa Touch를 다루고 있기 때문입니다. 즉, Swift를 사용하면서 어떻게 이들의 Objective-C 프레임워크를 다루는지를 배우지 않으면 안 됩니다.

프레임워크 중 어느 정도가 Apple의 Swift를 사용하고 있는지 확인하는, 간단한 Ruby 스크립트를 작성 보았습니다 :

#!/usr/bin/ruby
developers_dir = `xcode-select -p`.chomp
frameworks_dir = "#{developers_dir}/Platforms/iPhoneSimulator.platform"
frameworks_dir += "/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/*"
swift_frameworks_count = 0
Dir[frameworks_dir].each { |file|
    framework = "#{file}/#{File.basename(file, ".*")}"
    next unless File.exist?(framework)
    puts framework unless `otool -l #{framework} | grep -i swift`.empty?
}

시스템 프레임워크 폴더에 이 스크립트를 실행시켜 보면 충격적인 숫자를 보게 될 것입니다 …… “0” 완전 제로입니다. 따라서 Swift를 사용할 때 어떻게 Objective-C 프레임워크와 공존할지를 배울 필요가 있습니다. 왜 우리가 이 숫자가 중요할까요? 왜냐하면 우리의 사용자는 신경 쓰지 않기 때문입니다. 당신의 앱이 충돌했을 때, 사용자는 누구의 코드에 그 책임이 있다 따위는 전혀 상관하지 않습니다. 그것이 Apple의 잘못인지, 당신의 잘못인지 전혀 신경쓰지 않습니다. 사용자는 버그를 보고하는 방법을 모릅니다. 따라서 좋은 품질을 보장​​하기 위해서는 지금부터 말하는대로 proprietary code의 문제에 대한 해결 방법을 찾아야합니다.

Objective-C 메소드의 Swizzling (데모) (2:35)

이 포인트에 대해서 설명하기 위한 UIKitty라는 앱을 보겠습니다.. 이 앱은 Instagram과 비슷하지만 고양이의 사진을 한 장만 친구들과 공유하기 위한 것입니다.

이런 개발 뉴스를 더 만나보세요

참고 : ⌘+ 에서 문자 크기를 크게 할 수 Xcode 플러그인을 썼습니다.

이 앱 내에서 iOS 공유 시트 안의 “프린트”는 뷰가 올바른 사이즈가 아니기 때문에 정상적으로 표시되지 않습니다. 제 패치는 이 문제를 해결합니다. 그리고 이 방법은 다른 더 도움이 되는 버그 수정에도 응용할 수 있다고 생각합니다.

그럼 그 해결책과 그것을 어떻게 달성했는지에 대해 한 가지 말씀 드릴게요. 좀 무섭게 보일 수도 있지만 한줄 한줄보고 갑시다.

if let cls = NSClassFromString("UIPrinterSearchingView") {
    let block: @objc_block (AspectInfo) -> Void = { (aspectInfo) in
        if let view = aspectInfo.instance() as? UIView {
            view.frame.size.height = view.superview!.frame.height - 44
        }
    }
    let blockObject = unsafeBitCast(block, AnyObject.self)
    cls.aspect_hookSelector(
        Selector("layoutSubviews"),
        withOptions: .PositionAfter,
        usingBlock: blockObject,
        error: nil
    )
}

먼저 비주얼 디버거를 사용하고, 이 정렬되지 않은 뷰의 뒤에 실제로 어떤 클래스가 숨어 있는지를 이해 할 수 있습니다 : UIPrinterSearchingView 입니다. 첫번째 줄에서 이 클래스를 런타임에 얻을 수 있습니다. 그리고 그 뒤에 swizzling 이라는 기술을 사용하고 있습니다. Swizzling은 프레임워크 코드를 직접적으로 접근하지 않고 메소드를 당신의 커스텀 코드 실행시 대체 가능하게 합니다. 이번 경우는 layoutSubviews () 이 일어날 때마다 호출 해 원하는 코드 블록을 정의합니다.

프레임워크 자체는 아무것도 변하지 않습니다. UIKitlayoutSubviews () 을 레이아웃 과정에서 지금까지대로 호출합니다. 그렇지만 이 특정 경우에서는 layoutSubviews () 의 실행이 완료된 후, 우리의 메서드도 호출됩니다. Apple 상태바의 높이를 고려하지 않았기 때문에 이렇게 수정하는 것은 현명하지 못합니다. 그래서 44 포인트라는 값을 하드 코딩하기로 했습니다.

해봤더니 잘 동작하는 것을 확인할 수 있었습니다. 때로는 문제에 따라서 더 복잡한 수정을 할 필요가 있을지도 모릅니다. 또한 원래 구현조차도 호출하지 않고 모든 것을 대체하고 싶을 때도 있을 것입니다.

layoutSubviews ()을 swizzle하기 위해 Aspects라는 라이브러리를 사용했습니다. 이 라이브러리와 순수 swizzling의 차이는 이 라이브러리는 모든 클래스에 걸쳐 특정 메소드에 대해서 swizzling의 적용을 가능하게 해줍니다. 이것은 전체 프로젝트에서 Apple의 뷰를 포함한 모든 viewWillAppear () 메소드에 구문 분석 코드를 추가할 때 매우 유용합니다.

이를 위해서는 한 줄의 코드를 추가하여 언제 실행 원하는지를 정의하면 됩니다. 우리의 예에서는 viewWillAppear ()는 호출되는 그 직전에 호출되어 실행됩니다. 그때 대화명이나 흥미로운 정보를 기록 할 수 있을 것입니다.

Swizzling 노트 (8:34)

UIKitty 데모에서는 Swift가 갖춘 최적화에도 불구하고 swizzling 할 수 있었습니다. 이것은 미리 컴파일 된 프레임워크 내에서 움직이는 코드는 최적화되지 않다는 것을 의미합니다. 우리가 관련된 모든 프레임워크와 UIKit의 모든 것은 아직 Objective-C로 작성되어 있기 때문에 이러한 swizzling를 추가할 수 있습니다. 프레임워크 자체의 호출은 최적화되어 있지 않기 때문에, 런타임 구현의 대체는 당분간 안전하게 할 수 있습니다.

또한, 적합한 버전의 iOS와 OS X에만 패치할 것입니다. 왜냐하면 iOS 6에서 iOS 7로의 마이그레이션에서 많은 코드가 깨진 것을 본 것처럼 매우 극적인 변화가 있기 때문입니다. 마이너 버전에만 한정하고 미래 버전의 OS는 swizzling 의한 패치를 적용하지 않도록하는 것이 좋습니다.

이어서 패치를 가능한 한 많은 디바이스에서 테스트해야 합니다. 많은 프레임워크는 장치에 따라 다른 방법으로 컴파일될 뿐만 아니라 시뮬레이터와 장치 사이에서 다르게 사용하고 있습니다.

종종 패치는 시뮬레이터에서 문제를 고치더라도 디바이스에 적용 할 수 없거나 문제를 해결하지 않을 수 있기 때문에 swizzle 된 메소드를 시뮬레이터만으로 테스트하는 것은 피해야합니다.

마지막으로 만약 관심이 있다면, 관점 지향 프로그래밍을 배워볼 필요가 있습니다.

C언어 함수의 패치 (10:12)

또 다른 예는 내 친구가 만든 Organizer 라는 앱입니다. 이 앱은 오래된 카메라에서 사진을 많이 가져올 때 iPhoto가 찍힌 사진의 메타 데이터를 제거하거나 망치는 문제를 해결하는 것입니다.

Organizer에서 사진의 메타 데이터를 조정할 수 있습니다. 친구 그의 말에의하면 Apple도 사내에서 이를 사용하고 있다고 합니다. 여기에는 Photos.framework을 사용합니다. 1 개의 이전 버전의 OS에서 도입 된 사진 라이브러리를 사용하는데 편리한 방법을 제공하는 것을 목적으로 하고 있습니다.

이 프레임워크의 훌륭한 기능으로 사진 라이브러리의 변경을 감시 할 수 있는 기능이 있으며, UI에서 변경을 애니메이션 할 수 있습니다. 예를 들어, 사진이 앱 외부에서 삭제 된 경우 그리드 모양으로 늘어선 사진 목록에서 변경을 애니메이션할 수 있습니다. 이 프레임워크는 이러한 동작을 그냥 reloadData () 호출보다 훌륭하게 수행합니다.

이 데모에서는 Apple 웹 사이트에서 직접 다운로드 할 수 있는 샘플 프로젝트를 보여드립니다. 이것은 사진 라이브러리를 표시하는 간단한 앱입니다. 제가 원래 코드 이외에 유일한 변경 사항은 사진 보관함을 모니터링하고 변화를 애니메이션시킬 것입니다. 실제로 사진을 누르면 앱이 사진의 수정 시간을 현재 시간으로 변경하고 정렬 애니메이션을 실행시킵니다.

이러한 변화가 그 자리에서 발생하는 것을 확인할 수 있으며, 또한 누군가가 백그라운드에서 이 사진을 변경하려고는 하지 않습니다. 하지만, 사진을 누르면 크래시가 발생합니다. 이것은 탭 부분을 제외하고는 Apple 웹 사이트에서 찾을 샘플 코드 그대로입니다. 그리고 탭 부분에 버그가 없다는 것을 약속합니다. 이 부분은 의도대로 움직이고있는 것입니다.

크래시를 파악(데모) (13:04)

이제 문제를 찾고 우리끼리 해결해봅시다.

[데모] - 크래시 후 스택 추적에서 크래시은 우리가 만든 것이 아닌 메소드에서 나타나고 있습니다. 스택의 탑이 objc_msgSend 을 표시하고 있다고는 하지만, 문제는 대부분 거기서 아니라 그 이전 단계에 있습니다. objc_msgSend 단지의 마지막 자취입니다.

하지만 그 아래에는 어딘가 Apple의 코드에서 온 2개의 모르는 것들이 있습니다. 이것이 우리가 추적하고 고쳐야 하는 것입니다.

팁 : 콘솔에 텍스트가 표시될 만한 것보다 더 좋은 크래시의 스택 추적을 얻기 위해서는 “All Exceptions”를 사용하는 것이 좋습니다. 이는 예외가 던져 캐치되지 않았던 후에 그치는 것이 아니라 예외가 발생한 그 순간에 멈춰줍니다. 그러면 스택에서 변수의 상태를 확인할 수 있게됩니다.

크래시를 발생시키는 것을 추적하기 위해 우선 Quick Open에서 이름을 검색하는 것을 시도합니다. 그리고 Apple Public 헤더 안에 나타나는 것을 기대합니다. 이 예에서는 Photos.framework에서 특정 클래스를 나타내고 있었기때문에 운이 상황입니다.

하지만, 아무런 결과도 얻지 못한 경우에는 GitHub에서 검색하는 것을 추천합니다. iOS의 내부 플레이 베이스 헤더를 덤프 한 많은 저장소 (예를 들면 이것은 등)이 있습니다. 그래서 그 클래스나 메소드의 이름을 입력하면, 그것이 Apple의 프레임워크 어디에서 왔는지 알 수 있습니다. 이것은 모든 프레임워크에서 문자열을 뽑아서보다 좋은 방법입니다. 아주 고려 가치가 있습니다.

Disassembling Hopper(데모) (16:15)

이제 이 크레시가 Photos 프레임워크에서 일어난 것을 알 수 있었습니다. 그럼 이제 어떻게 할까요? 어떻게 실제로 무엇이 충돌하고 있는 것인지 파악할까요?

Hopper Disassembler는 이 경우에 놀랄만큼 유용한 도구입니다. 만약 단순히 iOS와 OS X가 어떻게 움직이고 있는지에 관심이 있다면 이 도구를 시작하고 프레임워크를 열어 봅시다. 컴파일 된 코드를 디스 어셈블 할 수 있고, 원래의 코드에 가까운 형태로 아주 좋게 표시 해줍니다.

Hopper를 이용하여 프레임워크를 읽고 잘못된 클래스를 발견하고 충돌 메소드를 검색 할 수 있습니다. 어셈블리가 빙의되지만, 의사 코드 버튼으로 Objective-C와 어셈블리의 혼합물을 볼 수 있고, 문제의 해독을 시도하고 추적 할 수 있습니다. 때때로 이것은 문제 자체를 표시하여 주지만, 어셈블리 지식이 도움이됩니다. 이 예와 같은 실제 문제는 추적하고 이해하는 것이 어려울 수 있습니다.

이 앱은 분명히 8개의 파라미터를 가진 정적인 C 함수에서 충돌하고 있습니다. 재미 있네요 :

diffArrays(var_24, eax, edi->_changedItems, nil, nil, nil, nil, nil);

void diffArrays(NSArray <NSManagedObject *> *arg0,
    NSArray <NSManagedObject *> *arg1,
    NSArray <NSManagedObject *> *arg2,
    NSIndexSet **arg3,
    NSIndexSet **arg4,
    NSIndexSet **arg5,
    NSArray <NSManagedObject *> **arg6,
    NSIndexSet **arg7);

이 함수는 사진을 나타내는 관리 객체와 삽입, 삭제, 갱신 등의 변화를 나타내는 인덱스 세트를 객체에 취합니다.

이 함수는 끝에서 무엇이 변경되었는지 반환해야합니다. 그야말로 제가 먼저 하려고 한 것이 었습니다. 이 프레임워크는 변경 사항을 모니터링하고 UI에서 애니메이션을 가능하게 해줍니다. 그리고 이 함수는 그 핵심에있는 것입니다.

하지만 정적인 C 함수이기 때문에 이를 swizzle하는 방법은 없습니다. swizzle하여 이 함수를 대체 같은 클래스가 단순히 존재하지 않기 때문에 앞의 예에서의 해결 방법을 사용할 수 없습니다.

다음과 같은 우리의 패치는 이것을 다른 방법으로 해결하고 있습니다. 즉, Swift의 내부 구현에 손을 뻗어 (Swift 자체의 불안정한 해결책입니다) 실제 문제가 있는 함수의 주소에 액세스합니다.

충돌 해결 (19:14)

가장 쉬운 파트는 문제를 발견한 후 어떻게 해결할 지 간단하게 발견하는 부분입니다. 함수 자체의 구현을 쫓는 것은 매우 힘든 것으로 도중에 포기했습니다. 하지만 내가 찾으려했던 것은 deallocated 된 메모리에 전형적인 배드 포인터지만, 누군가가 아직도 그 포인터를 사용하여 메모리에 액세스하려고 하고있는 것이라고 깨달았습니다.

이에 패치를 위해, 나는 마지막으로 반환되는 매개 변수를 하나를 여분으로 retain 하기로 했습니다. 내 친구가 이 해결 방법을 Apple의 Q & A 엔지니어에게 보여 줬더니 그는 지금에서는 8 바이트 누수가 발생하는 것이라고 설명했습니다. 하지만, 이것은 충돌하지 않습니다 - 훨씬 더 나은 결과입니다.

이러한 종류의 문제를 고치는 것은 항상 어떻게 움직이고 있는지를 이해하려고 노력하고, 과도한 메모리를 희생하지 않도록하는 것, 그리고 앱의 핵심 기능으로 사용자를 행복하게 하는 것의 균형입니다. 만약 날짜를 변경할 때마다 매번 앱이 충돌하면 누구도 행복하지 않을 것입니다.

Swift에서 C 언어 패치 (21:02)

다음은 패치의 내용을 하나 하나 살펴보도록 하겠습니다.

// Internal structures
struct swift_func_wrapper {
    var trampolinePtr: UnsafeMutablePointer<uintptr_t>
    var functionObject: UnsafeMutablePointer<swift_func_object>
}
struct swift_func_object {
    var original_type_ptr: UnsafeMutablePointer<uintptr_t>
    var unknown: UnsafeMutablePointer<UInt64>
    var address: uintptr_t
    var selfPtr: UnsafeMutablePointer<uintptr_t>
}

이 버전 (2.0)까지 Swift는 내부에서 함수를 이와 같이 나타냅니다. 만약 Swift 내부 함수의 심장부에 도달하고 싶다면 주소를 얻기 위해 이 구조를 잘라내어 내부에 도달해야 합니다. 이것은 Swift 함수에서 C에, 혹은 C 함수에서 Swift에 접근하는 데 필요한 프로세스에서, 그 중에서 함수의 주소에 접근 할 수 있습니다.

다음 예제로 간단한 함수를 정의하고 어떻게 이를 호출하거나 원래의 버그를 포함한 함수에 끈을 붙이는지 보여드립니다. 실제로는 C 함수에 대응 한 8개의 인수가 있으며 이들을 retain 하도록 ​​구현 될 것입니다. 여기에 문자열을 기록하는 간단한 것을 예를 들어보겠습니다. :

// Method we want to call
func hello(world: String) -> Void {
    print("Hello, \(world)")
}

typedef helloFn = (String) -> Void

다음 Swift 함수의 뒤에 숨어있는 실제 C의 함수 포인터를 얻습니다 :

//  C function pointer
let fn = UnsafeMutablePointer<helloFn>.alloc(1)
fn.initialize(hello)
let fnWrapper = UnsafeMutablePointer<swift_func_wrapper>(fn)
let opaque = COpaquePointer(bitPattern: fnWrapper.memory.functionObject.memory.address)
let cFunction = CFunctionPointer<helloFn>(opaque)

여기에서는 우선 우리가 정의한 Swift 함수에서 시작하고 그 후에 Swift 함수를 나타내는 구조의 구현에 대해 자세하게 주소를 구하겠습니다. 다음에는 실제로 C의 함수 포인터로 얻습니다. 이것은 C 언어 측에 전달하고 거기에서 호출하는 것으로 이 부분은 단순히 잘됩니다.

여기에서 아직 언급하지 않은 것은 어떻게 원래의 구현을 호출 하는가 하는 것입니다. 이 경우에는 내가 어떻게 원래의 함수를 다시 구현하는지는 알고 싶지 않기 때문에 원래의 구현을 호출하여 반환되는 객체를 retain 해야합니다. 이것은 Swift 1.2에서의 문제점입니다. C의 함수 포인터를 이용하여 C 함수를 호출 할 수 없습니다. 명확하게 말하면, Swift에서 C 함수를 호출 할 수 있지만 다른 도구를 사용 함수 포인터를 얻어, Objective-C에서 그러 하듯이 포인터를 통해 함수를 호출 할 수 없습니다.

결과적으로 이 부분은 Objective-C에서 구현해야합니다. 지금은 이렇게 할 수 밖에 없습니다.

결론 및 Tips (24:30)

먼저 버그 해결 방법을 찾아내고 도구를 사용하여 패치를 적용해야합니다.

사용한 해결 코드를 패치하는 실제 엔진은 Fishhook 입니다. 이것은 Objective-C에서 실행시 어떤 C 함수에서도 얻을 수 있으며, 기본적으로 swizzle할 수 있는 라이브러리입니다.

다시 말하면, 그 함수를 부르는 경우는 누구도 당신의 구현을 호출하게 되고 또한 패치에서 원래 구현을 얻을 수 있습니다. 여기가 Swift가 미치지 못하는 바입니다. 하지만, Swift 2.0에 대해 내가 이해하는 것부터 말하면, 잠재적으로 @convention(c)이 그것을 직접 Swift에서 할 수 있게 해 줄 것입니다.

다른 프레임워크의 문제해결 (25:28)

만약 Objective-C 코드에 문제가 있다면, swizzle하고 그것으로 끝입니다.

만약 문제가 C 함수에 있다면 프로젝트에 Objective-C 코드를 추가해야하기 때문에 다소 볼품없게 되어 버립니다. 본대로 Swift는 이런 부분은 아주 나쁩니다. Swift 최적화로 인해 최적화가 없을 때와 마찬가지로 동적으로 처리되는지는 보장되지 않습니다. 그러므로 런타임 메소드 대체의 기회는 없습니다.

최적화는 우리가 좋아하는 Swift의 속도와 이점을 가져다 주지만 동시에 이러한 패칭에 의한 해결책을 가로막고 있습니다.

참고 문헌 및 자료 (26:19)

  • Perceptual Debugging by Kendall Gelner: 가장 중요한 파트인 직관적인 디버깅 능력을 발달시키는 프로세스에 대한 논의를 하는 AltConf의 추천 세션입니다. 나머지 패치를 하는 방법은 단순한 기술이지만, 어떻게 문제를 찾거나 어떻게 패치 방법을 발견하는 방법을 이해할 필요가 있습니다. 이것은 배우고 숙련하는 데 시간이 걸립니다.

  • Reverse Engineering by Samantha Marshall: 리버스 엔지니어링에 대한 도구 기술에 대해 같은 유용한 링크입니다.

  • Unsafe Swift: For Fun & Profit by Russ Bishop: C 언어와 Swift의 상호 작용에 대한 심층적인 논의입니다. 이 주제에 내가 가볍게 언급했고 모든 것을 다루고 있습니다. 구조체 및 세이프 포인터 등. 이 토크를 보는 것을 매우 추천합니다. 꽤 재미 있습니다.

  • Peter Steinberger’s blog by himself: 그는 일 때문에 PSPDFKit 에 임하는 일환으로 UIKit에 대단한 패치를 맞추고 있기 때문에 좋은 자료집이 되고 있습니다.

  • WWDC debugging sessions: 당신을 더 다은 인간으로 만들어 줍니다. 어쨋든 봅시다.

  • Fishhook: C 언어의 함수를 swizzle하는데 도움이 될 라이브러리입니다.

  • Aspects:Peter에 의한 멋진 라이브러리에서 앱 전체에 걸쳐서 적용되는 기능을 추가합니다. 처음의 예에서 나타냈습니다.

Q&A (29:09)

Q: 예전에는 함수의 오프셋에 따라 스택 중의 주소를 바탕으로 코드의 위치를​​ 나타내는 로드맵을 생성하는 도구가 있었다고 생각합니다만, iOS에서 LLVM을 이용한 뭔가가 있나요?

Sash: LLVM을 사용하여 실행을 일시 정지했을 때 런 루프 어딘가에 착지해 버리고 거기에서 조사해야지 알 수 있다고 생각합니다. 이를 좀 더 쉽게 할 수 있도록 특정 도구에 대해 많이 알지 못합니다.

청중의 한 사람 (@nevyn) : 이건 단순히 otool or nm 사용하면 실행이 가능합니다. ─ 어느 쪽 이었는지는 기억하지 않지만 바이너리의 로드 명령의 목록을 얻을 수 있습니다.

번역:Yongbin Cha

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.

Sash Zats

Sash leads the iOS team at Wondermall. He started developing for iOS back when it was just Objective-C! Being a big fan of creative solutions, he believes in repurposing technologies as long as they work great and solve problems in the best way possible.

4 design patterns for a RESTless mobile integration »

close