iOS2015.08.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.05.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.04.03 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.05.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