iOS2015. 8. 31. 13:38



아래는 애플뉴스스(https://developer.apple.com/news/?id=08282015a)의 전문이다.

Supporting IPv6 in iOS 9

August 28, 2015


At WWDC 2015 we announced that iOS 9 will support IPv6-only network services. All apps submitted to the App Store must support IPv6 starting in early 2016. To make sure your app is compatible, use the networking frameworks (e.g., “NSURLSession”), avoid use of IPv4-specific APIs, and avoid hard-coded IP addresses. Before submitting your app, test for compatibility.

To learn more, read Supporting IPv6 DNS64/NAT64 Networks and watch Your App and Next Generation Networks.


WWDC2015에서 이미 언급했다는데 iOS9부터 IPv6-only network service를 지원할거라나. 코드상에 웹서비스 주소를 도메인주소를 사용하고 있다면 문제가 안되겠지만 ip주소를 사용하고 있다면 문제가 될거란다. 내년(2016) 초부터 앱스토어가 지원을 시작할거라는데 이 얘기인즉슨 그런 앱들을 리젝을 놓겠다는건지 정확히 모르겠다. 무슨 지혼자 잘났다고 IPv6를 강제한단 말인가? 장비부터 해서 갑자기 강제하면 분명히 혼란과 혼돈이 있을것인제...

무튼 IPv4전용 API는 하드코딩된 IP주소를 모두 피해야 한다. 기존 앱들은 물론 다 바까야겠지.

자 그럼 불평만 할떄가 아니다 기존앱들은 그리고 새로운앱들은 또 어떻게 대응해야 할까를 연구해보자...

만일 앱이 high-level network api를 즉 NSURLSession이나 CFNetwork frameworkd의 API들을 사용하고 연결은 도메인명으로 하고 있다면 아무것도 할게 없다.

만일 도메인명으로 연결하고 있지 않다면 가이드(Avoid Resolving DNS Names Before Connectiing to Host)를 참고







Posted by 삼스
iOS2015. 8. 28. 16:12


http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/

https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/


App Transport Security Technote


앱과 웹서비스간의 보안을 더 향상시킬 수 있는 기능이다.

디폴트연결 요구사항들로 구성한다. 이 요구사항은 보안연결을 위한 최적의 구성을 기술한다.

App은 이 동작에 대해 오버라이드하거나 기능을 아예 꺼버릴 수 있다.

iOS 9.0, OSX 10.11 이후부터 지원한다.


디폴트 동작

iOS 9.0과 OSX 10.11에서 돌아가는 앱에서 NSURLConnection, CFURL 또는 NSURLSession을 이용한 모든 통신은 Transport security의 디폴트 동작을 사용한다.

이 요구사항에 부합하지 않은 모든 연결을 실패할것이다.


ATS 필수 요구사항

- 서버는 Transport Layer Security(TLS) protocol version 1.2이상을 지원해야 한다.

- 암호화는 forward secrecy를 지원하는것들로 제한된다.

- 인증서는 반드시 SHAR256이나 더 발전된 해시알고리즘을 사용해야 한다. 잘못된 인증서는 실패한다.


허용된 암호화

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384

TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA

TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256

TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384

TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256

TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA


예외

디폴트 동작에 대한 예외를 info.plist파일을 통해서 정의할 수 있다. 해당 예외에 대한 키값들을 사용하거나 기능을 끌 수 있다. 다음은 키값들과 키값에 해당하는 타입들을 나타내었다.



NSAppTransportSecurity   Dictionary

    NSAllowsArbitraryLoads   Boolean

    NSExceptionDomains Dictionary

        <domain-name-for-exception-as-string> Dictionary

            NSExceptionMinimumTLSVersion String

            NSExceptionRequiresForwardSecrecy Boolean

            NSExceptionAllowsInsecureHTTPLoads Boolean

            NSIncludesSubdomains Boolean

            NSThirdPartyExceptionMinimumTLSVersion String

            NSThirdPartyExceptionRequiresForwardSecrecy Boolean

            NSThirdPartyExceptionAllowsInsecureHTTPLoads         Boolean



NSAppTransportSecurity

  디폴트동작을 오버라이드할 셋팅값들을 포함하는 딕셔너리이다. info.plist의 최상위 key값


NSAllowArbitraryLoads

  NSExceptionDomains 딕셔너리에 리스터업되지 않는 도메인들에 대한 ATS를 사용할지 boolean 값으로 정의한다.

  디폴트값은 NO이고 모든 연결들에 대해 ATS를 요구한다.


NSExceptionDomains

  ATS 예외 도메인들의 딕셔너리이다. 각 키는 예외할 도메인명을 담은 문자열이다. named domain이며 예를 들면 www.google.com같은것이다.


NSExceptionMinimumTLSVersion

  최소 TLS버전으로 TLSv1.0, TLSv1.1, TLSv1.2 가 있다. TLSv1.2가 디폴트값이다.


NSExceptionRequiresForwardSecrecy

  도메인이 지원하는 암호화 요구사항을 오버라이드 한다. YES가 디폴트이며 디폴트동작에서 정의한 암호화로 제한다.

  No이면 다음 암호화가 추가된다.


TLS_RSA_WITH_AES_256_GCM_SHA384

TLS_RSA_WITH_AES_128_GCM_SHA256

TLS_RSA_WITH_AES_256_CBC_SHA256

TLS_RSA_WITH_AES_256_CBC_SHA

TLS_RSA_WITH_AES_128_CBC_SHA256

TLS_RSA_WITH_AES_128_CBC_SHA


NSExceptionAllowsInsecureHTTPLoads

  No가 디폴트값이며 모든 연결이 HTTPS를 사용할지 결정한다. 이 키값으로 도메인이 인증서없이 접근하거나 self-signed로 접근하거나, 만료되거나 호스트명 미스매치 인증으로 접근가능하도록 할 수 있다.

  

NSIncludeSubdomains

  No가 디폴트 값이며 최상위 레벨 도메인의 모든 서브도메인에도 적용할지 결정한다.


NSThirdPartyExceptionMinimumTLSVersion

NSThirdPartyExceptionRequiresForwardSecrecy

NSThirdPartyExceptionAllowsInsecureHTTPLoads

  개발자에의해 앱서비스가 컨트롤되지 않는 도메인의 경우 사용된다. <- 먼소린지???



Posted by 삼스
iOS2015. 5. 13. 19:02



매버릭스부터인가? automake, autoconf, libtool을 xcode가 기본제공하지 않는다고 한다.

그리하여 부득이 아래와 같이 직접 소스를 빌드하여 설치하여야 한다.


curl -OL http://ftpmirror.gnu.org/autoconf/autoconf-2.69.tar.gz
tar -xzf autoconf-2.69.tar.gz 
cd autoconf-2.69
./configure && make && sudo make install
 
curl -OL http://ftpmirror.gnu.org/automake/automake-1.14.tar.gz
tar -xzf automake-1.14.tar.gz
cd automake-1.14
./configure && make && sudo make install
 
curl -OL http://ftpmirror.gnu.org/libtool/libtool-2.4.2.tar.gz
tar -xzf libtool-2.4.2.tar.gz
cd libtool-2.4.2
./configure && make && sudo make install


Posted by 삼스
iOS2015. 4. 3. 15:16



objective C에 비해 swift의 생소한것들 중에 optional value란것이 있다.

자바의 Nullable과 Nonnull과는 좀 마이 다르다.

어떤 변수(값이나 참조모두)에 대해 nil을 허용할것인가에 대한 옵션이 되겠는데 이게 무슨소용인가 싶을 것이다.

기존에 코딩시 습관적으로 if (someobject != nil) 을 삽입하던 코드를 좀더 간결하고 버그가능성을 줄일 수 있도록 많은 고민을 한듯 하다.


먼저 변수를 선언하는 방법은 그냥 선언하는것과 Optional로 선언하는 방법이 있겠다.

var normalValue: String = "samse"

var optionalValue: String?


optional의 의미는 nil을 허용하는가에 대한 것이다 따라서 아래 코드는 에러다.


normalValue = nil // Type 'String' does not conform to protocol 'NilLiteralConvertible'


아래코드는 정상코드이다.


optionalValue = nil


optional 변수는 Explicit optional 변수와 Implicit optional 변수가 있다.

?를 붙인것이 Explicit이고 !를 붙인것이 implicit이다.


var explicitValue : String?

var implicitValue : String!


Explicit변수는 참조 시 nil인지 확인을 하기 위해 Forced unwrapping을 해야 한다. 해당 변수에 !를 붙임으로서 forced unwrapping이 수행된다

.

var unwrappedExplicitValue = optionalValue!


반드시 forced unwrapper를 통해서 값을 추출해서 사용해야 에러가 발생하지 않는다.


if unwrapperExplicitValue != nil {

    println( unwrapperExplicitValue)

}

Implicit 변수는 참조시 unwrapper를 안해도 되나 런타임에 오류가 발생 가능하므로 항상 주의해야 한다.


if implicitValue != nil {

    println( implicitValue )

}

Optional binding을 통해서 코드를 좀더 간결하게 할 수 있다.


if let value = explicitValue { // explicitValue! 로 표기하지 않는다.

  println("Value = \(value)")

}


형식은 아래와 같다.


if let [새변수명] = [optional 변수 또는 optional변수를 반환하는 함수] { // 

  ..

}


위에서 [새변수명]은 scope이 if문 내 이다.

if문내에서 [새변수명]을 변경하고자 할 경우에는 let이 아니라 var를 사용한다.


if var value = explicitValue {   

    value = "changed to SAMSE"

    println("Value = \(value)")

}


값이 nil인 경우 디폴트값을 사용하고자 할 수 있는데 이 때 ?? 연산자를 제공한다.


var value = explicitValue ?? "SAMSE"

위 코든는 explicitValue가 평가한바 nil이면 value에 "SAMSE"를 대입한다.



클래스도 참조로서 당연히 optional로 선언가능하고 사용가능한데 class내의 optional 변수를 참조하는데 좀더 간결하게 접근하는 방법을 제공한다. 애플개발자들이 많은 고민을 하면서 어떻게든 코드양을 줄이고 편하게 코딩하고자 한 노력이 보이는 부분이다.

class Person {

    var contact: Contact?

    init() {}

}

class Contact {

    var address: String?

    var tel: String?

    var email: String?

    var extra: String = "admin@gmail.com"

    init() {}

}


위 와 같이 클래스를 정의하고 아래처럼 Persion객체를 생성하였다.


var p = Person()


클래스 선언부에서 변수선언시 초기화하지 않았기 때문에 초기값은 extra를 제외하고 optional인 변수들은 모두 nil이 된다.

따라서 아래 코드는 p의 contact가 nil이기 때문에 컴파일 에러가 발생한다.


var email = p.contact!.email


nil 체크를 반드시 해주어야 한다.


if let contact = p.contact {

    if let email = contact.email! {

        println(email)

    }

    else {

        println("nil email")

    }

}

else {

    priintln("nil contact")


이 때 위 코드를 간결하게 줄여주는 방법이 Optional Chaining 표기법이다. 이는 변수 뒤에 ?를 붙임으로서 가능하다. 

다음 코드는 정상이다.


var email2 = p.contact?.email?


이 코드는 p.contact가 nil이므로 검사를 통해 email2에 nil을 대입한다. optional binding과 함께하여 다음과 같이 코드 작성이 가능하다.


if let email = p.contact?.email? {

    println('email = \(email)")

} else {

    println("email is nil")

}


email is nil이 표시될것이다.


값을 대입할 때도 chaining을 통해 nil이 반환되면 해당 변수에 값이 대입되지 않는다.


p.contact?.email? = "someone@gmail.com"

if let email = p.contact?.email? {

    println("Email : \(email)")

} else {

    println("Email is nil yet")

}


마찬가지로 Email is nil yet이 표시될것이다.


아래와 같이 contact를 유효하게 만들어주면 대입이 될것이다.


p.contact = Contact()

p.contact?.email? = "someone@gmail.com"


if let email = p.contact?.email? {

    println("Email : \(email)")

else {

    println("Email is nil yet")

}

Email : someone@gmail.com 이 표시될것이다.


Contact클래스의 extra field는 optional 변수가 아니다.


class Contact {

    var address: String?

    var tel: String?

    var email: String?

    var extra: String = "admin@gmail.com"

    init() {}

}


이는 optional chaing으로 접근 시 타입은 String형식이더라도 옵셔널인 String?으로 받게됨을 알아야 한다. <-- 중요 중요!!!

let extra = p.contact?.extra! // p.contact?extra String? 반환함으로써 타입캐스팅 에러 발생


이는 다음과 같이 수정되어야 한다.


let extra = (p.contact?.extra)!

또는

if let extra = p.contact?.extra {

    // ...

}

애플개발자가 고민하여 만든것 같은데 nil에 대한 대비를 코딩레벨로 끌어내려서 런타임에러가 많이 줄어들것으로 예상된다. 실제 개발하면서 빠르게 익숙해져야 할듯 하다.





Posted by 삼스
iOS2014. 11. 20. 14:12


iOS7과 A7 processor로 64비트가 가능해짐.

32비트앱을 동일 디바이스에 동작시킬때 성능향상이 있다고 함. 실제로 느껴질정도는 모르겠음.


A7은 두개의 구분되는 명령어셋틀 가진다.

이전프로세서의 32비트 명령어셋과 64비트프로세서의 명령어셋을 추가고 제공한다. 두배 많은 integer와 floating-point 레지스터를 제공한다. 

64비트 앱은 성능향상을 위해 더 많은 데이터를 사용한다. 

64비트 숫자연산이나 NEON operation을 수행하는 앱에서 더 많은 성능향상이 있다.


64비트 시스템에서 포인터는 64비트이다. 기존의 32비트 정수도 이제 64비트이다. UIKit이나 Foundation같은 많은 시스템 프레임워크들은 이미 변경되었다. 변경은 64비트 앱이 32비트 앱보다 더 많은 메모리를 사용한다는 것이다. 주의 하지 않으면 더 많은 메모리를 사용하면서 앱 성능에 안좋은 영향을 미칠수 있다.


iOS는 32비트와 64비트용 시스템 프레임워크를 모두 가지고 있다.

모든앱이 로드될때 64비트 런타임이 로드된다. 32비트용은 로드하지 않는다. 따라서 시스템은 메모를 절약하면서 더 빠르게 앱을 실행한다. 빌트인앱들이 모두 64비트런타임을 지원하기 때문에 모든 앱들도 64비트 런타임을 지원하도록 빌드되어야 한다.


XCode 5.0.1부터 64비트 바이너리 생성이 가능

타겟은 iOS5.1.1이상이어야 한다.

64비트 바이너리는 iOS7.0.3이상의 64비트 지원 디바이스에서 구동된다.


기존앱의 경우 

1. iOS7로 업데이트

-  deprecate된 코드 패스들을 모두 제거 하고 modern practice를 사용해야 한다.

2. 64비트 포팅


새로운앱의 경우 

1. 타겟을 iOS7로

2. 32비트와 64비트로 앱을 빌드


Cocoa Touch앱을 64비트로 변환과정

- Pointer와 C 타입중에 32비트를 64비트로 변경


64비트 런타임으로 빌드를 하면 64비트 지원관련하여 경고가 뜰것임.


- 함수호출이 적절한 프로토타입으로 호출되고 있는지

- 64비트 값을 32비트에 대입하면서 발생하는 데이터 유실 회피

- 연산들이 64비트에서 적적하게 동작할지 

- Ensure that calculations are performed correctly in the 64-bit version of your app. <- 머 알아서 연산부분 챙기라는 말같고.

- Create data structures whose layouts are identical in the 32-bit and 64-bit versions of your app (such as when you write a data file to iCloud). <- 32비트와 64비트용 데이터구조체를 사용하라는데 먼소린지 잘 모르겠다.. 더 공부해야 할듯..


Posted by 삼스
iOS2014. 5. 27. 16:27


http://code.tutsplus.com/tutorials/ios-7-sdk-core-bluetooth-practical-lesson--mobile-20741

Core Bluetooth framework은 iOS앱에서 Bluetooth low energy기술을 사용할 수 있게 해준다. 이 튜토리얼은 iOS5에서 iOS7으로 CB가 어떻게 진화하였는지 안내할것이다. CB central과 peripheral을 어떻게 설정하고 서로간에 통신을 하며 어떻게 CB로 코딩이 가능한지에 대해서 설명한다.

Introduction

CB 튜토리얼은 두개의 장으로 나누어지는데 첫번쨰 장에서는 CB에 대한 이론을 설명한다. 반면에 이 튜토리얼은 완전한 예제를 통한 실용적인 과정이 될것이다 관련된 전체 소스코드가 수록되어 있다.

1. 소스코드 다운로드

이 튜토리얼의 목적은 CB framework을 어떻게 사용하는지에 대해 가르치는 것이다. 샘플코드를 통해 쉽게 알수 있다. 코드를  다운로드 먼저 하라.

CB framework에 집중할것이기 때문에 당신이 XCode와 iOS에 대해 잘 알고 있다고 가정할것이다. 샘플코드는 다음을 포함한다.

  • 앱은 navigation controller, 3개의 view 그리고 상속한 controller들로 구성된다.
  • 초기 뷰컨트롤로인 ViewController는 2개의 버튼으로 구성된다.
  • CBCentrolManagerViewController는 custom iBeacon을 생성한다.
  • CBPeripheralViewController는 iBeacon과 상속한 정보를 수신한다.
  • SERVICES헤더파일 : 앱에서 사용하는 변수들 

기본적인 구현은 다 되어 있으며 CB process관련하여 코드를 추가하면 된다. 프로젝트를 열고 실행하면 된다.

SERVICES.h파일은 2개의 UUID가 있는데 uuidgen으로 생성한 값이다. 이 값은 재생성해서 변경해도 된다.

이 테스트는 iOS장비가 있어야 테스트가 가능하다.

앱을 실행하면 다음 화면이 나온다.



2.  Central Role 프로그래밍

이 튜토리얼에서  CBCentralManagerViewController class가 중앙에 있다. 첫번째 과정은 CBCentralManager와 CBPeripheral을 지원하기 위한 2개의 프로토콜을 추가하는것 이다. 이 프로토콜들의 선언은 메소드를 정의한다. interface는 다음과 같을 것이다.

1
@interface CBCentralManagerViewController : UIViewController < CBCentralManagerDelegate, CBPeripheralDelegate>

이제 반드시 3개의 속성을 정의해야 한다. CBCentralManager, CBperipheral그리고 NSMutableData가 그것이다. 두개는 명백한 정보이고 하나는 디바이스간에 주고받는 정보이다.

1
2
3
@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) CBPeripheral *discoveredPeripheral;
@property (strong, nonatomic) NSMutableData *data;

에러가 날거고 수정해야 한다. centralManager와 data 객체는 초기화해야 한다. centralManager는 sele delegate로 시작하고 viewDidLoad에서 다음과 같이 수행한다.

1
2
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
_data = [[NSMutableData alloc] init];

warning을 없에려면 다음 메소드를 추가한다.

- (void)centralManagerDidUpdateState:(CBCentralManager *)central

프로토콜메소드로이며 디바이스의 상태를 체크한다. 몇가지 상태가 정의되어 있다. 앱에서는 항상 상태를 확인해야 한다.

  • CBCentralManagerStateUnknown
  • CBCentralManagerStateResetting
  • CBCentralManagerStateUnsupported
  • CBCentralManagerStateUnauthorized
  • CBCentralManagerStatePoweredOff
  • CBCentralManagerStatePoweredOn

예를 들어, Bluetooth 4.0을 지원하지 않는 디바이스에서 이 앱을 실행하면 CBCentralManagerStateUnsupported 을 받게 될것이다.

CBCentralManagerStatePoweredOn 상태가 되면 디바이스를 스캔을 시작할 수 있다. scanForPeripheralsWithService메소드로 스캔한다. 첫번째 파라메터에 nil을 넣게 되면 CBCentralmanager는 모든 디바이스를 검색한다. UUID를 사용하게 되면 UUID에 해당하는 디바이스를 찾게 된다.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // You should test all scenarios
    if (central.state != CBCentralManagerStatePoweredOn) {
        return;
    }
     
    if (central.state == CBCentralManagerStatePoweredOn) {
        // Scan for devices
        [_centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
        NSLog(@"Scanning started");
    }
}

  이 때 앱은 다른  디바이스를 검색한다. 디바이스가 실제로 있음에도 불구하고 정보를 찾지 못할 수 있다. 이를 해결하기 위해서는 다음 메소드를 추가해야 한다.

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI

이 메소든느 디바이스가 발견될때마다 호출된것이다. 하지만 TRANSFER_SERVICE_UUID로 게시중인 peripherals들에 대해서만 반응할것이다.

추가적으로 캐시시스템을 사용하여 추후에 참조하여 더 빠르게 통신할 수 있도록 디바이스정보를 저장할 수 있다. 다음코드를 보라

01
02
03
04
05
06
07
08
09
10
11
12
13
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
     
    NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);
     
    if (_discoveredPeripheral != peripheral) {
        // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
        _discoveredPeripheral = peripheral;
         
        // And connect
        NSLog(@"Connecting to peripheral %@", peripheral);
        [_centralManager connectPeripheral:peripheral options:nil];
    }
}

연결이 실패할 수 있다. 이 경우 다음 메소드로 알수 있다.

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error

1
2
3
4
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"Failed to connect");
    [self cleanup];
}

