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. 使用步骤

  1. 传入 XML 数据创建解析器
  2. 设置代理
  3. 调用 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,使用前需:

  1. 引入 libxml2.dylib(或 libxml2.tbd)
  2. 在 Build Settings → Header Search Paths 添加 $(SDKROOT)/usr/include/libxml2
  3. 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,调用方法会崩溃,需判空。

posted @ 2015-08-22 20:30  Mr.陳  阅读(1174)  评论(0)    收藏  举报