iOS 中 Base64 字符串转 Image

背景:

项目中遇到一个需求,服务器返回一个验证码图片的 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又可以放心的使用苹果爸爸提供的原生方法了。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 嘟哝嘟哝:最近接到一个任务:在客户端动态生成RSA密钥对,然后向服务器发送这个密钥对中的公钥字符串,由服务器进行公...
    TimmyR阅读 7,862评论 19 21
  • /**ios常见的几种加密方法: 普通的加密方法是讲密码进行加密后保存到用户偏好设置( [NSUserDefaul...
    彬至睢阳阅读 2,834评论 0 7
  • 1.数据安全 01数据安全的原则1)在网络上"不允许"传输用户隐私数据的"明文"2.)在本地"不允许"保存用户隐私...
    陈贺阅读 2,107评论 0 2
  • MD5加密 简介 它是用哈希算法加密的。哈希算法是一种摘要算法(是一种能产生特殊输出格式的算法,这种算法的特点是:...
    YANG1220阅读 3,854评论 0 9
  • 感觉自己好久都没有写什么了,昨天编辑部最后一次会议,我终于上台说话了,真的觉得在编辑部的我好怂啊!我知道因为什么,...
    沅曦阅读 206评论 0 0