사용자에게 경고할 수 있다. cleanup메소드는 아직 정의되지 않았다. 이제 정의해보자!. 

이 메소드는 원격디바이스에 대한 모든 subscription정보를 취소하거나 연결을 끊어버린다. 서비스에 속한 특성들을 루프를 돌면서 바인드되어 있는것들을 제거한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
- (void)cleanup {
     
    // See if we are subscribed to a characteristic on the peripheral
    if (_discoveredPeripheral.services != nil) {
        for (CBService *service in _discoveredPeripheral.services) {
            if (service.characteristics != nil) {
                for (CBCharacteristic *characteristic in service.characteristics) {
                    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
                        if (characteristic.isNotifying) {
                            [_discoveredPeripheral setNotifyValue:NO forCharacteristic:characteristic];
                            return;
                        }
                    }
                }
            }
        }
    }
 
    [_centralManager cancelPeripheralConnection:_discoveredPeripheral];
}

성공적으로 연결이 되었다면 이젠 서비스와 특성(characteristic)을 알아내야 한다. 다음 메소드를 통해 가능하다.

 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral

연결이 완료되고 나서 스캔프로세스는 중단한다. 그리고나서 UUID(TRANSFER_SERVICE_UUID)에 맷칭되는 서비스를 검색한다.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"Connected");
     
    [_centralManager stopScan];
    NSLog(@"Scanning stopped");
     
    [_data setLength:0];
     
    peripheral.delegate = self;
 
    [peripheral discoverServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}

