8
Обработка данных
Áîëüøèíñòâî ïðèëîæåíèé íà ïëàòôîðìå iOS â êàêîé-òî ìîìåíò ïîäêëþ÷àåòñÿ ê ñåòè äëÿ ïîëó÷åíèÿ äàííûõ. Îáû÷íî ýòè äàííûå ïåðåäàþòñÿ â ôîðìàòå, óäîáíîì äëÿ ðàçáîðà — XML, èëè â áîëåå ïîïóëÿðíîì â íàøå âðåìÿ ôîðìàòå JSON.  ýòîé ãëàâå âû óâèäèòå, êàê îðãàíèçîâàòü ïîëó÷åíèå äàííûõ íåïîñðåäñòâåííî îò ïîëüçîâàòåëÿ ÷åðåç èíòåðôåéñ ïðèëîæåíèÿ è êàê çàòåì ðàçîáðàòü äàííûå, çàãðóæåííûå èç ñåòè.  çàâåðøàþùåé ÷àñòè áóäåò ïîêàçàíî, êàê ñîõðàíèòü ïîëó÷åííûå äàííûå íà óñòðîéñòâå.
Ввод данных Ñðåäà Cocoa Touch (è iOS SDK) ïðåäîñòàâëÿåò ðÿä ýëåìåíòîâ ïîëüçîâàòåëüñêîãî èíòåðôåéñà, îò ïðîñòûõ ïîëåé äëÿ ââîäà òåêñòà äî ïåðåêëþ÷àòåëåé è ñåãìåíòèðîâàííûõ ýëåìåíòîâ. Âñå ýòè ýëåìåíòû ïðåäíàçíà÷åíû äëÿ ââîäà äàííûõ, íî êàê ïðàâèëî, êîãäà ðå÷ü èäåò î ââîäå äàííûõ, èìååòñÿ â âèäó ââîä òåêñòîâîé èíôîðìàöèè â ïðèëîæåíèè. Äâà îñíîâíûõ ýëåìåíòà ïîëüçîâàòåëüñêîãî èíòåðôåéñà, ïðåäíàçíà÷åííûõ äëÿ ââîäà òåêñòà, ïðåäñòàâëåíû êëàññàìè UITextField è UITextView. Èìåíà êëàññîâ ïîõîæè, íî ñàìè êëàññû çàìåòíî îòëè÷àþòñÿ. Ñàìîå î÷åâèäíîå ðàçëè÷èå ìåæäó íèìè çàêëþ÷àåòñÿ â òîì, ÷òî UITextView ïîçâîëÿåò ââîäèòü (è îòîáðàæàòü) ìíîãîñòðî÷íûå òåêñòîâûå äàííûå, à ó UITextField òàêàÿ âîçìîæíîñòü îòñóòñòâóåò. Íàèáîëåå ðàçäðàæàþùåå ðàçëè÷èå ìåæäó êëàññàìè ñâÿçàíî ñ ïðîáëåìîé îòêàçà îò ðîëè ïåðâîãî îòâåò÷èêà. Ïðè íàæàòèè îáà ýëåìåíòà îòîáðàæàþò ýêðàííóþ êëàâèàòóðó äëÿ ââîäà òåêñòà. Íî åñëè êëàññ UITextField ïîçâîëÿåò ïîëüçîâàòåëþ óáðàòü êëàâèàòóðó ñ ýêðàíà (ïðè ýòîì òåêñòîâîå ïîëå ïåðåñòàåò áûòü ïåðâûì îòâåò÷èêîì) ïðè íàæàòèè êíîïêè Done, êëàññ UITextView ýòîãî íå äåëàåò. Êàê âñêîðå áóäåò ïîêàçàíî, ó ýòîé ïðîáëåìû ñóùåñòâóþò îáõîäíûå ðåøåíèÿ, è âñå æå ýòî îäíà èç ñàìûõ íåïðèÿòíûõ ñòðàííîñòåé ñðåäû Cocoa Touch.
Класс UITextField и его делегат  ãëàâå 5 ìû èñïîëüçîâàëè ýëåìåíò UITextField êàê ÷àñòü ïðåäñòàâëåíèÿ AddCityController. Îäíàêî ïðè ýòîì âîçìîæíîñòè êëàññà íå èñïîëüçîâàëèñü â ïîëíîé ìåðå.
Класс UITextField и его делегат
231
Ïðè íàæàòèè êíîïêè Save ìû ïðîñòî ïðîâåðÿëè, ââåë ëè ïîëüçîâàòåëü êàêîé-ëèáî òåêñò, è ÷òî, ïîæàëóé, åùå âàæíåå, íå çàêðûâàëè êëàâèàòóðó ïðè íàæàòèè ïîëüçîâàòåëåì êëàâèøè Return. Ìåòîä saveCity:sender èç ýòîãî ïðèìåðà âûãëÿäåë òàê: - (void)saveCity:(id)sender { CGAppDelegate *delegate = (CGAppDelegate *)[[UIApplication sharedApplication] delegate]; NSMutableArray *cities = delegate.cities; UITextField *nameEntry = (UITextField *)[nameCell viewWithTag:777]; UITextView *descriptionEntry = (UITextView *)[descriptionCell viewWithTag:777]; if ( nameEntry.text.length > 0 ) { City *newCity = [[City alloc] init]; newCity.cityName = nameEntry.text; newCity.cityDescription = descriptionEntry.text; newCity.cityPicture = nil; [cities addObject:newCity]; CGViewController *viewController = delegate.viewController; [viewController.tableView reloadData]; } [delegate.navController popViewControllerAnimated:YES]; }
Îäíàêî ïðîòîêîë UITextFieldDelegate ïðåäîñòàâëÿåò øèðîêèé íàáîð ìåòîäîâ äåëåãàòà. ×òîáû èñïîëüçîâàòü èõ, íåîáõîäèìî îáúÿâèòü êëàññ êàê ðåàëèçóþùèé ïðîòîêîë äåëåãàòà (ñòðîêè ñ èçìåíåíèÿìè âûäåëåíû æèðíûì øðèôòîì): @interface AddCityController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate> { UITextField *activeTextField;
... }
Åñëè ïðåäñòàâëåíèå ñîäåðæèò íåñêîëüêî òåêñòîâûõ ïîëåé, â êëàññ ñòîèò ââåñòè ñïåöèàëüíóþ ïåðåìåííóþ ýêçåìïëÿðà äëÿ îòñëåæèâàíèÿ àêòèâíîãî ïîëÿ.
Ïîñëå ðåàëèçàöèè ïðîòîêîëà äåëåãàòà îòêðîéòå ôàéë nib, ñîäåðæàùèé UITextField (â ïðèëîæåíèè CityGuide ýòî áóäåò ôàéë AddCityController.xib). Ïåðåòàùèòå óêàçàòåëü ìûøè ñ íàæàòîé êëàâèøåé Control èç UITextField íà çíà÷îê File’s Owner è âûáåðèòå ñòðîêó «delegate» â îòêðûâøåìñÿ âðåìåííîì îêíå. Êîãäà ñâÿçü áóäåò ñîçäàíà, ñîõðàíèòå ôàéë nib.
Ïðè íàæàòèè íà òåêñòîâîì ïîëå âûçûâàåòñÿ ìåòîä äåëåãàòà textFieldShouldBeginEditing:, êîòîðûé äîëæåí ïîäòâåðäèòü, ÷òî òåêñòîâîå ïîëå âõîäèò â ðåæèì ðåäàêòèðîâàíèÿ è ñòàíîâèòñÿ ïåðâûì îòâåò÷èêîì. Äëÿ ýòîãî â ôàéë ðåàëèçàöèè êîíòðîëëåðà (íàïðèìåð, AddCityController.m) âêëþ÷àåòñÿ ñëåäóþùèé ôðàãìåíò:
232
Глава 8. Обработка данных
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { activeTextField = textField; return YES; }
Åñëè ïðåäñòàâëåíèå âàøåãî ïðèëîæåíèÿ ñîäåðæèò íåñêîëüêî òåêñòîâûõ ïîëåé,
àêòèâíîå ïîëå õðàíèòñÿ â ýòîé ïåðåìåííîé. Åñëè ìåòîä âîçâðàùàåò NO, òåêñòîâîå ïîëå îñòàåòñÿ íåäîñòóïíûì äëÿ ðåäàêòèðîâàíèÿ. Ïåðåõîä â ðåæèì ðåäàêòèðîâàíèÿ ïðîèñõîäèò òîëüêî â òîì ñëó÷àå, åñëè ìåòîä âåðíóë YES. Íà ýòîé ñòàäèè îòîáðàæàåòñÿ ýêðàííàÿ êëàâèàòóðà; òåêñòîâîå ïîëå ñòàíîâèòñÿ ïåðâûì îòâåò÷èêîì è âûçûâàåòñÿ ìåòîä äåëåãàòà textFieldDidBeginEditing:. Ñàìûé ïðîñòîé ñïîñîá ñêðûòü êëàâèàòóðó çàêëþ÷àåòñÿ â ðåàëèçàöèè ìåòîäà äåëåãàòà textFieldShouldReturn: è ÿâíîãî îòêàçà îò ðîëè ïåðâîãî îòâåò÷èêà. Ýòîò ìåòîä äåëåãàòà âûçûâàåòñÿ ïðè íàæàòèè êíîïêè Return íà ýêðàííîé êëàâèàòóðå. ×òîáû ñêðûòü êëàâèàòóðó ïðè íàæàòèè Return, âêëþ÷èòå â ðåàëèçàöèþ êîíòðîëëåðà ñëåäóþùèé ôðàãìåíò: - (BOOL)textFieldShouldReturn:(UITextField *)textField { activeTextField = nil; [textField resignFirstResponder]; return YES; }
Åñëè âàøå ïðèëîæåíèå îòñëåæèâàåò òåêóùåå àêòèâíîå òåêñòîâîå ïîëå, â ýòîì
ìåñòå ñëåäóåò ïðèñâîèòü ïåðåìåííîé àêòèâíîãî ïîëÿ nil ïåðåä îòêàçîì îò ðîëè ïåðâîãî îòâåò÷èêà. Ýòîò ìåòîä îáû÷íî èñïîëüçóåòñÿ äëÿ îòêàçà òåêñòîâîãî ïîëÿ îò ðîëè ïåðâîãî îòâåò÷èêà, ÷òî ïðèâîäèò ê àêòèâèçàöèè ìåòîäîâ äåëåãàòà textFieldShouldEndEditing: è textFieldDidEndEditing:. Ýòè ìåòîäû ìîãóò èñïîëüçîâàòüñÿ äëÿ îáíîâëåíèÿ ìîäåëè äàííûõ íîâûì ñîäåðæèìûì èëè âíåñåíèÿ ñîîòâåòñòâóþùèõ èçìåíåíèé â ïîëüçîâàòåëüñêèé èíòåðôåéñ ïîñëå ðàçáîðà ââåäåííûõ äàííûõ (íàïðèìåð, äîáàâëåíèÿ èëè óäàëåíèÿ äîïîëíèòåëüíûõ ýëåìåíòîâ èíòåðôåéñà).
Элемент UITextView и его делегат Êàê è ïðè èñïîëüçîâàíèè UITextField â ïðåäñòàâëåíèè AddCityController èç ãëàâû 5, âîçìîæíîñòè êëàññà UITextView â ýòîì ïðèìåðå íå áûëè èñïîëüçîâàíû ïîëíîöåííî. Êëàññ UITextView, êàê è UITextField, èìååò ñâîé ïðîòîêîë äåëåãàòà, îòêðûâàþùèé ìíîãî äîïîëíèòåëüíûõ âîçìîæíîñòåé.
Закрытие экранной клавиатуры  ïðîòîêîëå UITextViewDelegate îòñóòñòâóåò àíàëîã ìåòîäà textFieldShouldReturn:. Âåðîÿòíî, ýòî îáúÿñíÿåòñÿ òåì, ÷òî ïðè ââîäå ìíîãîñòðî÷íîãî òåêñòà íàæàòèå êëàâèøè Return íå óêàçûâàåò íà òî, ÷òî ïîëüçîâàòåëü æåëàåò ïðåêðàòèòü ðåäàêòè-
Класс UITextField и его делегат
233
ðîâàíèå òåêñòà (â êîíöå êîíöîâ, ïåðåâîä ñòðîêè òîæå îñóùåñòâëÿåòñÿ íàæàòèåì Return).
Âïðî÷åì, íåóìåíèå UITextView îòêàçàòüñÿ îò ðîëè ïåðâîãî îòâåò÷èêà ñ êëàâèàòóðû ìîæíî îáîéòè ðàçíûìè ñïîñîáàìè. Îäíî èç ñòàíäàðòíûõ ðåøåíèé — ðàçìåùåíèå êíîïêè Done íà ïàíåëè íàâèãàöèè, êîãäà UITextView âûçûâàåò ýêðàííóþ êëàâèàòóðó. Ïðè íàæàòèè ýòà êíîïêà ïðèêàçûâàåò òåêñòîâîìó ïðåäñòàâëåíèþ îòêàçàòüñÿ îò ðîëè ïåðâîãî îòâåò÷èêà, ÷òî ïðèâîäèò ê çàêðûòèþ êëàâèàòóðû. Îäíàêî â çàâèñèìîñòè îò òîãî, êàê áûë ñïëàíèðîâàí ïîëüçîâàòåëüñêèé èíòåðôåéñ, âû, âîçìîæíî, ïðåäïî÷òåòå, ÷òîáû ýëåìåíò UITextView ñàì îòêàçûâàëñÿ îò ðîëè ïåðâîãî îòâåò÷èêà, êîãäà ïîëüçîâàòåëü êàñàåòñÿ ýêðàíà çà ïðåäåëàìè ýëåìåíòà UITextView. Äëÿ ýòîãî íóæíî ñîçäàòü ñóáêëàññ UIView, à çàòåì ïðèêàçàòü òåêñòîâîìó ïðåäñòàâëåíèþ îòêàçàòüñÿ îò ðîëè ïåðâîãî îòâåò÷èêà, êîãäà ïîëüçîâàòåëü êàñàåòñÿ ýêðàíà çà ïðåäåëàìè ïðåäñòàâëåíèÿ. Ùåëêíèòå ïðàâîé êíîïêîé ìûøè íà ãëàâíîé ãðóïïå íà ïàíåëè íàâèãàòîðà ïðîåêòà è âûáåðèòå â ìåíþ êîìàíäó New File. Âûáåðèòå ñîçäàíèå êëàññà Objective-C, çàòåì â ñïèñêå Subclass of óêàæèòå ïóíêò UIView è ïðèñâîéòå êëàññó èìÿ CustomView.  èíòåðôåéñíîì ôàéëå (CustomView.h) äîáàâüòå âíåøíþþ ññûëêó IBOutlet äëÿ UITextView: #import <UIKit/UIKit.h> @interface CustomView : UIView { IBOutlet UITextView *textView; } @end
Çàòåì â ôàéëå ðåàëèçàöèè (CustomView.m) ðåàëèçóéòå ìåòîä touchesEnded:withEvent: è ïðèêàæèòå UITextView îòêàçàòüñÿ îò ðîëè ïåðâîãî îòâåò÷èêà. Ðåàëèçàöèÿ äîëæíà âûãëÿäåòü ñëåäóþùèì îáðàçîì (äîáàâëåííûå ñòðîêè âûäåëåíû æèðíûì øðèôòîì): #import "CustomView.h" @implementation CustomView - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // Код инициализации } return self; } - (void) awakeFromNib { self.multipleTouchEnabled = YES; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"touches began count %d, %@", [touches count], touches); [textView resignFirstResponder];
ïðîäîëæåíèå
234
Глава 8. Обработка данных [self.nextResponder touchesEnded:touches withEvent:event];
} @end
Ïîñëå äîáàâëåíèÿ êëàññà ñîõðàíèòå èçìåíåíèÿ, îòêðîéòå Interface Builder è ùåëêíèòå íà ïðåäñòàâëåíèè. Îòêðîéòå èíñïåêòîðà èäåíòèôèêàöèè íà ïàíåëè Utilities è çàìåíèòå òèï ïðåäñòàâëåíèÿ â ôàéëå nib íà âàø êëàññ CustomView (âìåñòî èñïîëüçóåìîãî ïî óìîë÷àíèþ êëàññà UIView). Çàòåì â èíñïåêòîðå ñâÿçåé ïåðåòàùèòå âíåøíþþ ññûëêó textView íà UITextView. Ïîñëå âñåõ ïåðå÷èñëåííûõ äåéñòâèé ïðèêîñíîâåíèå çà ïðåäåëàìè àêòèâíûõ ýëåìåíòîâ ïîëüçîâàòåëüñêîãî èíòåðôåéñà áóäåò ïðèâîäèòü ê çàêðûòèþ êëàâèàòóðû. Åñëè ñóáêëàññèðóåìûé ýëåìåíò UIView íàõîäèòñÿ «ïîçàäè» äðóãèõ ýëåìåíòîâ ïîëüçîâàòåëüñêîãî èíòåðôåéñà, ýòè ýëåìåíòû ïåðåõâàòÿò íàæàòèÿ äî òîãî, êàê òå äîáåðóòñÿ äî óðîâíÿ UIView. Íàïðèìåð, â ïðèëîæåíèè CityGuide3 èç ãëàâû 6 è åãî èíòåðôåéñà Add City âàì ïðèäåòñÿ îáúÿâèòü ñâîå ïîëüçîâàòåëüñêîå ïðåäñòàâëåíèå ñóáêëàññîì UITableViewCell âìåñòî UIView. Ïîñëå ýòîãî òðåì ÿ÷åéêàì òàáëè÷íîãî ïðåäñòàâëåíèÿ â ãëàâíîì îêíå AddCityController.xib íóæíî áóäåò ïðèñâîèòü êëàññ CustomView âìåñòî èñïîëüçóåìîãî ïî óìîë÷àíèþ êëàññà UITableViewCell (íå èçìåíÿéòå êëàññ ïðåäñòàâëåíèÿ). Çàòåì âíåøíÿÿ ññûëêà textView âñåõ òðåõ ÿ÷ååê òàáëè÷íîãî ïðåäñòàâëåíèÿ ñâÿçûâàåòñÿ ñ ýëåìåíòîì UITextView ÿ÷åéêè òàáëè÷íîãî ïðåäñòàâëåíèÿ, èñïîëüçóåìîé äëÿ ââîäà äëèííîãî îïèñàíèÿ.
Ðåøåíèå ïîëó÷àåòñÿ ýëåãàíòíûì, íî ïðèìåíèìî íå âñåãäà. Âî ìíîãèõ ñëó÷àÿõ ïðèõîäèòñÿ ïðèáåãàòü ê ïðèìèòèâíîìó ðàçìåùåíèþ íà ïàíåëè íàâèãàöèè êíîïêè Done, çàêðûâàþùåé ýêðàííóþ êëàâèàòóðó.
Разбор данных XML Äâà ïîïóëÿðíûõ ìåõàíèçìà ðàçáîðà äîêóìåíòîâ XML — SAX è DOM. Ïàðñåð SAX (Simple API for XML) óïðàâëÿåòñÿ ñîáûòèÿìè. Îí ïîñëåäîâàòåëüíî ÷èòàåò äîêóìåíò XML è âûçûâàåò ìåòîä äåëåãàòà ïðè êàæäîì ðàñïîçíàâàíèè òîêåíà. Ñîáûòèÿ ãåíåðèðóþòñÿ â íà÷àëå è êîíöå äîêóìåíòà, â íà÷àëå è êîíöå êàæäîãî ýëåìåíòà. Ïàðñåð DOM (Document Object Model) ÷èòàåò âåñü äîêóìåíò è ñòðîèò â ïàìÿòè äðåâîâèäíóþ ñòðóêòóðó. Ïîñëå ýòîãî ÿçûê çàïðîñîâ XPath èñïîëüçóåòñÿ äëÿ âûáîðà óçëîâ äîêóìåíòà XML ïî ðàçëè÷íûì êðèòåðèÿì. Ìíîãèå ïðîãðàììèñòû ñ÷èòàþò ìåòîä DOM áîëåå çíàêîìûì è ïðîñòûì â èñïîëüçîâàíèè; ñ äðóãîé ñòîðîíû, ïðèëîæåíèÿ íà áàçå SAX îáû÷íî áîëåå ýôôåêòèâíû, áûñòðåå âûïîëíÿþòñÿ è ðàñõîäóþò ìåíüøå ïàìÿòè. Òàêèì îáðàçîì, ïðè îòñóòñòâèè îãðàíè÷åíèé ïî ñèñòåìíûì òðåáîâàíèÿì, åäèíñòâåííûé ðåàëüíûé ôàêòîð âûáîðà ìåæäó ïàðñåðàìè SAX è DOM ñâîäèòñÿ ê ïðîèçâîäèòåëüíîñòè. Åñëè âû çàõîòèòå áîëüøå óçíàòü î XML, ÿ ðåêîìåíäóþ íà÷àòü ñ êíèãè Ýðèêà Ò. Ðýÿ (Erik T. Ray) «Learning XML, Second Edition» (O’Reilly).
Разбор данных XML
235
Разбор XML с использованием libxml2 Ïàðñåð libxml2 è íàäñòðîéêè XPath Ìýòòà Ãàëëàõåðà óæå óïîìèíàëèñü â ïðåäûäóùåé ãëàâå. Òàì æå ÿ ðåêîìåíäîâàë èñïîëüçîâàòü ýòè íàäñòðîéêè, åñëè âû õîòèòå âûïîëíèòü DOM-ðàçáîð XML íà iPhone èëè iPod touch. Èíñòðóêöèè ïî äîáàâëåíèþ íàäñòðîåê XPath â ïðîåêò ïðèâåäåíû âî âðåçêå «Èñïîëüçîâàíèå íàäñòðîåê XPath» â ãëàâå 7. Íàäñòðîéêè ïðåäîñòàâëÿþò äâà ìåòîäà, êîòîðûå ðàçëè÷àþòñÿ òîëüêî îäíèì: ïåðâûé ïîëó÷àåò äîêóìåíò HTML (à ñëåäîâàòåëüíî, ìåíåå òðåáîâàòåëåí îòíîñèòåëüíî òîãî, ÷òî ñ÷èòàòü «ïðàâèëüíûì» äîêóìåíòîì), à äðóãîé îæèäàåò ïîëó÷èòü äåéñòâèòåëüíûé äîêóìåíò XML: NSArray *PerformHTMLXPathQuery(NSData *document, NSString *query); NSArray *PerformXMLXPathQuery(NSData *document, NSString *query);
Åñëè âû õîòèòå, ÷òîáû âåñü äîêóìåíò âîçâðàùàëñÿ â âèäå îäíîé ñòðóêòóðû äàííûõ, ñëåäóþùèé ôðàãìåíò ïîçâîëèò âàì ýòî ñäåëàòü. Ó÷òèòå, ÷òî äëÿ ëþáûõ äîêóìåíòîâ XML, êðîìå ñàìûõ ïðîñòåéøèõ, îáû÷íî ãåíåðèðóåòñÿ ñòðóêòóðà èç ýëåìåíòîâ ìàññèâîâ è ñëîâàðåé ñ âûñîêîé ñòåïåíüþ âëîæåííîñòè, ïîëüçû îò êîòîðîé áóäåò íåìíîãî: NSString *xpathQueryString; NSArray *nodes; xpathQueryString = @"/*"; nodes = PerformXMLXPathQuery(responseData, xpathQueryString); NSLog(@"nodes = %@", nodes );
Ïðèñìîòðèìñÿ ïîâíèìàòåëüíåå ê äîêóìåíòó XML, ïîëó÷åííîìó îò ñëóæáû ïîãîäû Google, êîòîðûé ðàçáèðàëñÿ â ïðèëîæåíèè Weather ãëàâû 7. Ýòîò äîêóìåíò XML èìååò ñëåäóþùóþ ñòðóêòóðó: <forecast_conditions> ... <icon data="/ig/images/weather/chance_of_rain.gif"/> </forecast_conditions> <forecast_conditions> ... <icon data="/ig/images/weather/chance_of_rain.gif"/> </forecast_conditions> <forecast_conditions> ... <icon data="/ig/images/weather/chance_of_rain.gif"/> </forecast_conditions> <forecast_conditions> ... <icon data="/ig/images/weather/chance_of_rain.gif"/> </forecast_conditions>
Äëÿ èçâëå÷åíèÿ URL-àäðåñîâ çíà÷êîâ áûë âûïîëíåí ñëåäóþùèé çàïðîñ XPath: xpathQueryString = @"//forecast_conditions/icon/@data"; nodes = PerformXMLXPathQuery(responseData, xpathQueryString);
236
Глава 8. Обработка данных
Çàïðîñ
èùåò àòðèáóòû äàííûõ ýëåìåíòîâ <icon>, âëîæåííûõ â ýëåìåíòû <forecast_conditions>. Âîçâðàùàåòñÿ ìàññèâ, ñîäåðæàùèé âñå âõîæäåíèÿ.
Ìàññèâ nodes, âîçâðàùàåìûé ìåòîäîì PerformXMLXPathQuery, âûãëÿäèò òàê: (
{ nodeContent = "/ig/images/weather/mostly_sunny.gif"; nodeName = data; }, { nodeContent = "/ig/images/weather/chance_of_rain.gif"; nodeName = data; }, { nodeContent = "/ig/images/weather/mostly_sunny.gif"; nodeName = data; }, { nodeContent = "/ig/images/weather/mostly_sunny.gif"; nodeName = data; }
)
Ñòðóêòóðà ïðåäñòàâëÿåò ñîáîé îáúåêò NSArray, ñîäåðæàùèé îáúåêòû NSDictionary. Äëÿ ðàçáîðà äàííûõ ìû ïåðåáèðàåì ýëåìåíòû ìàññèâà è èçâëåêàåì èç ñëîâàðåé çíà÷åíèå, ñîîòâåòñòâóþùåå êëþ÷ó nodeContent. Äàííûå âêëþ÷àþòñÿ â ìàññèâ icons: for ( NSDictionary *node in nodes ) { for ( id key in node ) { if( [key isEqualToString:@"nodeContent"] ) { [icons addObject: [NSString stringWithFormat:@"http://www.google.com%@", [node objectForKey:key]]]; } } }
Разбор данных XML с использованием NSXMLParser Äëÿ ðàçáîðà äàííûõ â ôîðìàòå XML íà iPhone îôèöèàëüíî ðåêîìåíäóåòñÿ èñïîëüçîâàòü êëàññ NSXMLParser íà áàçå SAX. Îäíàêî ýòîò ïàðñåð ïðåäúÿâëÿåò æåñòêèå òðåáîâàíèÿ ê ôîðìàòó äàííûõ è íå ïðèíèìàåò äîêóìåíòû HTML: NSString *url = @"http://feeds.feedburner.com/oreilly/news"; NSURL *theURL = [[NSURL URLWithString:url] retain]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theURL]; [parser setDelegate:self]; [parser setShouldResolveExternalEntities:YES]; BOOL success = [parser parse]; NSLog(@"Success = %d", success);
Ìû ïåðåäàåì ïàðñåðó äîêóìåíò XML è ðåàëèçóåì åãî ìåòîäû äåëåãàòà. Êëàññ NSXMLParser ïðåäîñòàâëÿåò ñëåäóþùèå ìåòîäû äåëåãàòîâ:
Разбор данных XML
237
parserDidStartDocument: parserDidEndDocument: parser:didStartElement:namespaceURI:qualifiedName:attributes: parser:didEndElement:namespaceURI:qualifiedName: parser:didStartMappingPrefix:toURI: parser:didEndMappingPrefix: parser:resolveExternalEntityName:systemID: parser:parseErrorOccurred: parser:validationErrorOccurred: parser:foundCharacters: parser:foundIgnorableWhitespace: parser:foundProcessingInstructionWithTarget:data: parser:foundComment: parser:foundCDATA:
Èç âñåõ ìåòîäîâ äåëåãàòîâ ÷àùå âñåãî èñïîëüçóþòñÿ ìåòîäû parser:didStartElement:namespaceURI:qualifiedName:attributes: è parser:didEndElement:namespaceURI:qualifiedName:. Ýòè äâà ìåòîäà íàðÿäó ñ ìåòîäîì parser:foundCharacters: ïîçâîëÿþò îïðåäåëèòü íà÷àëî è êîíåö âûáðàííîãî ýëåìåíòà è ïîëó÷èòü åãî ñîäåðæèìîå. Êîãäà îáúåêò NSXMLParser îáíàðóæèâàåò ýëåìåíò â äîêóìåíòå XML, îí îòïðàâëÿåò ñâîåìó äåëåãàòó òðè ðàçíûõ ñîîáùåíèÿ â ñëåäóþùåì ïîðÿäêå: parser:didStartElement:namespaceURI:qualifiedName:attributes: parser:foundCharacters: parser:didEndElement:namespaceURI:qualifiedName:
Âåðíåìñÿ ê ïðèëîæåíèþ Weather: ÷òîáû çàìåíèòü ðåøåíèå, èñïîëüçóþùåå XPath è DOM, ðåøåíèåì íà áàçå NSXMLParser, ñëåäóåò çàìåíèòü ñóùåñòâóþùèé ìåòîä queryService:withParent: ñëåäóþùèì: - (void)queryService:(NSString *)city withParent:(UIViewController *)controller { viewController = (MainViewController *)controller; responseData = [NSMutableData data]; NSString *url = [NSString stringWithFormat: @"http://www.google.com/ig/ api?weather=%@",city]; theURL = [NSURL URLWithString:url]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theURL]; [parser setDelegate:self]; [parser setShouldResolveExternalEntities:YES]; BOOL success = [parser parse]; }
Òàêæå íåîáõîäèìî óäàëèòü âñå ìåòîäû äåëåãàòà NSURLConnection ñ äîáàâëåíèåì ñëåäóþùåãî ìåòîäà äåëåãàòà NSXMLParser äëÿ îáðàáîòêè çàïîëíåíèÿ ìàññèâîâ: - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { // Разбор данных для получения пути к значку if([elementName isEqualToString:@"icon"]) { NSString *imagePath = [attributeDict objectForKey:@"data"];
ïðîäîëæåíèå
238
Глава 8. Обработка данных [icons addObject: [NSString stringWithFormat:@"http://www.google.com%@", imagePath]];
} ... Код разбора для других элементов ... [viewController updateView]; }
 ïðèâåäåííîì ïðèìåðå ðàçáèðàåòñÿ òîëüêî ýëåìåíò icon; åñëè âû çàõîòèòå èñïîëüçîâàòü NSXMLParser, îáðàòèòåñü ê ìåòîäó connectionDidFinishLoading: èñõîäíîãî ïðèëîæåíèÿ Weather è äîáàâüòå êîä ðàçáîðà äëÿ êàæäîãî èç ýëåìåíòîâ ïåðåä âûçîâîì [viewController updateView] â ýòîì ìåòîäå (â ïðîòèâíîì ñëó÷àå ïðîèçîéäåò èñêëþ÷åíèå, êîòîðîå ïðèâåäåò ê àâàðèéíîìó çàâåðøåíèþ ïðèëîæåíèÿ, òàê êàê ñîîòâåòñòâóþùèå ñòðóêòóðû äàííûõ îêàæóòñÿ íåçàïîëíåííûìè).
