在網(wǎng)絡開發(fā)中,從服務器獲取的二進制數(shù)據(jù)包括:
> html
> 圖片
> 視頻
> 音頻
> zip 等
除了以上文件格式之外,最常見的數(shù)據(jù)莫過于JSON,偶爾也會有XML。
無論 JSON還是 XML都是一種特殊格式的字符串,按照特定的規(guī)則描述數(shù)據(jù)結(jié)構(gòu)。
一、準備工作--->安裝Apache服務器
> 參考備課筆記/筆記/02-Apache服務器安裝筆記.m
二、JSON
1、JSON格式介紹
# JSON本質(zhì)上,就是一個“特殊格式”的字符串。
> JSON是網(wǎng)絡上用來傳輸數(shù)據(jù)使用最廣泛的數(shù)據(jù)格式,沒有之一。
> JSON出生草根,是Javascript的子集,是Javascript的SON,專門用來描述數(shù)據(jù)結(jié)構(gòu)。
> Javascrpit 是做網(wǎng)頁開發(fā)使用的一種"腳本"語言
> Javascrpit & Java沒有任何關(guān)系! 就好像 雷鋒 和 雷峰塔 之間的關(guān)系。
#參考網(wǎng)站:http//www.w3cschool.cc。w3c 是國際互聯(lián)網(wǎng)組織的縮寫。
2、JSON的語法規(guī)則
> 數(shù)據(jù)以 key/value 鍵值對表示
> 數(shù)據(jù)由逗號分割
> 花括號保存對象,對應OC的NSDictionary。
> 方括號保存數(shù)組,對應OC的NSArray。
3> JSON值
* 數(shù)字(整數(shù)或浮點數(shù))
* 字符串(在雙引號中)
* 邏輯值(true 或 false)
* 數(shù)組(在方括號中)
* null
4、JSON解析--->解析天氣預報
> 序列化:在向服務器發(fā)送數(shù)據(jù)之前,將 NSArray/NSDcitionary 轉(zhuǎn)成二進制的過程。
> 反序列化:在從服務器接收到數(shù)據(jù)之后,將二進制數(shù)據(jù)轉(zhuǎn)換成NSArray/NSDcitionary的過程。
/**
*? 解析天氣預報
*/
- (void)loadData{
// 創(chuàng)建 url
NSURL *url = [NSURL URLWithString:@"http://www.weather.com.cn/adat/sk/101010100.html"];
// 創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 發(fā)送異步請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"data = %@",data);
/*
> 序列化:在向服務器發(fā)送數(shù)據(jù)之前,將 NSArray/NSDcitionary 轉(zhuǎn)成二進制的過程。
> 反序列化:在從服務器接收到數(shù)據(jù)之后,將二進制數(shù)據(jù)轉(zhuǎn)換成NSArray/NSDcitionary的過程。
*/
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
/*
NSJSONReadingMutableContainers = (1UL << 0), 容器可變
NSJSONReadingMutableLeaves = (1UL << 1), 葉子可變
NSJSONReadingAllowFragments(碎片) = (1UL << 2) 頂級節(jié)點可以既不是數(shù)組也不是字典
*/
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@市 溫度 %@ 風向 %@ 風力 %@ ",dict[@"weatherinfo"][@"city"],dict[@"weatherinfo"][@"temp"],dict[@"weatherinfo"][@"WD"],dict[@"weatherinfo"][@"WS"]);
}];
}
5、JSON解析第三方框架
> 常見的JSON解析第三方框架
* JSONKit(最快)
* SBJson
* TouchJSON
# 以上三個框架的性能依次降低。
> 介紹JSONKit第三方框架的目的
* JSON的解析并不是表面上這么簡單
* 官方說JSONKit 比蘋果原生的 JSON 解析速度要快。
* JSONKit在很多老的項目中仍然在使用
#知道JSONKit 說明我們是資深的iOS程序員。
* JSONKit已經(jīng)在2012年停止更新,適用于iOS5.0之前的版本開發(fā)使用。(蘋果原生的NSJSONSerialization是iOS5.0之后出現(xiàn))
* 了解 ARC & MRC 混編的方法
> JSONKit解析和性能測試
使用步驟:
* 下載框架:http://github.com/johnezang/JSONKit
* 導入框架文件:JSONKit.h & JSONKit.m
* 設置 MRC 標記
> 選擇"項目"-"Build Phases"-"Compile Sources"
> 找到 JSONKit.m 并且在 Compiler Flags 中添加 -fno-objc-arc。
# -fno-objc-arc 告訴編譯器,編譯 JSONKit.m 時不使用ARC。
* 修改錯誤
> 利用自動修復功能,修改兩處isa的錯誤。
* 反序列化
> id? result =? [[JSONDecoder decoder] objectWithData:data];
使用代碼和測試如下:
int largeNumber = 100 * 1000;
- (void)loadData{
// 創(chuàng)建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 發(fā)送異步請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
/*
官方說JSONKit 比蘋果原生的 JSON 解析速度要快。我們可以通過測試驗證下
JSONKit:4.907761 秒
官方:0.299994 秒
通過測試得出:蘋果原生的JSON解析速度遠遠比JSONKit快。
如果工作中遇到使用第三方框架解析 JSON 的舊項目,建議替換為蘋果原生的。
替換的原則:看項目是否要支持 iOS5.0之前的版本。如果不需要果斷替換。
替換步驟:
1> 備份(給自己一個后悔的機會)
2> 刪除 JSONKit.h & .m 文件
3> 哪里出錯改哪里
*/
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int index = 0; index < largeNumber; index ++) {
//? ? ? ? ? ? NSMutableDictionary *result =? [[JSONDecoder decoder] objectWithData:data];
NSMutableDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
}
NSLog(@"-----endTime = %f",CFAbsoluteTimeGetCurrent() - start);
//JKDictionary 其實就是 NSMutableDictionary 的 子類
//? ? ? ? NSLog(@"------%@,%@",result,result[@"message"]);
}];
}
6、PList解析
> PList 主要在蘋果開發(fā)中常用,很多后臺并不會返回 Plist 的格式數(shù)據(jù)。有關(guān) PList的反序列化知道即可。
> 代碼如下:
/**
*? 解析plist 數(shù)據(jù)
*/
- (void)loadData{
// 創(chuàng)建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/videos.plist"];
// 創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 發(fā)送異步請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
/**
參數(shù):
- data:要反序列化的二進制數(shù)據(jù)
- options:選項,位移枚舉類型
NSPropertyListImmutable = 0? 不可變,
NSPropertyListMutableContainers = 1? 容器可變,
NSPropertyListMutableContainersAndLeaves = 2? 容器和葉子可變
- format:如果不希望知道格式,傳人 NULL 即可
- error:錯誤信息
*/
id result = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:nil];
NSLog(@"%@? %@",result,[result class]);
}];
}
三、XML解析
1、XML介紹
> XML的英文全稱(eXtensible Markup Language),中文名稱可擴展標記語言。
> XML的特點:語法規(guī)則很簡單,且很有邏輯。
#出身名門,w3c制定,微軟和 IBM 曾經(jīng)共同大力推薦過的數(shù)據(jù)格式。
> 專門設計用來傳輸和存儲數(shù)據(jù)
> 與HTML的區(qū)別:HTML的標記不是所有的都需要成對出現(xiàn),
XML要求所有的標記必須成對出現(xiàn)。
HTML標記不區(qū)分大小寫,它則大小敏感,即區(qū)分大小寫。
2、XML解析的方式
> DOM解析
* 是在MAC使用的解析方式。
* 內(nèi)存消耗極大,不適用于手機。因此蘋果沒有提供手機上DOM解析。
* iPhone無法直接使用DOM方式解析XML。
> SAX解析
* 是只讀的方式,從上向下的方式解析
* 是蘋果提供的解析方式
* 使用 NSXMLParser 通過 代理 實現(xiàn)解析。
3、SAX解析步驟
1> 開始文檔--準備工作
2> 開始"節(jié)點"
3> 發(fā)現(xiàn)節(jié)點內(nèi)部的內(nèi)容,每一個節(jié)點,可能需要多次才能找完
4> 結(jié)束"節(jié)點"
5> 結(jié)束文檔--解析結(jié)束
#以上步驟,2,3,4會不斷循環(huán),一直到所有解析完成。
4、代碼演示XML的解析。
/**
*? 加載 xml 數(shù)據(jù)
*/
- (void)loadData{
// 創(chuàng)建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/videos.xml"];
// 創(chuàng)建請求對象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 發(fā)送異步請求(由于解析 xml 是一個耗時的過程,所以新開啟隊列)
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 1.創(chuàng)建 xml 解析器
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// 2.設置代理來解析
parser.delegate = self;
// 3.解析器開始解析 -- 一旦開始解析,后續(xù)的解析工作全部交給代理完成
[parser parse];
}];
}
> 通過NSLog打印,確認XML解析思路
#pragma mark? - NSXMLParserDelegate 代理方法
/**
*? 開始解析
*/
- (void)parserDidStartDocument:(NSXMLParser *)parser {
NSLog(@"1、開始解析");
}
/**
*? 解析到一個開始節(jié)點調(diào)用
*/
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
NSLog(@"2、開始節(jié)點 %@ %@",elementName,attributeDict);
}
/**
*? 解析到節(jié)點文字調(diào)用? 一個節(jié)點的內(nèi)容可以會調(diào)用多次該方法
*/
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
NSLog(@"3、======> %@",string);
}
/**
*? 解析到結(jié)束節(jié)點調(diào)用? elementName 沒有反斜線 /
*/
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(@"4、結(jié)束節(jié)點------%@",elementName);
}
/**
*? 解析結(jié)束調(diào)用
*/
- (void)parserDidEndDocument:(NSXMLParser *)parser{
NSLog(@"5、結(jié)束文檔");
}
5、畫XML解析思維導圖
6、XML解析代碼實現(xiàn)
#pragma mark? - NSXMLParserDelegate 代理方法
/**
*? 開始解析
*/
- (void)parserDidStartDocument:(NSXMLParser *)parser {
// 清空數(shù)組
[self.videos removeAllObjects];
}
/**
*? 解析到一個開始節(jié)點調(diào)用
*/
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:@"video"]) {
// 創(chuàng)建 video 模型
self.currentVideo = [[Video alloc] init];
// 設置 id
self.currentVideo.videoId = @([attributeDict[@"videoId"] integerValue]);
}
// 清空當前正在拼接的字符內(nèi)容
[self.elementString setString:@""];
// 錯誤寫法
//self.elementString = @"";
}
/**
*? 解析到節(jié)點文字調(diào)用? 一個節(jié)點的內(nèi)容可以會調(diào)用多次該方法
*/
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self.elementString appendString:string];
}
/**
*? 解析到結(jié)束節(jié)點調(diào)用? elementName 沒有反斜線 /
*/
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// 如果 elementName 是 video,則添加到數(shù)組中
/* 最笨方法
if ([elementName isEqualToString:@"video"]){
[self.videos addObject:self.currentVideo];
} else if ([elementName isEqualToString:@"name"]){
self.currentVideo.name = self.elementString;
} else if ([elementName isEqualToString:@"length"]){
self.currentVideo.length = @(self.elementString.integerValue);
} else if ([elementName isEqualToString:@"desc"]){
self.currentVideo.desc = self.elementString;
} else if ([elementName isEqualToString:@"imageURL"]) {
self.currentVideo.imageURL = self.elementString;
} else if ([elementName isEqualToString:@"videoURL"]){
self.currentVideo.videoURL = self.elementString;
} else if ([elementName isEqualToString:@"teacher"]){
self.currentVideo.teacher = self.elementString;
}
*/
// 如果是 name,length,desc....則設置屬性
// 簡便方法
if ([elementName isEqualToString:@"video"]){
[self.videos addObject:self.currentVideo];
} else if(![elementName isEqualToString:@"videos"]){
//使用 KVC 設置屬性, KVC是一種以字符串形式間接設置數(shù)據(jù)的方式
[self.currentVideo setValue:self.elementString forKey:elementName];
}
}
/**
*? 解析結(jié)束調(diào)用
*/
- (void)parserDidEndDocument:(NSXMLParser *)parser{
NSLog(@"%@",self.videos);
}
四、tableView展示數(shù)據(jù)
1、展示視頻數(shù)據(jù)--Storyboard自定義cell
2、設置時間格式
> HMVideo模型中提供屬性
//視頻時長
@property (nonatomic, strong,readonly) NSString *time;
# 計算時間方式有兩種
# 方式一:重寫time屬性getter方法
- (NSString *)time {
int len = self.length.intValue;
return [NSString stringWithFormat:@"%02d:%02d:%02d", len / 3600, (len % 3600) / 60, (len % 60)];
}
# 方式二:
- (void)setLength:(NSNumber *)length {
_length = length;
int len = self.length.intValue;
_time = [NSString stringWithFormat:@"%02d:%02d:%02d", len / 3600, (len % 3600) / 60, (len % 60)];
}
3、SDWebImage下載頭像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
// SDWebImageLowPriority:會在表格滾動的時候,暫時圖像下載,性能會好很多
// SDWebImageRetryFailed:圖片下載失敗后重試
[self.iconView sd_setImageWithURL:video.fullImageUrl placeholderImage:placeholder options:SDWebImageRetryFailed | SDWebImageLowPriority];
4、集成下列刷新控件-->通過代碼和storyboard
/**
*? 集成下拉刷新控件(通過代碼)
*/
- (void)setRefreshControl{
// 集成下拉控件(不需要設置frame)
self.refreshControl = [[UIRefreshControl alloc] init];
// 監(jiān)聽下拉事件
[self.refreshControl addTarget:self action:@selector(loadData) forControlEvents:UIControlEventValueChanged];
// 設置提示文字,顏色
NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:@"努力加載中……" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor redColor]}];
self.refreshControl.attributedTitle = attributedTitle;
// 設置菊花顏色
self.refreshControl.tintColor = [UIColor grayColor];
}
# UIRefreshControl使用說明
1> 目前只對UITableviewController有用
2> 只能下拉刷新,不能上拉刷新
3> init或者viewdidload中創(chuàng)建UIRefreshControl,設置文字,顏色等信息
4> 系統(tǒng)自動管理UIRefreshControl,自動添加到tableview視圖中;
5> 給UIRefreshControl添加方法,當值改變的時候調(diào)用,方法用于數(shù)據(jù)請求
6> 該方法中請求數(shù)據(jù)確認完成之后,調(diào)用endRefreshing方法,關(guān)閉刷新
五、XML解析重構(gòu)
1> 新建一個模型類HMSAXVideo,專門做解析工作。
2> 提供類方法:+(void)saxParser:(NSData *)data finished:(void (^)(NSArray *))finished。
// 如果回調(diào)的 block 在當前方法不執(zhí)行,需要用一個屬性記錄
+(void)saxParser:(NSData *)data finished:(void (^)(NSArray *))finished{
// 使用斷言確保外界傳人的回調(diào)不為空,如果為空,則崩潰提示
NSAssert(finished != nil, @"必須傳人回調(diào)");
// 0.創(chuàng)建解析模型
HMSAXVideo *sax = [[HMSAXVideo alloc] init];
// 記錄回調(diào) block
sax.finishedBlock = finished;
// 1.創(chuàng)建 xml 解析器
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// 2.設置代理來解析
parser.delegate = sax;
// 3.解析器開始解析
// parse:是同步方法,會等所用代理方法執(zhí)行完畢后,才會執(zhí)行后面的代碼
[parser parse];
#warning 不能在parse方法調(diào)用后記錄回調(diào)
// 記錄回調(diào) block
// sax.finishedBlock = finished;
}
六、Copy,NSNumber,id
1、為什么模型HMVideo中的屬性都使用copy? 改為strong運行看看效果。
> strong:在設置數(shù)值的時候,僅僅是引用計算+1
> copy:在設置數(shù)值的時候,如果有方法是可變的,會copy一個不可變的副本。copy動作是在 setter方法中執(zhí)行的。
> 畫圖解析使用strong導致的效果。
> 如果屬性使用strong:
#下面的代碼
[self.currentVideo setValue:self.elementString forKey:elementName];
#修改為
[self.currentVideo setValue:self.elementString.copy forKey:elementName];
> 重新了屬性的setter方法,就是接管了原有系統(tǒng)提供的setter方法。
# 如果重新了copy屬性的 setter 方法,一定要 copy,否則外面的copy屬性就是strong屬性。
比如重寫了模型的name屬性的 setter方法:
- (void)setName:(NSString *)name {
_name = [name copy];
//? ? _name = name; 屬性就相當于 strong 了
}
2、NSNumber,id屬性
> 代碼演示實際開發(fā)中不用NSNumber帶來的問題。
> 代碼演示服務器返回數(shù)據(jù)中有id鍵值對的問題。