IT_Programming/Objective-C · Swift · iOS

[펌] iOS13 앱 푸시 장치 토큰(deviceToken) 관련 이슈 | iOS 13에서 푸시토큰 확인 방법

JJun ™ 2020. 1. 2. 23:02



 * 출처

 : https://velog.io/@gwangyonglee/iOS13-앱-푸시-장치-토큰deviceToken-관련-이슈

 : https://www.mrlatte.net/code/2019/10/31/ios13-push-token.html

 : https://stackoverflow.com/q/8798725




iOS13 앱 푸시 장치 토큰(deviceToken) 관련 이슈





안녕하세요.
이번 글에서는 iOS13 환경에서 deviceToken을 가져올 때 달라진 점에 대해서 공유하려고 합니다.

달라진 점

iOS12 환경에서는 deviceToken을 가져오는 메소드에서 deviceToken 파라미터 데이터 형식은

<124686a5 556a72ca d808f572 00c323b9 3eff9285 92445590 3225757d b83997ba>

iOS13 환경에서는

{length=32,bytes=0x91e27cd9ddf20c69da8fa650e2a06f55...e60b100d99d60adf}

로 변경되었습니다.

대응

iOS12 환경에서 deviceToken 데이터를 String으로 아래의 방법으로 가공하였다면,

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    var deviceTokenString = String(format: "%@", deviceToken as CVarArg)
    deviceTokenString = deviceTokenString.replacingOccurrences(of: "<", with: "")
    deviceTokenString = deviceTokenString.replacingOccurrences(of: ">", with: "")
    deviceTokenString = deviceTokenString.replacingOccurrences(of: " ", with: "")
    ...
  }

iOS13 환경에서는 Base16-encoded / hexadecimal string 표현으로 변환해야 합니다.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined()
    ...
}

map메소드를 이용하여 Data의 각 바이트를 16진수로 String으로 만들고, joined()을 이용하여 단일 문자열로 변환하는 방식입니다.

마무리

iOS12 환경에서는 푸시기능이 잘 작동하였는데, iOS13버전 대응하면서 디바이스 토큰 데이터 구조가 변경되었고, 갑작스럽게 대응하느라 미리 대응했으면 좋았을텐데에 대한 아쉬움이 남네요. iOS14버전 부터는 미리 미리 대응하는 습관을 길러야겠네요:)









iOS 13에서 푸시토큰 확인 방법



iOS 13에서 푸시토큰이 iOS 13 이하 버전에서와 다르게 확인되는 경우가 있어서 해결 방법을 공유한다.

Problem

기존 iOS의 푸시토큰을 확인할 때 아래와 같이 인터넷에서 공유된 잘못된 방법을 많이 사용했었다.

NSString *token = [[[[deviceToken description]
                    stringByReplacingOccurrencesOfString:@" " withString:@""]
                    stringByReplacingOccurrencesOfString:@"<" withString:@""]
                    stringByReplacingOccurrencesOfString:@">" withString:@""];

iOS 13 이전 버전까지는 보통 아래와 같은 형식으로 반환되어 <> 그리고 공백을 제거해서 사용했지만 적절한 방법이라고 할 수 없다.

<12345678 asdfghjk 45678900 dfb16ddd 12345678 45678900 45678900 asdfghjk>

iOS 13부터는 description를 사용할 경우 푸시토큰이 아래와 같이 반환되는경우가 있어서 문제가 될 수 있다. 심지어 bytes부분은 중간에 생략되기까지하여 서버에 저장할 경우 클라이언트 수정 없이 이 데이터만을 이용해서 문제를 해결하기도 힘들다.

{length=32,bytes=0x16406f0b18032ca902ed1117edd3bf6b...6221947229b7ca18}

Solution

이것은 apple의 갑작스러운 변경이 원인이라기 보다는 푸시 토큰을 가져오는 방법을 잘못 사용하고 있는것이 문제의 원인으로 아래와 같이 변경해야 한다.

+ (NSString *)stringFromDeviceToken:(NSData *)deviceToken {
    NSUInteger length = deviceToken.length;
    if (length == 0) {
        return nil;
    }
    const unsigned char *buffer = deviceToken.bytes;
    NSMutableString *hexString  = [NSMutableString stringWithCapacity:(length * 2)];
    for (int i = 0; i < length; ++i) {
        [hexString appendFormat:@"%02x", buffer[i]];
    }
    return [hexString copy];
}

이상한점은 iOS 13이라고 항상 발생하는 것은 아니고 아래와 같은 조합의 경우 발생했음을 참고로 공유한다.

CFNetwork/1098.4 Darwin/19.0.0
CFNetwork/1098.7 Darwin/19.0.0
CFNetwork/1107.1 Darwin/19.0.0
CFNetwork/1115 Darwin/19.0.0

iOS 13인 아래의 경우는 발생하지 않았다.

CFNetwork/1120 Darwin/19.0.0







Get device token for push notification



NOTE: The below solution no longer works on iOS 13+ devices - it will return garbage data.

Please use following code instead:

+ (NSString *)hexadecimalStringFromData:(NSData *)data
{
  NSUInteger dataLength = data.length;
  if (dataLength == 0) {
    return nil;
  }
  const unsigned char *dataBuffer = (const unsigned char *)data.bytes;
  NSMutableString *hexString  = [NSMutableString stringWithCapacity:(dataLength * 2)];
  for (int i = 0; i < dataLength; ++i) {
    [hexString appendFormat:@"%02x", dataBuffer[i]];
  }
  return [hexString copy];
}

Solution that worked prior to iOS 13:

Objective-C

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 
{
    NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"this will return '32 bytes' in iOS 13+ rather than the token", token);
} 

Swift 3.0

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
    let tokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
    print("this will return '32 bytes' in iOS 13+ rather than the token \(tokenString)")
}