peripheral의 delegate를 통해 몇가지 콜백이 호출된다. 그 중 하나가 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 인데 이 메소드는 서비스에 속한 특성들을 찾아준다. 에러가 났을 때는 하지말고 성공했을 때만 찾아야 한다. 

01
02
03
04
05
06
07
08
09
10
11
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    if (error) {
        [self cleanup];
        return;
    }
 
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]] forService:service];
    }
    // Discover other characteristics
}

모든게 정확하다면 특성이 발견될것이다. 다시 콜백이 호출될건데.. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 가 호출된다. 이렇게 발견이 되면 CBCentralManager가 peripheral로부터 받고자 하는 데이터를 받을 수 있도록 subscript할 수 있다.

에러시 처리가 되어야 한다. 믿을만하다면 특성을 바로 구독할 수 있지만 반드시 특성을 하나하나 확인해볼것을 권장한다. 그리고 확인이 되었을 때 만 구독하라. 이 과정까지 완료되면 데이터가 오는것을 기다리면된다.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    if (error) {
        [self cleanup];
        return;
    }
     
    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
    }
}

peripheral이 새로운 데이터를 보낼때마다 다음 콜백이 호출된다.

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 

두번째 인자는 특성을 포함한다. 

초기에 특성값을 저장하려고 NSString을 생성할것이다. 그런 후 데이터 수신이 모두 수신되었는지 또는 더 전달이 될것인지 확인할 것이다. 새로운  데이터가 수신되자마자 textview에 갱신될것이다. 모든데이터가 완료되면 특성과 장치에 대한 연결을 해재할수 있다.