Åñëè òîëüêî âû íå ïðèâûêëè èñïîëüçîâàòü ïàðñåðû íà áàçå SAX, íà ìîé âçãëÿä, XPath è DOM êîíöåïòóàëüíî ïðîùå ñîáûòèéíîé ìîäåëè SAX. Ýòî îñîáåííî âàæíî ïðè ðàáîòå ñ HTML, ïîñêîëüêó äîêóìåíòû HTML ïðèäåòñÿ «ïî÷èñòèòü» ïåðåä òåì, êàê ïåðåäàâàòü èõ êëàññó NSXMLParser.
Разбор JSON JSON (http://www.json.org/ ) — îáëåã÷åííûé ôîðìàò ïåðåäà÷è äàííûõ, áîëåå èëè ìåíåå ïîíÿòíûé äëÿ ÷åëîâåêà, íî ïðè ýòîì ëåãêî ðàçáèðàåìûé êîìïüþòåðîì.  îòëè÷èå îò äîêóìåíòíî-îðèåíòèðîâàííîãî ôîðìàòà XML, ôîðìàò JSON îðèåíòèðîâàí íà äàííûå. Åñëè âàì ïîòðåáóåòñÿ ïåðåäàòü áëîê äàííûõ ñî ñëîæíîé ñòðóêòóðîé, âåðîÿòíî, ëó÷øå âîñïîëüçîâàòüñÿ XML. Íî åñëè ïåðåäàâàåìûå äàííûå îòíîñèòåëüíî ïðîñòû, JSON ìîæåò îêàçàòüñÿ õîðîøèì âàðèàíòîì. Î÷åâèäíîå ïðåèìóùåñòâî JSON ïåðåä XML çàêëþ÷àåòñÿ â òîì, ÷òî ýòîò ôîðìàò îðèåíòèðîâàí íà äàííûå è ðàçáèðàåòñÿ (ïî÷òè) òàê æå ëåãêî, êàê õåø-òàáëèöà — à ñëåäîâàòåëüíî, íå òðåáóåò òÿæåëîâåñíûõ áèáëèîòåê ðàçáîðà. Êðîìå òîãî, äîêóìåíòû JSON ãîðàçäî ìåíüøå ýêâèâàëåíòíûõ äîêóìåíòîâ XML. Ïðè îãðàíè÷åííîé ïðîïóñêíîé ñïîñîáíîñòè êàíàëà (êàê, íàïðèìåð, íà iPhone) ýòî ìîæåò áûòü âàæíî. Äîêóìåíòû JSON îáû÷íî çàíèìàþò îêîëî ïîëîâèíû îáúåìà ýêâèâàëåíòíîãî äîêóìåíòà XML, ñîäåðæàùåãî òàêîé æå îáúåì äàííûõ.
NSJSONSerialization Äî âûõîäà iOS 5 âñòðîåííîé ïîääåðæêè ðàçáîðà JSON íå ñóùåñòâîâàëî. Îäíàêî SDK òåïåðü âêëþ÷àåò êëàññ NSJSONSerialization, ïîääåðæèâàþùèé ïðåîáðàçîâàíèÿ äîêóìåíòîâ JSON â îáúåêòû Foundation, è íàoáîðîò. Êëàññ NSJSONSerialization ìîæåò èñïîëüçîâàòüñÿ òîëüêî â iOS 5. Ê ñîæàëåíèþ, âûçîâ ìåòîäîâ êëàññà class íà óñòðîéñòâàõ ñ áîëåå ñòàðîé âåðñèåé îïåðàöèîííîé ñèñòåìû íå ïðèâîäèò ê àâàðèéíîìó çàâåðøåíèþ ïðèëîæåíèÿ; âìåñòî ýòîãî âûçîâû ïðîñòî èãíîðèðóþòñÿ.
Разбор JSON
239
Разбор документов JSON Õîòÿ êëàññ NSJSONSerialization òàêæå ïîääåðæèâàåò ðàçáîð äàííûõ èç ïîòîêà NSInputStream, ÷àùå âñåãî äîêóìåíò JSON ïåðåäàåòñÿ êëàññó â âèäå îáúåêòà NSData ñ èñïîëüçîâàíèåì ñòàòè÷åñêîãî ìåòîäà êëàññà JSONObjectWithData:options:error:: NSError *error = nil; id object = nil; NSData *json = [NSData dataWithContentsOfFile:path]; object = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error]; if( error ) { ... код обработки ошибок ... }
Çàãðóçêà äîêóìåíòà JSON èç ôàéëà. Îáðàòèòå âíèìàíèå: îáúåêò êëàññà id, âîçâðàùàåìûé ìåòîäîì êëàññà NSJSONSerialization, áóäåò (ïðè íîðìàëüíîì õîäå ñîáûòèé) ïðåäñòàâëÿòü ñîáîé îáúåêò NSArray èëè NSDictionary â çàâèñèìîñòè îò ñòðóêòóðû èñõîäíîãî äîêóìåíòà JSON.  ïðèâåäåííîì ïðèìåðå âîçâðàùàåòñÿ îáîáùåííûé îáúåêò; îäíàêî åñëè âû óâåðåíû â òîì, êàêîé îáúåêò Foundation áóäåò ñîçäàí, âû ìîæåòå ÿâíî ïðåîáðàçîâàòü âîçâðàùåííûé îáúåêò ê òèïó NSArray èëè NSDictionary. Ïðè ñîçäàíèè îáúåêòîâ Foundation ïî äàííûì JSON ìîãóò ïåðåäàâàòüñÿ ðàçëè÷íûå àðãóìåíòû, â òîì ÷èñëå: NSJSONReadingMutableContainers — óêàçûâàåò, ÷òî ìàññèâû è ñëîâàðè ñîçäàþòñÿ êàê èçìåíÿåìûå (mutable) îáúåêòû. NSJSONReadingMutableLeaves — óêàçûâàåò, ÷òî ëèñòîâûå ñòðîêè ãðàôà îáúåêòîâ JSON ñîçäàþòñÿ êàê ýêçåìïëÿðû NSMutableString. NSJSONReadingAllowFragments — óêàçûâàåò, ÷òî ïàðñåð äîëæåí ðàçðåøàòü îáúåêòû âåðõíåãî óðîâíÿ, êîòîðûå íå ÿâëÿþòñÿ ýêçåìïëÿðàìè NSArray èëè NSDictionary. Âîçìîæíà ïåðåäà÷à íåñêîëüêèõ àðãóìåíòîâ, îáúåäèíåííûõ ëîãè÷åñêèì îïåðàòîðîì (íàïðèìåð, NSJSONReadingMutableContainers|NSJSONReadingAllowFragments).
Создание документов JSON Îáúåêò Foundation, êîòîðûé ìîæåò áûòü ïðåîáðàçîâàí â ôîðìàò JSON, äîëæåí îáëàäàòü ñëåäóþùèìè ñâîéñòâàìè: Îáúåêò Foundation âåðõíåãî óðîâíÿ äîëæåí îòíîñèòüñÿ ê òèïó NSArray èëè NSDictionary. Âñå îáúåêòû ÿâëÿþòñÿ ýêçåìïëÿðàìè NSString, NSNumber, NSArray, NSDictionary èëè NULL. Âñå êëþ÷è ñëîâàðÿ ÿâëÿþòñÿ ýêçåìïëÿðàìè NSString. ×èñëà íå ðàâíû NaN èëè áåñêîíå÷íîñòè.
240
Глава 8. Обработка данных
Åñëè âàø îáúåêò óäîâëåòâîðÿåò ýòèì êðèòåðèÿì, åãî ìîæíî ñåðèàëèçîâàòü â JSON ñëåäóþùèì îáðàçîì: NSError *error = nil; NSData *json = nil; if( [NSJSONSerialization isValidJSONObject:object] ) { json = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; } else { ... обработка недействительных объектов ... } if( error ) { ... код обработки ошибок ... }
Çäåñü
ìû ïðîâåðÿåì, ÿâëÿåòñÿ ëè ïåðåäàííûé îáúåêò äåéñòâèòåëüíûì îáúåêòîì äëÿ ñåðèàëèçàöèè. Âûçîâ ìåòîäà isValidJSONObject: (èëè ïîïûòêà ïðåîáðàçîâàíèÿ) — åäèíñòâåííûå íàäåæíûå ïóòè îïðåäåëåíèÿ âîçìîæíîñòè ïðåîáðàçîâàíèÿ çàäàííîãî îáúåêòà â äàííûå JSON.
Библиотека JSON Áèáëèîòåêà Ñòèãà Áðàóòàñåòà (Stig Brautaset) json-framework ðåàëèçóåò è ïàðñåð JSON, è ãåíåðàòîð, à åå èíòåãðàöèÿ â ïðîåêò âûïîëíÿåòñÿ î÷åíü ïðîñòî. Åñëè âàøå ïðèëîæåíèå iOS äîëæíî ðàáîòàòü íà óñòðîéñòâàõ, íà êîòîðûõ íå óñòàíîâëåíà ñèñòåìà iOS 5 è âûøå, âîçìîæíî, âàì ñòîèò ïîäóìàòü îá èñïîëüçîâàíèè áèáëèîòåêè json-framework. Îáðàç äèñêà ñ íîâåéøåé âåðñèåé áèáëèîòåêè json-framework ìîæíî çàãðóçèòü ïî àäðåñó https://github.com/stig/json-framework/. Îòêðîéòå îáðàç äèñêà è ïåðåòàùèòå ïàïêó JSON â ãðóïïó Classes íà ïàíåëè íàâèãàòîðà ïðîåêòà. Íå çàáóäüòå óñòàíîâèòü ôëàæîê Copy items into destination group's folder ïåðåä äîáàâëåíèåì ôàéëîâ. Èñõîäíûå ôàéëû JSON áóäóò âêëþ÷åíû â âàø ïðîåêò; ÷òîáû èñïîëüçîâàòü èõ, âû äîëæíû èìïîðòèðîâàòü ôàéë JSON.h â ñâîé êëàññ.
Получение актуальных тем Twitter ×òîáû âû ïîíÿëè, êàê ðàáîòàòü ñ JSON, ìû ðåàëèçóåì ïðîñòåéøåå ïðèëîæåíèå äëÿ ïîëó÷åíèÿ èíôîðìàöèè îá àêòóàëüíûõ òåìàõ Twitter ñ èñïîëüçîâàíèåì ïëàòôîðìû è API Twitter, à çàòåì ðàçáåðåì ïîëó÷åííûå ðåçóëüòàòû ïðè ïîìîùè êëàññà NSJSONSerialization. Åñëè âàñ èíòåðåñóåò Twitter API, îáðàùàéòåñü ê äîêóìåíòàöèè Twitter (https:// dev.twitter.com/ ) çà áîëåå ïîäðîáíûìè îïèñàíèÿìè äîñòóïíûõ ìåòîäîâ. Ïî çàïðîñó ê ïîèñêîâîìó API Twitter â ôîðìå https://api.twitter.com/1/trends/1. json âîçâðàùàåòñÿ äîêóìåíò JSON ñ äåñÿòüþ ñàìûìè àêòóàëüíûìè òåìàìè Twitter.  îòâåò âêëþ÷àåòñÿ âðåìÿ çàïðîñà, èìÿ êàæäîé òåìû è URL-àäðåñ ñòðàíèöû ðåçóëüòàòîâ ïîèñêà Twitter.