iOS 13 是否有获取设备通知令牌的新方法?

Does iOS 13 has new way of getting device notification token?

所以我的朋友从 OneSignal 收到了这封电子邮件

Due to a change that may occur as part of the upcoming iOS 13 release, you must update to the latest version of the iOS SDK before building your app with Xcode 11. All of OneSignal’s wrapper SDKs including React Native, Unity, and Flutter have been updated as well. The reason for this is that Xcode 11, which is being released alongside iOS 13, breaks a common technique that apps and libraries like OneSignal were using to get a push token for the device. If you do not use our new SDK then new users will not be able to subscribe to notifications from your app.

我对此很好奇。

这是我们在 iOS 12

上获得设备通知令牌的方式
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    var token = ""
    for i in 0..<deviceToken.count {
        token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
    }
    print("Notification token = \(token)")
}

在 iOS 13 上获取它的正确方法是什么? 我应该为我当前正在开发的应用程序采用新方法还是旧方法仍然可以?

你这样做的方式很好,它应该会在 iOS 13 上继续工作。但是有些开发人员喜欢 。要将 Data 转换为 base-16 字符串,他们调用 description,其中 returns 类似于

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

然后他们 trim <> 并删除空格。

在 iOS 13 上 description 调用令牌数据 returns 类似

{ length = 32, bytes = 0xd3d997af 967d1f43 b405374a 13394d2f ... 28f10282 14af515f }

这显然使这条路坏了。

Another example 实施错误(已编辑以包含正确的实施)。

可以在 this thread 中找到更多示例。

您可以使用此方法在 iOS 13 之后获取设备令牌:

Objective-C:

+ (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];
}

Swift 5.0(未测试)

class func string(fromDeviceToken deviceToken: Data?) -> String? {
    let length = deviceToken?.count ?? 0
    if length == 0 {
        return nil
    }
    let buffer = UInt8(deviceToken?.bytes ?? 0)
    var hexString = String(repeating: "[=11=]", count: length * 2)
    for i in 0..<length {
        hexString += String(format: "%02x", buffer[i])
    }
    return hexString
}

取自OneSignal blog

使用deviceToken.debugDescription

func getStringFrom(deviceToken: Data) -> String {
    var token = ""
    for i in 0..<deviceToken.count {
        token += String(format: "%02.2hhx", arguments: [deviceToken[i]])
    }
    return token
}

与 Swift 5 相同的代码,但变体更短一些。在 iOS 13.

验证
func getStringFrom(token:NSData) -> String {
    return token.reduce("") { [=10=] + String(format: "%02.2hhx", ) }
}

你可以看看下面的代码,因为我也遇到了这个问题。下面是iOS13及以上

获取设备令牌的代码
    NSString *str = [NSString stringWithFormat:@"%@", devTokendata]; // devTokendata is NSData
    str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"<" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@">" withString:@""];
    if (@available(iOS 13, *)) {
        str = [self hexadecimalStringFromData:devToken];
    NSLog(@"APNS Token: %@",str);
}

-(NSString *)deviceTokenFromData:(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];
}

在Xamarin.iOS

中正确捕获iOS 13 Device Token
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    //DeviceToken = Regex.Replace(deviceToken.ToString(), "[^0-9a-zA-Z]+", "");
    //Replace the above line whick worked up to iOS12 with the code below:
    byte[] bytes = deviceToken.ToArray<byte>();
    string[] hexArray = bytes.Select(b => b.ToString("x2")).ToArray();
    DeviceToken = string.Join(string.Empty, hexArray);
}

这是这里发生的事情:

  1. 首先我们必须通过调用获取设备令牌中的所有字节 它上面的 ToArray() 方法。
  2. 一旦我们有了 bytes,它是字节数组或 byte[],我们 调用 LINQ Select,它应用一个内部函数,该函数接受每个 字节和 returns 一个 zero-padded 2 位十六进制字符串。 C#可以做到这一点 很好地使用格式说明符 x2。 LINQ Select 函数 returns 一个 IEnumerable,所以很容易调用 ToArray() 来 获取字符串数组或字符串[].
  3. 现在只要在字符串数组上调用 Join() 方法,我们就会得到 一个连接的字符串。

参考:https://dev.to/codeprototype/correctly-capture-ios-13-device-token-in-xamarin-1968

解决方案 2:这也可以正常工作

public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    byte[] result = new byte[deviceToken.Length];
    Marshal.Copy(deviceToken.Bytes, result, 0, (int)deviceToken.Length);
    var token = BitConverter.ToString(result).Replace("-", "");
}

在 C# 中使用 Xamarin 的不错的解决方案:

// In AppDelegate class:
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    var bytes = deviceToken.ToArray();
    var deviceTokenString = string.Concat(bytes.Select(b => $"{b:x2}"));
    // TODO: handle deviceTokenString
}