다시 말해 데이터가 수신되면 대기하든지 연결을 해재하든지 알아서 하면 된다. 이 콜백은 특성에 따라 추가적으로 데이터가 도달할지에 대해 알 수 있다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"Error");
        return;
    }
     
    NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
     
    // Have we got everything we need?
    if ([stringFromData isEqualToString:@"EOM"]) {
     
        [_textview setText:[[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]];
         
        [peripheral setNotifyValue:NO forCharacteristic:characteristic];
         
        [_centralManager cancelPeripheralConnection:peripheral];
    }
     
    [_data appendData:characteristic.value];
}

추가적으로 CBCentral이 특성 변경에 대한 통지상태를 알수 있는 메소드가 있다. 

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

특성통지가 중단되었는지 반드시 확인해야 하며 중단될경우 연결을 해재해야 한다.

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
     
    if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
        return;
    }
     
    if (characteristic.isNotifying) {
        NSLog(@"Notification began on %@", characteristic);
    } else {
        // Notification has stopped
        [_centralManager cancelPeripheralConnection:peripheral];
    }
}

디바이스들간에 연결해제가 발생하면 로컬카피로 저장해둔 peripheral을 clean up해야 한다. 다음 메소드를 사용해서..

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 

이 메소든느 단순하고 peripheral을 0으로 셋팅한다. 이어서 스켄을 다시 시작할 지 그냥 둘지 앱에서 정해서 하면 된다. 여기서는 다시 스캔하고 있다.

