背景:
项目中遇到一个需求,服务器返回一个验证码图片的 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];
结果发现 decodeData
和 decodedImage
都有值了,那么问题算是解决了。
原因探讨
那么真的会是苹果爸爸的 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又可以放心的使用苹果爸爸提供的原生方法了。
最后,再次确认了一件事, 苹果爸爸果然不会错啊...