iOS开发基础41-网络编程之JSON和XML
JSON 与 XML 数据解析深度解析:NSJSONSerialization、NSXMLParser、GDataXML 与 Swift Codable
本文系统梳理 iOS 数据解析:JSON 格式与 OC 对象映射、NSJSONSerialization 解析与生成、字典转模型(手动/MJExtension)、XML 语法与解析方式、NSXMLParser(SAX)完整代理解析、GDataXML(DOM)解析、JSON 与 XML 对比,以及 Swift Codable 现代解析方案。
一、JSON 概述
1. JSON 简介
JSON(JavaScript Object Notation)是一种轻量级数据交换格式,易于人阅读和编写,也易于机器解析和生成。服务器返回的数据通常是 JSON 或 XML 格式(文件下载除外)。
JSON 格式类似于 OC 中的字典和数组:
{
"name": "jack",
"age": 10
}
{
"names": ["jack", "rose", "jim"]
}
标准 JSON 要求键名必须用双引号,字符串也必须用双引号(不能用单引号)。
2. JSON 与 Objective-C 对象映射
| JSON 类型 | OC 类型 | 示例 |
|---|---|---|
{} 对象 |
NSDictionary |
@{} |
[] 数组 |
NSArray |
@[] |
"jack" 字符串 |
NSString |
@"jack" |
10 整数 |
NSNumber |
@10 |
10.5 浮点数 |
NSNumber |
@10.5 |
true 布尔 |
NSNumber |
@YES / @1 |
false 布尔 |
NSNumber |
@NO / @0 |
null 空 |
NSNull |
[NSNull null] |
JSON 的
null解析为NSNull(单例对象),不是nil。从字典中取nil不会崩溃,但取NSNull后调用方法会崩溃(unrecognized selector),需做判空处理。
3. JSON 解析方案
| 方案 | 说明 |
|---|---|
| NSJSONSerialization(苹果原生) | 性能最好,iOS 5+ 支持,推荐 |
| JSONKit / SBJson / TouchJSON(第三方) | 早期第三方库,NSJSONSerialization 出现后基本不再使用 |
新项目直接使用
NSJSONSerialization,无需引入第三方 JSON 库。
二、NSJSONSerialization 解析与生成
1. JSON 数据 → OC 对象
+ (id)JSONObjectWithData:(NSData *)data
options:(NSJSONReadingOptions)opt
error:(NSError **)error;
NSJSONReadingOptions:
| 选项 | 说明 |
|---|---|
NSJSONReadingMutableContainers |
解析出的数组和字典是可变的(NSMutableArray/NSMutableDictionary) |
NSJSONReadingMutableLeaves |
解析出的字符串是可变的(iOS 7 后被忽略,无效) |
NSJSONReadingAllowFragments |
允许顶层不是数组或字典(如单独的字符串、数字) |
示例:
NSURL *url = [NSURL URLWithString:@"https://api.example.com/login?username=test&pwd=123"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data || error) return;
NSError *parseError = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&parseError];
if (parseError) {
NSLog(@"解析失败: %@", parseError);
return;
}
NSLog(@"解析结果: %@", dict);
}] resume];
2. OC 对象 → JSON 数据
+ (NSData *)dataWithJSONObject:(id)obj
options:(NSJSONWritingOptions)opt
error:(NSError **)error;
NSJSONWritingOptions:
| 选项 | 说明 |
|---|---|
NSJSONWritingPrettyPrinted |
格式化输出(带缩进和换行,便于阅读) |
NSJSONWritingSortedKeys |
键名按字母排序输出(iOS 11+) |
NSJSONWritingFragmentsAllowed |
允许顶层不是数组/字典(iOS 11+) |
NSJSONWritingWithoutEscapingSlashes |
不转义斜杠 /(iOS 13+) |
示例:
NSDictionary *dict = @{
@"name": @"lnj",
@"age": @10,
@"gender": @"man"
};
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dict
options:NSJSONWritingPrettyPrinted
error:&error];
if (!error) {
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", jsonString);
}
可被序列化为 JSON 的 OC 对象必须满足:顶层是 NSArray 或 NSDictionary(除非使用 FragmentsAllowed);所有对象是 NSString/NSNumber/NSArray/NSDictionary/NSNull;字典的键必须是 NSString;NSNumber 不能是 NaN 或无穷大。
3. JSON 调试工具
- 在线格式化:JSON.cn、OSChina 在线格式化
- 浏览器插件:JSONView、FeHelper
- Xcode 中可将解析后的字典/数组写入 plist 文件查看
三、JSON 字典转模型
1. 手动转模型
// 模型类
@interface XMGVideo : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *length;
@property (nonatomic, copy) NSString *image;
+ (instancetype)videoWithDict:(NSDictionary *)dict;
@end
@implementation XMGVideo
+ (instancetype)videoWithDict:(NSDictionary *)dict {
XMGVideo *video = [[self alloc] init];
[video setValuesForKeysWithDictionary:dict]; // KVC 批量赋值
return video;
}
@end
// 解析并转模型
NSArray *dictArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSMutableArray *models = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
[models addObject:[XMGVideo videoWithDict:dict]];
}
使用 KVC
setValuesForKeysWithDictionary:时,模型属性名必须与 JSON 键名一致;若不一致需重写setValue:forUndefinedKey:或手动映射。
2. MJExtension(推荐)
MJExtension 无需继承任何基类,使用简单,支持键名映射、嵌套模型、数组转模型等。
#import "MJExtension.h"
// 键名映射(属性名 ID 对应 JSON 中的 id)
[XMGVideo setupReplacedKeyFromPropertyName:^NSDictionary *{
return @{@"ID": @"id"};
}];
// 字典数组转模型数组
NSArray *videos = [XMGVideo objectArrayWithKeyValuesArray:dict[@"videos"]];
3. 其他字典转模型框架
| 框架 | 特点 |
|---|---|
| Mantle | 模型需继承 MTLModel,支持归档和序列化 |
| JSONModel | 模型需继承 JSONModel,自动校验类型 |
| MJExtension | 无需继承,性能优秀,使用简单,推荐 |
四、XML 概述
1. XML 简介
XML(Extensible Markup Language,可扩展标记语言)是一种用于数据交互的标记语言,也称为 XML 文档。
<?xml version="1.0" encoding="UTF-8"?>
<videos>
<video name="小黄人 第01部" length="30" />
<video name="小黄人 第02部" length="19" />
<video name="小黄人 第03部" length="33" />
</videos>
2. XML 语法
- 文档声明:位于最前面,声明版本和编码:
<?xml version="1.0" encoding="UTF-8"?> - 元素(Element):包含开始标签和结束标签。
- 有内容:
<video>小黄人</video> - 无内容(自闭合):
<video />
- 有内容:
- 属性(Attribute):元素可拥有多个属性:
<video name="小黄人" length="30" /> - CDATA:不被解析器解析的原始数据块,用于包含特殊字符:
<description><![CDATA[这里的内容 < > & 不会被解析]]></description>
XML 中的空格和换行符会被视为元素内容,解析时需注意 trim。
3. XML 解析方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| DOM | 一次性加载整个 XML 文档到内存,形成树结构,可随机访问节点 | 小文件、需要频繁访问节点 |
| SAX | 按顺序逐个元素解析,基于事件回调,内存占用低 | 大文件、流式解析、只需一次遍历 |
4. iOS XML 解析工具
| 工具 | 方式 | 说明 |
|---|---|---|
| NSXMLParser(苹果原生) | SAX | 使用简单,基于代理回调,推荐 |
| libxml2 | DOM/SAX | C 语言库,包含在 iOS SDK 中,功能强大但使用复杂 |
| GDataXML | DOM | Google 基于 libxml2 封装,面向对象,但已多年未更新 |
五、NSXMLParser(SAX 解析)
1. 使用步骤
- 传入 XML 数据创建解析器
- 设置代理
- 调用
parse开始解析
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
2. NSXMLParserDelegate 代理方法
| 方法 | 调用时机 |
|---|---|
parserDidStartDocument: |
扫描到文档开始 |
parserDidEndDocument: |
扫描到文档结束 |
parser:didStartElement:namespaceURI:qualifiedName:attributes: |
扫描到元素开始(attributes 保存元素属性) |
parser:foundCharacters: |
扫描到元素内容(可能调用多次,需拼接) |
parser:didEndElement:namespaceURI:qualifiedName: |
扫描到元素结束 |
parser:parseErrorOccurred: |
解析出错 |
3. 完整解析示例
@interface ViewController () <NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableArray *videos;
@property (nonatomic, strong) XMGVideo *currentVideo;
@property (nonatomic, strong) NSMutableString *elementContent; // 拼接元素内容
@end
@implementation ViewController
- (void)parseXML:(NSData *)data {
self.videos = [NSMutableArray array];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
}
#pragma mark - NSXMLParserDelegate
- (void)parserDidStartDocument:(NSXMLParser *)parser {
NSLog(@"开始解析文档");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
if ([elementName isEqualToString:@"video"]) {
// 扫描到 video 元素开始,创建模型
self.currentVideo = [[XMGVideo alloc] init];
self.currentVideo.name = attributeDict[@"name"];
self.currentVideo.length = attributeDict[@"length"];
}
// 准备拼接元素内容(如果元素有子文本内容)
self.elementContent = [NSMutableString string];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
// 拼接元素内容(可能调用多次)
[self.elementContent appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:@"video"]) {
// 扫描到 video 元素结束,加入数组
[self.videos addObject:self.currentVideo];
self.currentVideo = nil;
} else if ([elementName isEqualToString:@"name"]) {
// 如果 name 是子元素而非属性,在此赋值
// self.currentVideo.name = [self.elementContent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
self.elementContent = nil;
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(@"解析完成,共 %lu 条", (unsigned long)self.videos.count);
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSLog(@"解析出错: %@", parseError);
}
@end
foundCharacters:可能在一个元素内被调用多次(内容较长时分段返回),必须用可变字符串拼接,不能直接赋值。元素内容可能包含前后空白,需用stringByTrimmingCharactersInSet:去除。
六、GDataXML(DOM 解析)
1. 使用前配置
GDataXML 基于 libxml2,使用前需:
- 引入
libxml2.dylib(或libxml2.tbd) - 在 Build Settings → Header Search Paths 添加
$(SDKROOT)/usr/include/libxml2 - GDataXML.m 是 MRC 代码,在 Compile Sources 中为其添加
-fno-objc-arc标记
2. 基本用法
#import "GDataXMLNode.h"
// 1. 加载整个 XML 文档
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:data options:0 error:nil];
// 2. 获取根节点
GDataXMLElement *root = doc.rootElement;
// 3. 获取所有名为 video 的子节点
NSArray *elements = [root elementsForName:@"video"];
// 4. 遍历节点,获取属性
NSMutableArray *videos = [NSMutableArray array];
for (GDataXMLElement *element in elements) {
XMGVideo *video = [[XMGVideo alloc] init];
video.name = [[element attributeForName:@"name"] stringValue];
video.length = [[element attributeForName:@"length"] stringValue];
[videos addObject:video];
}
// 5. 获取元素文本内容(如果有)
// NSString *text = [element stringValue];
GDataXML 已多年未更新,且配置繁琐(需引入 libxml2、设置 MRC 标记),现代项目推荐使用 NSXMLParser 或直接用 JSON。
七、JSON 与 XML 对比
| 对比项 | JSON | XML |
|---|---|---|
| 格式 | 键值对,结构简洁 | 标签嵌套,结构冗长 |
| 数据体积 | 小(无重复标签) | 大(开闭标签占用空间) |
| 解析速度 | 快 | 相对较慢 |
| 可读性 | 好 | 好(结构清晰) |
| 数据类型 | 支持字符串/数字/布尔/null/数组/对象 | 全部为字符串,需手动转换类型 |
| 命名空间 | 不支持 | 支持 |
| 注释 | 不支持 | 支持 <!-- --> |
| CDATA | 不支持 | 支持 |
| 适用场景 | 移动端 API、配置文件、数据交换 | 复杂文档、配置(如 Android layout、plist)、Web Service |
移动端服务器接口普遍使用 JSON(体积小、解析快、与字典/数组天然映射)。XML 多用于配置文件和复杂文档结构。
八、Swift 版本对照
1. JSON 解析(JSONDecoder / Codable,推荐)
// 定义模型,遵守 Codable 协议
struct Video: Codable {
let name: String
let length: String
let image: String
// 若属性名与 JSON 键名不一致,使用 CodingKeys 映射
enum CodingKeys: String, CodingKey {
case name, length, image
// case id = "ID" // 映射
}
}
// 解析 JSON 数据
let data: Data = ... // 网络返回的数据
do {
let videos = try JSONDecoder().decode([Video].self, from: data)
print(videos)
} catch {
print("解析失败: \(error)")
}
// 字典(顶层是对象)
struct Response: Codable {
let videos: [Video]
}
let response = try JSONDecoder().decode(Response.self, from: data)
2. JSON 生成(JSONEncoder)
let video = Video(name: "小黄人", length: "30", image: "xxx.jpg")
do {
let data = try JSONEncoder().encode(video)
let jsonString = String(data: data, encoding: .utf8)
print(jsonString ?? "")
} catch {
print("编码失败: \(error)")
}
3. 传统 JSONSerialization(Swift)
// JSON → 对象
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
if let dict = json as? [String: Any] {
print(dict)
}
// 对象 → JSON
let dict: [String: Any] = ["name": "lnj", "age": 10]
let data = try? JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
4. XML 解析(XMLParser,Swift)
class VideoParser: NSObject, XMLParserDelegate {
var videos: [Video] = []
private var currentVideo: Video?
private var elementContent = ""
func parse(data: Data) {
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
}
func parser(_ parser: XMLParser, didStartElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?,
attributes attributeDict: [String: String] = [:]) {
if elementName == "video" {
currentVideo = Video(name: attributeDict["name"] ?? "",
length: attributeDict["length"] ?? "",
image: "")
}
elementContent = ""
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
elementContent += string
}
func parser(_ parser: XMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "video", let video = currentVideo {
videos.append(video)
currentVideo = nil
}
}
}
九、总结
- JSON:轻量级数据交换格式,
{}→NSDictionary、[]→NSArray、字符串→NSString、数字/布尔→NSNumber、null→NSNull;移动端 API 主流格式。 - NSJSONSerialization:苹果原生 JSON 解析/生成工具,性能最好;
JSONObjectWithData:options:error:解析,dataWithJSONObject:options:error:生成;MutableContainers 可变容器、AllowFragments 允许顶层非数组/字典、PrettyPrinted 格式化输出、SortedKeys 键排序(iOS 11+)。 - 字典转模型:手动 KVC
setValuesForKeysWithDictionary:、MJExtension(无需继承,推荐)、Mantle/JSONModel(需继承基类);Swift 推荐Codable协议 +JSONDecoder。 - XML:可扩展标记语言,文档声明 + 元素 + 属性 + CDATA;空格换行会被视为内容。
- XML 解析方式:DOM(一次性加载,适合小文件)、SAX(顺序事件回调,适合大文件)。
- NSXMLParser:苹果原生 SAX 解析,代理方法:didStartElement(元素开始,含属性)、foundCharacters(元素内容,需拼接)、didEndElement(元素结束)、parseErrorOccurred(出错);foundCharacters 可能调用多次必须拼接,内容需 trim 空白。
- GDataXML:Google 基于 libxml2 的 DOM 封装,需引入 libxml2、设置 Header Search Paths、GDataXML.m 加 -fno-objc-arc;已多年未更新,现代项目少用。
- JSON vs XML:JSON 体积小、解析快、类型丰富,适合移动端 API;XML 支持命名空间/注释/CDATA/复杂文档结构,适合配置和文档;移动端普遍使用 JSON。
- NSNull 注意:JSON
null解析为 NSNull,不是 nil,调用方法会崩溃,需判空。

浙公网安备 33010602011771号