1
2
3
4
5
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    _discoveredPeripheral = nil;
 
    [_centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
}

최종적으로 하나의 단계가 더 있다. 뷰가 화면에서 가려질때마다 scan process를 중단해야 한다.

1
[_centralManager stopScan];

앱을 실행하자 하지만 peripherl앱이 있어야 데이터를 받을 수 있다. 다음 이미지는 최종 인터페이스를 보여준다.



3. Peripheral Role의 프로그래밍

이 튜터리얼에서는 CBPeripheralViewController에 집중한다. 먼저 CBPeripheralManagerDelegate와 UITextViewDelegate를 추가한다. 다음과 같은 인터페이스가 필요하다.

1
@interface CBPeripheralViewController : UIViewController < CBPeripheralManagerDelegate, UITextViewDelegate>

이어서 다음 4개의 속성을 추가한다. 처음 2개는 peripheral manager와 특성을 의미하고 3번째는 전송할 데이터, 그리고 마지막은 데이터 인덱스이다.

1
2
3
4
@property (strong, nonatomic) CBPeripheralManager *peripheralManager;
@property (strong, nonatomic) CBMutableCharacteristic *transferCharacteristic;
@property (strong, nonatomic) NSData *dataToSend;
@property (nonatomic, readwrite) NSInteger sendDataIndex;

먼저 _peripheralManager를 초기화하고 게시를 시작하기 위한 설정을 해야 한다. 서비스게시는 서비스UUID를 초기화하는것이다. viewDidLoad에서 다음과 같이 하면 된다.

1
2
3
4
5
6
7
- (void)viewDidLoad {
    [super viewDidLoad];
 
    _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
     
    [_peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
}

warning을 없애려면 CBCentramManager처럼 다음 메소드를 정의해야 한다.  - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral 상태가 CBPeripheralManagerStatePoweredOn이면 서비스와 특성을 빌드하고 정의해야 한다.

각 서비스와 특성은 Unique UUID이어야 한다. 

특성의 값들이 어떻게 사용되어질지를 정의하는 속성들이 있다.

  • CBCharacteristicPropertyBroadcast
  • CBCharacteristicPropertyRead
  • CBCharacteristicPropertyWriteWithoutResponse
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyNotify
  • CBCharacteristicPropertyIndicate
  • CBCharacteristicPropertyAuthenticatedSignedWrites
  • CBCharacteristicPropertyExtendedProperties
  • CBCharacteristicPropertyNotifyEncryptionRequired
  • CBCharacteristicPropertyIndicateEncryptionRequired

이 속석들에 대해 더 자세히 알고 싶으면 다음 링크를 참고해라.

 CBCharacteristic Class Reference.

i nit의 마지막 인자는 read, write 그리고 속성에 대한 안호화권한이다.

  • CBAttributePermissionsReadable
  • CBAttributePermissionsWriteable
  • CBAttributePermissionsReadEncryptionRequired
  • CBAttributePermissionsWriteEncryptionRequired

특성을 정의 완료하면  CBMutableService를 사용하여 서비스를 정의할 시간이다. 서비스는 TRANSFER_CHARACTERISTIC_UUID로 정의되어야 한다. 서비스에 특성을 추가하고 peripheral에 추가한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
        return;
    }
     
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
         
        CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID] primary:YES];
         
        transferService.characteristics = @[_transferCharacteristic];
         
        [_peripheralManager addService:transferService];
    }
}

자 인자 서비스와 특성도 정의가 되었다. 인자 디바이스가 연결되었을 때 그리고 그에 반응하는것이 남았다.

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic 메소드는 지원하는 특성에 맞는 누군가 연결이 되었을 때 호출된는데 이 때 데이터를 전송할 수 있다.

앱은 textview에서 데이터가 유효하면 전송한다. 사용자가 값을 수정하면 앱은 구독중인 central에 바로 전송한다. sendData메소드는 custom메소드이다.

1
2
3
4
5
6
7
8
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
     
    _dataToSend = [_textView.text dataUsingEncoding:NSUTF8StringEncoding];
 
    _sendDataIndex = 0;
 
    [self sendData];
}

sendData는 데이터를 전송하는 메소드이다. 다음과 같은 몇가지 동작을 수행할 것이다.

  • Send data
  • Send the end of communication flag
  • Test if the app sent the data
  • Check if all data was sent
  • React to all of the previous topics

