これを正しく解析していますか?多くの問題があり、実質的なものもあれば、より文体的なものもあります。まず、いくつかの実質的な観察:
各要素に対して一度だけ呼び出さfoundCharacters
れると想定しています。一般的に正しい URL のようなものですが、この事実が保証されているわけではありません。文字列に文字を追加するだけで、その後の最終的な文字列の保存とクリーンアップはdidEndElement
.
また、関心foundCharacter
のある要素のデータを取得しているかどうかに関係なく、結果を保存しています (さらに悪いことに、要素間の文字)。気になる要素名のとのfoundCharacter
間の場合にのみ、結果を保存します。didStartElement
didEndElement
あなたdidStartElement
は を実行してalloc
いますが、 も実行していませinit
ん。常にinit
同時に行う必要があります。init
後でプロパティを追加設定することもできますが、適切なメソッドを呼び出すことを忘れないでください。
サーバーdidEndElement
から画像を取得しています。データの取得を XML 自体 (たとえば、画像の場合は画像の URL) だけに制限し、サーバーから追加の項目を取得しないようにする必要があります。これは、メモリ ヒットを最小限に抑え、ユーザーの応答時間を改善するために、より洗練された設計を検討する場合に特に重要です。画像の遅延読み込みを使用する場合があります。つまり、XML から URLのみを取得し、UI に処理させたい場合です。画像が取得されるかどうか、いつ取得されるか。将来、より高度な開発を開始するときに、ここで画像検索を行ったことを後悔するでしょう。
elementName
で行っているチェックと で行っているチェックの間に不均衡がありdidStartElement
ますdidEndElement
。データを取得するすべての要素について、呼び出しのバランスを取る必要があります。簡単にするために、XML から 1 つの要素のみを取得する例を挙げたことは承知していますが、以下の例では、2 つの要素を取得していると仮定します。これらのバランスを取ることの重要性が明らかになります。
より文体的な観察のために:
慣例により、変数名は通常小文字で始まります。クラス名は通常、大文字で始まります。「EGS」は確立されたクラスの接頭辞 (イニシャルや会社の頭字語など) であると想定しています。この場合、クラス名はおそらくEGSBase
and EGSParser
. ところで、この名前はあまり好きではありません。これはEGSBase
、特定の種類の XML データをキャプチャするように設計されたクラスですが、一般的な名前が付けられているためです。のような一般的なデータ構造をNSMutableDictionary
使用するか、カスタム クラスを使用して意味のある名前を付けます (たとえば、「製品」への参照に基づいて、これは製品であると想定しているためEGSProduct
、以下の例では名前を変更しました.) 任意の名前を付けますが、意味のあるプロパティ名を使用している場合は、意味のある名前を付けてください。
プロパティをサポートするためにインスタンス変数を定義していることに気付きました。1 ~ 2 年前はこれが一般的な規則でしたが、最新バージョンのコンパイラではこれが不要になり、お勧めできません。あなたが持っているものは機能しますが、もはやベストプラクティスとは見なされず、お勧めできません (1 つのタイプミスでインスタンス変数が重複し、非常に奇妙な動作になる可能性があるため)。要するに、プロパティのインスタンス変数を定義しないでください。コンパイラに任せてください。
で定義されていることに気付きましimg
たegsBase
がIBOutlet
、Interface Builder で何かにリンクされている可能性は非常に低いと思います。まず、それはUIView
子孫でさえありません (それはUIImage
であり、 ではありませんUIImageView
)。2 つ目egsBase
は、それ自体は、Interface Builder コントロールに使用するクラスではありません。要するに、これは Interface Builder アウトレットではないため、 for を使用すると混乱するだけIBOutlet
ですimg
。
を使用していることに気づきましたretain
。ARC コードを書いているのであれば、おそらくそうあるべきstrong
です (たとえ iOS 4.3 をターゲットにしているとしても)。ARC コードを作成していない場合は、コードの別の場所でリークが発生しています。
プロパティを手動で合成しています。それはできますが、必須ではありません。また、最近のベスト プラクティスは、プロパティのインスタンス変数の先頭にアンダースコアを付けることです。@synthesize
ステートメントを省略すると、先頭のアンダースコアを使用してインスタンス変数が合成されることに注意してください。プロパティを排他的に使用するか (以下で説明)、インスタンス変数を使用するかについては、この分野でいくつかの議論があります。私はあまり気にしませんが、新しい慣習はプロパティのより広範な使用に傾いているかもしれません. ただし、プロパティを使用する場合は、(a)イニシャライザ メソッドと dealloc メソッドでアクセサ メソッドを使用しないでください。(b)プロパティを設定するときは、常にアクセサ メソッドを使用します。
プライベート実装の詳細であるプロパティとインスタンス変数 (特にパーサー用) を定義していることに気付きました。ここでの新しい標準は、.h で定義されたプロパティを、他のクラスがアクセスする必要があるものとして制限することです。クラスのプライベート実装の一部であるその他のプロパティは、通常、.m ファイル自体のプライベート クラス拡張子で定義されます。些細なことですが、この慣習を守れば、将来、クラスを使いやすくなり、パブリック インターフェイスの一部とその一部について、あなた (または他の開発者) が混乱することはありません。プライベート実装の。
loadXMLByURL
クラス自体へのポインターを返すことに気付きました。init
通常、オブジェクトを作成するか、新しいオブジェクトを作成するクラスのみが、それ自体へのポインターを返します。
loadXMLByURL
おそらく、次のいずれかを返す必要がありNSMutableArray
ますnil
。または (b)BOOL
成功/失敗の値。
のようなカスタム クラスの場合、メソッドEGSBase
を作成すると便利です。description
NSLog
それでは、例を挙げましょう。次のような XML があるとします。
<Products>
<products id="0">
<name>name1</name>
<img id="1">http://opentestdrive.com/images/first.png</img>
</products>
<products id="1">
<name>name2</name>
<img id="2">http://opentestdrive.com/images/second.png</img>
</products>
<products id="2">
<name>name3</name>
<img id="3">http://opentestdrive.com/images/img1.png</img>
</products>
<products id="3">
<name>name4</name>
<img id="4">http://opentestdrive.com/images/img2.png</img>
<img-subproduct id="0">http://opentestdrive.com/images/img5.png</img-subproduct>
<img-subproduct id="1">http://opentestdrive.com/images/img4.png</img-subproduct>
</products>
<products id="4">
<name>name5</name>
<img id="5">http://opentestdrive.com/images/img3.png</img>
<img-subproduct id="2">http://opentestdrive.com/images/img3.png</img-subproduct>
<img-subproduct id="3">http://opentestdrive.com/images/img2.png</img-subproduct>
</products>
<products id="5">
<name>name6</name>
<img id="6">http://opentestdrive.com/images/img4.png</img>
</products>
<products id="6">
<name>name7</name>
<img id="7">http://opentestdrive.com/images/img5.png</img>
</products>
</Products>
次にEGSProduct
、次のように定義されます。
// EGSProduct.h
#import <Foundation/Foundation.h>
@interface EGSProduct : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *imageUrlString;
@property (nonatomic, strong) NSString *imageIdentifier;
@property (nonatomic, strong) NSMutableArray *subProducts;
@end
と
// EGSProduct.m
#import "EGSProduct.h"
@implementation EGSProduct
- (NSString *)description
{
NSMutableString *result = [NSMutableString stringWithFormat:@"<EGSProduct %p; name='%@'; imageIdentifier='%@'; imageUrlString='%@'; subProducts=", self, self.name, self.imageIdentifier, self.imageUrlString];
NSMutableArray *subProductDescriptions = [NSMutableArray array];
for (EGSProduct *subProduct in self.subProducts)
{
[subProductDescriptions addObject:[subProduct description]];
}
[result appendFormat:@"%@>", [subProductDescriptions componentsJoinedByString:@"; "]];
return result;
}
@end
パーサーは次のようになります。
// EGSParser.h
#import <Foundation/Foundation.h>
@interface EGSParser : NSObject
@property (nonatomic, strong) NSMutableArray *products;
- (BOOL)loadXMLByURL:(NSURL *)url;
@end
と
// EGSParser.m
#import "EGSParser.h"
#import "EGSProduct.h"
@interface EGSParser () <NSXMLParserDelegate>
@property (nonatomic, strong) NSXMLParser *parser;
@property (nonatomic, strong) NSMutableString *currentElementContent;
@property (nonatomic, strong) EGSProduct *currentProduct;
@property (nonatomic, strong) EGSProduct *currentSubProduct;
@end
@implementation EGSParser
- (BOOL)loadXMLByURL:(NSURL *)url
{
self.parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
self.parser.delegate = self;
return [self.parser parse];
}
#pragma mark - NSXMLParser
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
self.products = [[NSMutableArray alloc] init];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"products"])
{
self.currentProduct = [[EGSProduct alloc] init];
self.currentProduct.imageIdentifier = attributeDict[@"id"];
}
else if ([elementName isEqualToString:@"img-subproduct"])
{
self.currentSubProduct = [[EGSProduct alloc] init];
self.currentSubProduct.imageIdentifier = attributeDict[@"id"];
if (self.currentProduct.subProducts == nil)
{
self.currentProduct.subProducts = [NSMutableArray array];
}
self.currentSubProduct.imageIdentifier = attributeDict[@"id"];
self.currentElementContent = [[NSMutableString alloc] init];
}
else if ([elementName isEqualToString:@"name"] || [elementName isEqualToString:@"img"])
{
self.currentElementContent = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// note, this construct of checking to see if it's nil is actually not needed
// (since sending a message to a nil object, by definition, does nothing)
// but I wanted to draw your attention to the fact that we set `currentElementContent`
// in `didStartElement` and, after saving it in our class, we set it to nil in
// `didEndElement`
if (self.currentElementContent != nil)
[self.currentElementContent appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"name"])
{
self.currentProduct.name = self.currentElementContent; // save the product name
self.currentElementContent = nil; // reset our `currentElementContent`
}
else if ([elementName isEqualToString:@"img"])
{
self.currentProduct.imageUrlString = [self.currentElementContent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
self.currentElementContent = nil;
}
else if ([elementName isEqualToString:@"img-subproduct"])
{
self.currentSubProduct.imageUrlString = self.currentElementContent;
self.currentElementContent = nil;
[self.currentProduct.subProducts addObject:self.currentSubProduct];
self.currentSubProduct = nil;
}
else if ([elementName isEqualToString:@"products"])
{
[self.products addObject:self.currentProduct];
self.currentProduct = nil;
}
}
@end
このクラスを使用するには、次のようにします。パーサーが完了した (そして範囲外になった) 後で XML の結果を保持したい場合は、クラス プロパティ (以下の例では、xmlProductsResults
.ビュー コントローラーの場合、このモデル データを共有クラス (モデル シングルトンなど)、アプリ デリゲートのプロパティに保存するか、パラメーターとしてビュー コントローラーからビュー コントローラーに渡します。
NSURL *url = [NSURL URLWithString:@"http://opentestdrive.com/Products.xml"];
EGSParser *parser = [[EGSParser alloc] init];
if ([parser loadXMLByURL:url])
{
NSLog(@"success; products=%@", parser.products);
self.xmlProductsResults = parser.products;
}
else
{
NSLog(@"fail");
}
パーサーを使用すると、さらに多くのことができます (不適切な形式の XML ファイルのより堅牢な検証、エラーがあればそれを報告するなど)。個人的には、私は十分な XML 構文解析を行ったので、扱っている XML ファイルのほとんどを数行で構文解析できる非常に汎用的なパーサーを使用し、パーサー クラスを最大限に再利用していますが、これでは橋渡しが遠すぎます。この会話のために。私はこの議論ですでに行き過ぎています。