项目经验iOS开发技术收藏

iOS 中 Base64 字符串转 Image

2017-08-09  本文已影响3875人  黄二瓜

背景:

项目中遇到一个需求,服务器返回一个验证码图片的 base64 字符串,需要转换成 UIImage 显示。

比如 base64 字符串返回的是:

NSString *base64String = "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"

Image to base64 这个网站中转换成图片是 7376
目前iOS通用的方法是:

// 将base64字符串转为NSData
NSData *decodeData = [[NSData alloc]initWithBase64EncodedString:base64String options:(NSDataBase64DecodingIgnoreUnknownCharacters)];
// 将NSData转为UIImage
UIImage *decodedImage = [UIImage imageWithData: decodeData];

遇到的问题:

结果在测试过程中,发现 decodeData 有时候有值,有时候是 nil ,但是 andorid 端却始终没问题。 那么问题应该就是出现在 base64 编码上了

仔细查看关于 initWithBase64EncodedString: options: 的方法。Xcode 右侧的 Quick Help 中写到:

A data object built by Base-64 decoding the provided string. Returns nil if the data object could not be decoded.

再查看关于 initWithBase64EncodedString: options:API 描述:

/* Create an NSData from a Base-64 encoded NSString using the given options. By default, returns nil when the input is not recognized as valid Base-64.
*/
- (nullable instancetype)initWithBase64EncodedString:(NSString *)base64String options:(NSDataBase64DecodingOptions)options NS_AVAILABLE(10_9, 7_0);

即:

如果传入的 base64 字符串不是标准的 base64 字符串的话,会返回nil

但是如果不是标准的 base64 字符串的话,为什么在 Image to base64 中又能转换图片成功呢? 所以个人猜测这是苹果爸爸的bug。 然后在网上找了一个第三方的 Base64编码库 ,用它提供的方法尝试转码:

NSData *decodeData = [NSData dataWithBase64String: base64String];
UIImage *decodedImage = [UIImage imageWithData: decodeData];

结果发现 decodeDatadecodedImage 都有值了,那么问题算是解决了。

原因探讨

那么真的会是苹果爸爸的 bug 么?这么明显的 bug 苹果不可能看不到吧,本着苹果爸爸永远是对的原则,我又仔细看了下相关资料。

果然 stackoverflow 上有人遇到类似的问题 NSData won't accept valid base64 encoded string,排名第一的答案中写到:

Your Base64 string is not valid. It must be padded with = characters to have a length that is a multiple of 4. In your case: "eyJlbWFp....MTM3fQ=="

大概意思就是

你的base64 字符串不是有效的 base64 编码的字符串, 需要在字符串后面补全 = 符号

我又查了下 base64 的编码说明:

Base64编码说明:
Base64编码要求把3个8位字节(38=24)转化为4个6位的字节(46=24,之后在6位的前面补两个0,形成8位一个字节的形式。 如果剩下的字符不足3个字节,则用0填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现1或2个'='。为了保证所输出的编码位可读字符,Base64制定了一个编码表,以便进行统一转换。编码表的大小为2^6=64,这也是Base64名称的由来。

贴心的 phatmann 同学甚至直接给出了代码用于处理 base64字符串未补全的情况

NSData+Base64.m

@interface NSData (Base64)

/**
 Returns a data object initialized with the given Base-64 encoded string.
 @param base64String A Base-64 encoded NSString
 @returns A data object built by Base-64 decoding the provided string. Returns nil if the data object could not be decoded.
 */
- (instancetype) initWithBase64EncodedString:(NSString *)base64String;

/**
 Create a Base-64 encoded NSString from the receiver's contents
 @returns A Base-64 encoded NSString
 */
- (NSString *) base64EncodedString;

@end

NSData+Base64.m

@interface NSString (Base64)

- (NSString *) stringPaddedForBase64;

@end

@implementation NSString (Base64)

- (NSString *) stringPaddedForBase64 {
    NSUInteger paddedLength = self.length + (self.length % 3);
    return [self stringByPaddingToLength:paddedLength withString:@"=" startingAtIndex:0];
}

@end

@implementation NSData (Base64)

- (instancetype) initWithBase64EncodedString:(NSString *)base64String {
    return [self initWithBase64Encoding:[base64String stringPaddedForBase64]];
}

- (NSString *) base64EncodedString {
    return [self base64Encoding];
}

@end

所以, 问题的根源在于服务端返回的 base64 字符串没有严格按照 Base64 编码进行补全, 为了进一步确认。 我找到了接口负责人进行确认,果然后台那边据说是为了兼容 Web 端在返回 base64 字符串时去掉换行符的同时 顺便 去掉了 padded (补全)。 最后,又让接口同事加上补全操作, 我们iOS又可以放心的使用苹果爸爸提供的原生方法了。

最后,再次确认了一件事, 苹果爸爸果然不会错啊...

上一篇下一篇

猜你喜欢

热点阅读