전체코드를 아래에 나타내었다. 

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- (void)sendData {
 
    static BOOL sendingEOM = NO;
     
    // end of message?
    if (sendingEOM) {
        BOOL didSend = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
         
        if (didSend) {
            // It did, so mark it as sent
            sendingEOM = NO;
        }
        // didn't send, so we'll exit and wait for peripheralManagerIsReadyToUpdateSubscribers to call sendData again
        return;
    }
     
    // We're sending data
    // Is there any left to send?
    if (self.sendDataIndex >= self.dataToSend.length) {
        // No data left.  Do nothing
        return;
    }
     
    // There's data left, so send until the callback fails, or we're done.
    BOOL didSend = YES;
     
    while (didSend) {
        // Work out how big it should be
        NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex;
         
        // Can't be longer than 20 bytes
        if (amountToSend > NOTIFY_MTU) amountToSend = NOTIFY_MTU;
         
        // Copy out the data we want
        NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend];
         
        didSend = [self.peripheralManager updateValue:chunk forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
         
        // If it didn't work, drop out and wait for the callback
        if (!didSend) {
            return;
        }
         
        NSString *stringFromData = [[NSString alloc] initWithData:chunk encoding:NSUTF8StringEncoding];
        NSLog(@"Sent: %@", stringFromData);
         
        // It did send, so update our index
        self.sendDataIndex += amountToSend;
         
        // Was it the last one?
        if (self.sendDataIndex >= self.dataToSend.length) {
             
            // Set this so if the send fails, we'll send it next time
            sendingEOM = YES;
 
            BOOL eomSent = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
             
            if (eomSent) {
                // It sent, we're all done
                sendingEOM = NO;
                NSLog(@"Sent: EOM");
            }
             
            return;
        }
    }
}

 마지막으로 PeripheralManager가 다음 chunk를 전송할 수 있는 준비가 될때 콜백을 구현해주어야 한다. 이 콜백은 전송한 데이터가 모두 완료되었음을 의미하며 - (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral 이다. 

1
2
3
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral {
    [self sendData];
}

인자 앱을 빌드하고 실행해보아라 다음과 같은 화면이 나올것이다.



인자 BTLE프로그래밍에 대한 기본적이 이해를 코드로서 완료하였다.

이 정보를 알아서 잘 사용하길 바란다.


소스코드 다운로드 링크 

CBTutorial-Initial.zip








Posted by 삼스
iOS2014. 5. 19. 19:09




코어블루투스 백그라운드 실행모드

- info.plist에 임의값을 추가하여 속성을 부여 가능

 UIBackgroundModes에 bluetooth-central또는  bluetooth-peripheral를 지정하면 됨.

- 2가지 모드지원

  1. central role

      bluetooth와 low energy peripheral로 통신한다.

  2. peripheral role

      데이터를 공유한다.


 Bluetooth-central 백그라운드 모드

 - 앱이 백그라운드 모드이어도 장치를 찾고(discover) 연결(connect)할 수 있으며 데이터를 탐색하고 연동할 수 있다.

 - CBCentralManagerDelegate와 CBPeripheralDelegate 델리게이트메소드로 앱을 깨울 수 있으며 중요한 central role 이벤트를 처리할수 있게 해준다(연결이나 연결해재, 속성값이 변경되었을때, 그리고 central manager의 상태가 변경되었을 때 등).

 - 백그라운드에서 블루투스동작을 할 때와 포그라운드에서 동작할때가 서러 다르다는것을 인지해야 한다. 특히 장치의 검색과정이 그렇다.

  -> CBCentralManagerScanOptionAllowDuplicatesKey 스캔옵션키는 무시된다. 그리고 여러개의 광고장치의 발견은 하나의 이벤트로 모아진다.

  -> 모든 앱들이 백그라운드에서 장치를 스캐닝한다면 스캐닝 인터벌은 늘어날것이고 그로 인해 장치를 찾는데 오래걸릴것이다.


bluetooth-peripheral 백그라운드모드

info.plist에 UIBackgroundModes키에 bluetooth-peripheral를 셋팅하면 되며 시스템이 등록된 이벤트들이 발생하면 앱을 깨워줄것이다.

추가적으로 연결된 central들로부터의 요청을 처리할 수 있도록 하기 위해 bluetooth framework은 백그라운드 동안 앱의 상태를 공시(advertise)한다. 즉 앱이 포그라운드 상태인지 상태를 체크해야 한다.

 - CBAdvertisementDataLocalNameKey 키는 무시된다. 그리고 페리퍼렁의 로칼네임은 공시되지 않는다.

 - 모든 서비스 UUID들(CBAdvertisementDataServiceUUIDsKey 키에 값으로 저장된)는 "overflow"영역에 위치하며 오로지 하나의 iOS장치(명시적으로 검색하고 있는)에 발견될 수 있다.


어떤 백그라운드 실행모드를 사용할것인가?

 백그라운드모드 한가지나 두가지를 모두 동시에도 사용이 가능하지만 백그라운드로 수행하는 작업은 정확하게 처리하고 정리되어야 한다. 왜냐 하면 배터리사용량때문에 문제가 발생할 수 있기 때문이다.

앱은 블루투스관련 이벤트를 가능한 빨리 처리하고 리턴되어야 하며 다시 대기상태가 될 수 있다. 배터리는 그렇게 절약하는 것이다.

모든 블루투스 백그라운드실행모드를 지원하는 앱은 다음의 단계를 따라야 한다.

  • 앱은 세션기반이어야 하며 사용자에게 블루투스 동작의 시작과 정지를 결정할 수 있는 인터페이스를 제공해야 한다.

  • 꺠어나면 앱은 10초안에 관련 테스크를 모두 마쳐야 한다.안그러면 시스템에 막대한 로드(병목)를 줄수 있으며 앱이 죽을수도 있다.
  • 쓸데없이 깨우는 짓을 하지 않아야 한다. 꼭 필요한 경우에만 앱을 깨울 수 있도록 해야 함.

백그라운드에서 아주 오래걸리는 동작의 수행하기


Some apps may need to use the Core Bluetooth framework to perform long-term actions in the background. As an example, imagine you are developing a home security app for an iOS device that communicates with a door lock (equipped with Bluetooth low energy technology). The app and the lock interact to automatically lock the door when the user leaves home and unlock the door when the user returns—all while the app is in the background. When the user leaves home, the iOS device may eventually become out of range of the lock, causing the connection to the lock to be lost. At this point, the app can simply call theconnectPeripheral:options: method of the CBCentralManagerclass, and because connection requests do not time out, the iOS device will reconnect when the user returns home.

Now imagine that the user is away from home for a few days. If the app is terminated by the system while the user is away, the app will not be able to reconnect to the lock when the user returns home, and the user may not be able to unlock the door. For apps like these, it is critical to be able to continue using Core Bluetooth to perform long-term actions, such as monitoring active and pending connections.

State Preservation and Restoration

Because state preservation and restoration is built in to Core Bluetooth, your app can opt in to this feature to ask the system to preserve the state of your app’s central and peripheral managers and to continue performing certain Bluetooth-related tasks on their behalf, even when your app is no longer running. When one of these tasks completes, the system relaunches your app into the background and gives your app the opportunity to restore its state and to handle the event appropriately. In the case of the home security app described above, the system would monitor the connection request, and re-relaunch the app to handle thecentralManager:didConnectPeripheral: delegate callback when the user returned home and the connection request completed.

Core Bluetooth supports state preservation and restoration for apps that implement the central role, peripheral role, or both. When your app implements the central role and adds support for state preservation and restoration, the system saves the state of your central manager object when the system is about to terminate your app to free up memory (if your app has multiple central managers, you can choose which ones you want the system to keep track of). In particular, for a given CBCentralManager object, the system keeps track of:

  • The services the central manager was scanning for (and any scan options specified when the scan started)

  • The peripherals the central manager was trying to connect to or had already connected to

  • The characteristics the central manager was subscribed to

Apps that implement the peripheral role can likewise take advantage of state preservation and restoration. For CBPeripheralManagerobjects, the system keeps track of:

  • The data the peripheral manager was advertising

  • The services and characteristics the peripheral manager published to the device’s database

  • The centrals that were subscribed to your characteristics’ values

When your app is relaunched into the background by the system (because a peripheral your app was scanning for is discovered, for instance), you can reinstantiate your app’s central and peripheral managers and restore their state. The following section describes in detail how to take advantage of state preservation and restoration in your app.

Adding Support for State Preservation and Restoration

State preservation and restoration in Core Bluetooth is an opt-in feature and requires help from your app to work. You can add support for this feature in your app by following this process:

  1. (Required) Opt in to state preservation and restoration when you allocate and initialize a central or peripheral manager object. This step is described in “Opt In to State Preservation and Restoration.”

  2. (Required) Reinstantiate any central or peripheral manager objects after your app is relaunched by the system. This step is described in “Reinstantiate Your Central and Peripheral Managers.”

  3. (Required) Implement the appropriate restoration delegate method. This step is described in “Implement the Appropriate Restoration Delegate Method.”

  4. (Optional) Update your central and peripheral managers’ initialization process. This step is described in “Update Your Initialization Process.”

Opt In to State Preservation and Restoration

To opt in to the state preservation and restoration feature, simply provide a unique restoration identifier when you allocate and initialize a central or peripheral manager. A restoration identifier is a string that identifies the central or peripheral manager to Core Bluetooth and to your app. The value of the string is significant only to your code, but the presence of this string tells Core Bluetooth that it needs to preserve the state of the tagged object. Core Bluetooth preserves the state of only those objects that have a restoration identifier.

For example, to opt in to state preservation and restoration in an app that uses only one instance of a CBCentralManager object to implement the central role, specify theCBCentralManagerOptionRestoreIdentifierKey initialization option and provide a restoration identifier for the central manager when you allocate and initialize it.

    myCentralManager =
        [[CBCentralManager alloc] initWithDelegate:self queue:nil
         options:@{ CBCentralManagerOptionRestoreIdentifierKey:
         @"myCentralManagerIdentifier" }];

Although the above example does not demonstrate this, you opt in to state preservation and restoration in an app that uses peripheral manager objects in an analogous way: Specify theCBPeripheralManagerOptionRestoreIdentifierKeyinitialization option, and provide a restoration identifier when you allocate and initialize each peripheral manager object.

Reinstantiate Your Central and Peripheral Managers

When your app is relaunched into the background by the system, the first thing you need to do is reinstantiate the appropriate central and peripheral managers with the same restoration identifiers as they had when they were first created. If your app uses only one central or peripheral manager, and that manager exists for the lifetime of your app, there is nothing more you need to do for this step.

If your app uses more than one central or peripheral manager or if it uses a manager that isn’t around for the lifetime of your app, your app needs to know which managers to reinstantiate when it is relaunched by the system. You can access a list of all the restoration identifiers for the manager objects the system was preserving for your app when it was terminated, by using the appropriate launch option keys (UIApplicationLaunchOptionsBluetoothCentralsKey orUIApplicationLaunchOptionsBluetoothPeripheralsKey) when implementing your app delegate’sapplication:didFinishLaunchingWithOptions: method.

For example, when your app is relaunched by system, you can retrieve all the restoration identifiers for the central manager objects the system was preserving for your app, like this:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    NSArray *centralManagerIdentifiers =
        launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
    ...

After you have the list of restoration identifiers, simply loop through it and reinstantiate the appropriate central manager objects.

Implement the Appropriate Restoration Delegate Method

After you have reinstantiated the appropriate central and peripheral managers in your app, restore them by synchronizing their state with the state of the Bluetooth system. To bring your app up to speed with what the system has been doing on its behalf (while it was not running), you must implement the appropriate restoration delegate method. For central managers, implement thecentralManager:willRestoreState: delegate method; for peripheral managers, implement theperipheralManager:willRestoreState: delegate method.

In both of the above delegate methods, the last parameter is a dictionary that contains information about the managers that were preserved at the time the app was terminated. For a list of the available dictionary keys, see the Central Manager State Restoration Options constants in CBCentralManagerDelegate Protocol Reference and thePeripheral_Manager_State_Restoration_Options constants inCBPeripheralManagerDelegate Protocol Reference.

To restore the state of a CBCentralManager object, use the keys to the dictionary that is provided in thecentralManager:willRestoreState: delegate method. As an example, if your central manager object had any active or pending connections at the time your app was terminated, the system continued to monitor them on your app’s behalf. As the following shows, you can use theCBCentralManagerRestoredStatePeripheralsKey dictionary key to get of a list of all the peripherals (represented byCBPeripheral objects) the central manager was connected to or was trying to connect to:

- (void)centralManager:(CBCentralManager *)central
      willRestoreState:(NSDictionary *)state {
 
    NSArray *peripherals =
        state[CBCentralManagerRestoredStatePeripheralsKey];
    ...

What you do with the list of restored peripherals in the above example depends on the use case. For instance, if your app keeps a list of the peripherals the central manager discovers, you may want to add the restored peripherals to that list to keep references to them. As described in “Connecting to a Peripheral Device After You’ve Discovered It,” be sure to set a peripheral’s delegate to ensure that it receives the appropriate callbacks.

You can restore the state of a CBPeripheralManager object in a similar way by using the keys to the dictionary that is provided in theperipheralManager:willRestoreState: delegate method.

Update Your Initialization Process

After you have implemented the previous three required steps, you may want to take a look at updating your central and peripheral managers’ initialization process. Although this is an optional step, it can be important in ensuring that things run smoothly in your app. As an example, your app may have been terminated while it was in the middle of exploring the data of a connected peripheral. When your app is restored with this peripheral, it won’t know how far it made it the discovery process at the time it was terminated. You’ll want to make sure you’re starting from where you left off in the discovery process.

For example, when initializing your app in thecentralManagerDidUpdateState: delegate method, you can find out if you successfully discovered a particular service of a restored peripheral (before your app was terminated), like this:

    NSUInteger serviceUUIDIndex =
        [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,
        NSUInteger index, BOOL *stop) {
            return [obj.UUID isEqual:myServiceUUIDString];
        }];
 
    if (serviceUUIDIndex == NSNotFound) {
        [peripheral discoverServices:@[myServiceUUIDString]];
        ...

As the above example shows, if the system terminated your app before it finished discovering the service, begin the exploring the restored peripheral’s data at that point by calling thediscoverServices:. If your app discovered the service successfully, you can then check to see whether the appropriate characteristics were discovered (and whether you already subscribed to them). By updating your initialization process in this manner, you’ll ensure that you’re calling the right methods at the right time.


Posted by 삼스
iOS2013. 8. 29. 02:10


아이폰 코드공유의 문제점

모두다 알다시피 애플은 개발자에세 아이폰용 프레임웍을 만들 수 있는 기회를 제공하지 않는다. 하지만 많은 경우 서로 다른 프로젝트나 개발자들에게 코드를 배포해야 하는 경우가 있다.

대안

몇가지 코드공유나 배포를 위한 방안이 있다. 

첫번째는 코드자체를 공유 및 배포 가능하게 하는것이다. 이 방법은 XCode가 관련 매커니즘을 제공하지만 개발자가 디펜던시나 헤더파일 경로등을 추가로 해주어야 하는 불편함이 있다.

두번째는 코드가 아니라 static library로 배포하는것이다. 잘 알려진 방법이고 리눅스/유닉스 개발자를 비롯해서 아주 많은 개발자들에게 익숙한 방법이다. 하지만 몇가지 불편함이 있는데.  static library는 C/C++코드로 작성이 되어지고 Objective-C의 property나 category를 사용할 수 없다. category를 static library에 추가하려면 명시적으로 링커옵션을 주어야 한다.

-ObjC

-all_load

개발자가 위 옵션을 깜박하면 런타임에러가 발생하게 된다."unrecognized selector sent to instance"


Static Framework

가장 좋은 방법은 'static framework'라고 생각한다. 이 방법은 컴파일러에게 이 라이브러리가 일반적인 아이폰 프레임웍인것처럼 인식하도록 하는 것이다. 공유하고 싶은 static library가 있다면 아주 쉽게 framework으로 만들 수 있다. 그리고 이런 프레임웍은 개발자에게 프로젝트에 추가하는것 말고 추가로 아무것도 시키지 않는다.


어떻게 만들지??

XCode에서 프레임웍으로 빌드하는 방법을 제공하지 않으므로 XCode MacOS framework template로 빌드해야 한다. 일반적인 프레임웍과 동일한 구조를 가지며 몇가지 추가적인 절차가 있다.

1. simulator와 device용을 하나로 만듦

2. framework bundle에 이전 단계에서 프레임웍 번들로 추가한다.

쉽게 작업하기 위해 쉘스크립트를 사용할것이다. lipo유틸을 사용할것인데 이 유틸은 라이브러리 여러개를 하나로 묶어주는 유틸리티이다. 마지막에 이 파일은 프레임웍번들에 복사되고 전용 링크가 생성될것이다.

FRAMEWORK=”${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework”
lipo \”${BUILD_DIR}/${CONFIGURATION}-iphoneos/libDev.a” “${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib Sim.a” -create -output “${FRAMEWORK}/Versions/Current/${PRODUCT_NAME}”cd “${FRAMEWORK}” && ln -sf “Versions/Current/${PRODUCT_NAME}” ./

위 스크립트는 프로젝트의 빌드페이즈에 스크립트로 추가된다.
위 과정을 거치면 이젠 코드를 포함하는 프레임웤을 쉽게 공유할 수 있다.


Posted by 삼스
iOS2013. 8. 28. 23:01


3rd party library를 사용하다보면 서로 같은 심볼을 사용하게 되어서 아래와 같은 링크에러가 나는 경우가 종종 있다.

ld: duplicate symbol .objc_class_name_CJSONScanner in /Users/myappOne/TapjoyConnect/Frameworks/libTapjoyConnectSimulatorRewardInstall_Ads_Pinch.a(CJSONScanner.o) and /Developer/Projects/BuildOutput/Debug-iphonesimulator/OtherLibrary_d.a(CJSONScanner.o)
이 경우 해결 방법을 정리하겠다.

2개의 3rd party library가 있고 소스가 없는 상황을 가정한다.
libtool, lipo, ar명령으로 라이브러리를 extract하고 recombine한다.

먼저 라이브러리가 어떤 아키텍쳐를 지원하는지 확인한다.

$ lipo -info libTapjoy.a
Architectures in the fat file: libTapjoy.a are: armv6 i386

armv6와 i386를 지원하는것을 알 수 있다. 여기서 armv6를 extract한다.

$ lipo -extract_family armv6 -output libTapjoy-armv6.a libTapjoy.a
$ mkdir armv6
$ cd armv6
$ ar -x ../libTapjoy-armv6.a

같은 방법으로 다른 라이브러리의 동일아키텍쳐를 extract하는데 같은 디렉토리에 한다. 그리고 다음과 같이 다시 recombine한다.

$ libtool -static -o ../lib-armv6.a *.o

아키텍쳐별로 이 과정을 마친 후 다시 lipo로 합친다.

$ cd ..
$ lipo -create -output lib.a lib-armv6.a lib-i386.a




Posted by 삼스
iOS2013. 7. 15. 17:57


#import <Cocoa/Cocoa.h>

@interface NSMutableArray (variadicMethodExample)

- (void) appendObjects:(id) firstObject, ...; // This method takes a nil-terminated list of objects.

@end

@implementation NSMutableArray (variadicMethodExample)

- (void) appendObjects:(id) firstObject, ...
{
id eachObject;
va_list argumentList;
if (firstObject) // The first argument isn't part of the varargs list,
  {                                   // so we'll handle it separately.
  [self addObject: firstObject];
  va_start(argumentList, firstObject); // Start scanning for arguments after firstObject.
  while (eachObject = va_arg(argumentList, id)) // As many times as we can get an argument of type "id"
      [self addObject: eachObject]; // that isn't nil, add it to self's contents.
  va_end(argumentList);
  }
}

@end


Posted by 삼스