iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
オートマティズムのレスポンスパーサを参考に、独自のカスタマイズを施している。
通信のように外部とのやり取りを行う場合、Cocoa touchでは、実装された時期によって、自分でスレッド化したり、デリゲートだったり、キューやGCDだったりして、単純にAPIに沿うと、機能毎に実装が異なる事になってしまう。レスポンスパーサをそれを共通の形にすると自分は理解している。
ヘッダーファイルを見てみよう。
オートマティズムでは、デリゲートとなるコネクタに対して、デリゲート・メソッド呼び出しで結果を伝えているが、それを呼び出し元が用意したブロックを呼び出すようにカスタマイズをしている。
#import <Foundation/Foundation.h>
@class RFCResponseParser;
#define kRFCResponseParserNoError 0
#define kRFCResponseParserGenericError 1
typedef enum _RFCNetworkState {
kRFCNetworkStateNotConnected = 0,
kRFCNetworkStateInProgress,
kRFCNetworkStateFinished,
kRFCNetworkStateError,
kRFCNetworkStateCanceled,
} RFCNetworkSate;
typedef void (^RFCResponseParserCompletionHandler)(RFCResponseParser *parser);
@protocol RFCResponseParserDelegate
- (void)parser:(RFCResponseParser*)parser didReceiveResponse:(NSURLResponse*)response;
- (void)parser:(RFCResponseParser *)parser didReceiveData:(NSData *)data;
- (void)parserDidFinishLoading:(RFCResponseParser *)parser;
- (void)parser:(RFCResponseParser *)parser didFailWithError:(NSError*)error;
- (void)parserDidCancel:(RFCResponseParser *)parser;
@end
@interface RFCResponseParser : NSObject
@property (assign, readonly, nonatomic) RFCNetworkSate networkState;
@property (assign, nonatomic) NSUInteger index;
@property (strong, nonatomic) NSError *error;
@property (strong, nonatomic) NSOperationQueue *queue;
@property (weak, nonatomic) id delegate;
@property (copy, nonatomic)RFCResponseParserCompletionHandler completionHandler;
@property (strong, readonly, nonatomic) NSArray *indexArray;
@property (strong, readonly, nonatomic) NSString *rfc;
- (void)parse;
- (void)cancel;
@end
URL通信という事で、通常はメインスレッドでデリゲートの非同期の処理という事になるが、先日のCocoa勉強会で教えてもらった、キューを使っている。これで、メインスレッド以外のスレッドで動作する。
#import "Document.h"
#import "RFCResponseParser.h"
@interface RFCResponseParser ()
@property (assign, readwrite, nonatomic) RFCNetworkSate networkState;
@property (strong, readwrite, nonatomic) NSArray *indexArray;
@property (strong, nonatomic) NSURLConnection *urlConnection;
@property (strong, nonatomic) NSMutableData *downloadedData;
- (void)_notifyParserDidFinishLoading;
- (void)_notifyParserDidFailWithError:(NSError*)error;
- (NSError *)_errorWithCode:(NSInteger)code localizedDescription:(NSString *)localizedDescription;
- (void)_parseIndexArray;
@end
@implementation RFCResponseParser
@synthesize networkState = _networkState;
@synthesize index = _index;
@synthesize error = _error;
@synthesize queue = _queue;
@synthesize delegate = _delegate;
@synthesize completionHandler = _completionHandler;
@synthesize indexArray = _indexArray;
@synthesize urlConnection = _urlConnection;
@synthesize downloadedData = _downloadedData;
- (id)init
{
DBGMSG(@"%s", __func__);
self = [super init];
if (self) {
_networkState = kRFCNetworkStateNotConnected;
_index = 0;
_error = nil;
_queue = nil;
_delegate = nil;
_completionHandler = NULL;
_indexArray = nil;
_urlConnection = nil;
_downloadedData = nil;
}
return self;
}
- (void)dealloc
{
DBGMSG(@"%s", __func__);
self.networkState = kRFCNetworkStateNotConnected;
self.index = 0;
self.error = nil;
self.queue = nil;
self.delegate = nil;
self.completionHandler = NULL;
self.indexArray = nil;
self.urlConnection = nil;
self.downloadedData = nil;
}
- (void)parse
{
DBGMSG(@"%s", __func__);
NSString *urlString = nil;
if (self.index == 0) {
urlString = [Document sharedDocument].indexUrlString;
}
else {
urlString = [[Document sharedDocument] rfcUrlStringWithIndex:self.index];
}
NSURLRequest *urlRequest = nil;
if (urlString) {
NSURL *url;
url = [NSURL URLWithString:urlString];
if (url) {
urlRequest = [NSURLRequest requestWithURL:url];
}
}
DBGMSG(@"%s urlString(%@)", __func__, urlString);
if (! urlRequest) {
self.networkState = kRFCNetworkStateError;
self.error = [self _errorWithCode:kRFCResponseParserGenericError
localizedDescription:@"NSURLRequestの生成に失敗しました。"];
return;
}
self.downloadedData = [[NSMutableData alloc] init];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[self.urlConnection setDelegateQueue:self.queue];
[self willChangeValueForKey:@"networkState"];
self.networkState = kRFCNetworkStateInProgress;
[self didChangeValueForKey:@"networkState"];
[self.urlConnection start];
}
- (void)cancel
{
DBGMSG(@"%s", __func__);
[self.urlConnection cancel];
self.downloadedData = nil;
[self willChangeValueForKey:@"networkState"];
self.networkState = kRFCNetworkStateCanceled;
[self didChangeValueForKey:@"networkState"];
if ([self.delegate respondsToSelector:@selector(parserDidCancel:)]) {
[self.delegate parserDidCancel:self];
}
self.urlConnection = nil;
}
- (NSString *)rfc
{
DBGMSG(@"%s", __func__);
NSString *result = [[NSString alloc] initWithData:self.downloadedData encoding:NSUTF8StringEncoding];
return result;
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
if ([self.delegate respondsToSelector:@selector(parser:didReceiveResponse:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate parser:self didReceiveResponse:response];
});
}
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
[self.downloadedData appendData:data];
if ([self.delegate respondsToSelector:@selector(parser:didReceiveData:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate parser:self didReceiveData:data];
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
[self willChangeValueForKey:@"networkState"];
self.networkState = kRFCNetworkStateFinished;
[self didChangeValueForKey:@"networkState"];
[self _parseIndexArray];
dispatch_async(dispatch_get_main_queue(), ^{
[self _notifyParserDidFinishLoading];
});
self.urlConnection = nil;
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
self.error = error;
[self willChangeValueForKey:@"networkState"];
self.networkState = kRFCNetworkStateError;
[self didChangeValueForKey:@"networkState"];
dispatch_async(dispatch_get_main_queue(), ^{
[self _notifyParserDidFailWithError:error];
});
self.urlConnection = nil;
}
- (void)_notifyParserDidFinishLoading
{
if ([self.delegate respondsToSelector:@selector(parserDidFinishLoading:)]) {
[self.delegate parserDidFinishLoading:self];
}
}
- (void)_notifyParserDidFailWithError:(NSError*)error
{
if ([self.delegate respondsToSelector:@selector(parser:didFailWithError:)]) {
[self.delegate parser:self didFailWithError:error];
}
}
- (NSError *)_errorWithCode:(NSInteger)code localizedDescription:(NSString *)localizedDescription
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:localizedDescription forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"RFCViewer" code:code userInfo:userInfo];
return error;
}
:
@end
キューを使って別スレッドで動作させる場合に気をつけないと行けないのは、呼び出し元に戻す際は、メインスレッドでということだ。そこで、メインスレッドで動作する- _notifyParserDidFinishLoadingと- _notifyParserDidFailWithErrorのメソッドを用意して、それをdispatch_async()関数でメインスレッドで呼びようにしている。
別スレッドで動作する事の副作用で、読み込んだRFCのインデックス情報の解析には、それなりに時間があかかると思うが、この処理が別スレッドで実行される為、UIのレスポンスへの影響が小さくなっている。
- (void)_parseIndexArray
{
DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
NSMutableArray *indexArray = [[NSMutableArray alloc] init];
NSString *indexString = nil;
NSString *parsedString = nil;
NSRange range, subRange;
NSUInteger length;
__block BOOL isCreatedOn = NO;
BOOL isIndex = NO;
NSMutableString *rfcString = nil;
indexString = [[NSString alloc] initWithData:self.downloadedData
encoding:NSUTF8StringEncoding];
length = [indexString length];
range = NSMakeRange(0, length);
while (0 < range.length) {
/* 行単位の取り出し */
subRange = [indexString lineRangeForRange:NSMakeRange(range.location, 0)];
parsedString = [indexString substringWithRange:subRange];
/* 改行文字削除 */
NSCharacterSet *chSet = nil;
NSScanner *scanner = nil;
chSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
scanner = [NSScanner scannerWithString:parsedString];
if (![scanner isAtEnd]) {
NSString *line = nil;
[scanner scanUpToCharactersFromSet:chSet intoString:&line];
parsedString = line;
}
else {
parsedString = @"";
}
//DBGMSG(@"[%@]", parsedString);
/* 更新日付に到着 */
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\(CREATED ON: (\\d{2}/\\d{2}/\\d{4})\\.\\)"
options:NSRegularExpressionCaseInsensitive
error:&error];
[regex enumerateMatchesInString:parsedString
options:0
range:NSMakeRange(0, parsedString.length)
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
if (match.numberOfRanges) {
isCreatedOn = YES;
}
}];
/* 目次に到達 */
if ((isCreatedOn) && ([parsedString isEqualToString:@"RFC INDEX"])) {
isIndex = YES;
}
else if (! isIndex) {
}
/* 区切り */
else if ([parsedString isEqualToString:@""]) {
if (rfcString) {
/* 表題の取り出し */
//DBGMSG(@"%@", rfcString);
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^([0-9]{4}+)\\s(.+?\\.)"
options:NSRegularExpressionCaseInsensitive
error:&error];
__block NSString *rfcNumber = nil;
__block NSString *title = nil;
[regex enumerateMatchesInString:rfcString
options:0
range:NSMakeRange(0, rfcString.length)
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
//NSRange matchRange = [match range];
NSRange firstHalfRange = [match rangeAtIndex:1];
NSRange secondHalfRange = [match rangeAtIndex:2];
rfcNumber = [rfcString substringWithRange:firstHalfRange];
title = [rfcString substringWithRange:secondHalfRange];
}];
RFC *rfc = [[RFC alloc] init];
rfc.rfcNumber = rfcNumber;
rfc.title = title;
//DBGMSG(@"%@ : %@", rfcNumber, rfc.title);
[indexArray addObject:rfc];
}
rfcString = nil;
}
/* 先頭 */
else {
NSRange match = [parsedString rangeOfString:@"^[0-9]{4}+\\s" options:NSRegularExpressionSearch];
if (match.location != NSNotFound) {
//DBGMSG(@"++++先頭");
rfcString = [[NSMutableString alloc] initWithString:parsedString];
}
else if (rfcString) {
[rfcString appendString:parsedString];
}
}
range.location = NSMaxRange(subRange);
range.length -= subRange.length;
}
self.indexArray = indexArray;
}