2012年6月30日土曜日

since_idの決め方

相変わらず仕事がつまってて全然いじれてないけど、今日は出張でいつもよりは早く帰れたからsince_idでの永久掘削続き。たまに(場合によってはしょっちゅう)loadingにすげー時間がかかることがあり、最初はネットワークの調子が悪いのかapiのサーバがヘタってるのかなどと考えていたが、いろいろ検証してみるとどうやらsince_idの確定アルゴリズムで遅延が起こってる様子。なーんだ自分のせいか安心した。

since_idを確定できるのは、lastidがおかわりしたJSONの中に含まれ、且つlastidとJSONの最後のidが等しくない場合なので、それ以外のケースでは、
1.lastidよりおかわりしたJSONの最初のidが小さい場合
2.lastidよりおかわりしたJSONの最後のidが大きい場合
に切り分けて、1の場合はlastidとJSONの最初のidの差分をsince_idに+=し、2の場合は逆に-=して再度おかわりして確定できるまで繰り返していた。さらに1の場合だと+=した際にsince_idがlastidを超えるという馬鹿なケースが起こるので、その場合は前述のとおりid乖離平均にlimit+1をかけた数値を半分(それでも多ければ戻ってくるたびに3分の1、4分の1〜)していた(これを3とする)。

上記アルゴリズムで、たまに(場合によってはしょっちゅう)以下の2パターンの遅延が発生していた。
A.1を数十回繰り返してもなかなかJSONの最初のidがlastidを超えない
B.1と2を行ったり来たり、または1と3を行ったり来たりする
Aについては、まあ待ってれば確定するが、それでも分単位でloadingが終わらないのは精神衛生上よくないしサーバにも優しくない。Bはさらに致命的で、ほぼ無限ループに近い状態になる。
Aは+=する値を倍々してスピードを上げ、Bは行き過ぎた分戻すだけなのでスピードを下げておかわりする。コードは以下のとおり。
- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 loadflg=NO;
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 long long topid=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue];
 int skip=0;
 if(offset+limit>250){
  if(lastid>topid){
   if(adjflg==1){adj2=2;since_id+=(lastid-topid)*adj1++;}
   else if(adjflg==2){adj1=2;since_id+=(lastid-topid)/adj2++;}
   else since_id+=lastid-topid;
   adjflg=1;
   if(since_id>=lastid){adj1=2;adj2=2;since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1))/adj3++;}
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];
   return;
  }else if(lastid<=[[[bd valueForKeyPath:@"response.posts.id"]lastObject]longLongValue]){
   if(adjflg==2){adj2=2;since_id-=(topid-lastid)*adj1++;}
   else if(adjflg==1){adj1=2;since_id-=(topid-lastid)/adj2++;}
   else since_id-=topid-lastid;
   adjflg=2;
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];
   return;
  }else{
   adjflg=0,adj1=2,adj2=2,adj3=2;
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }else if(lastid!=0&&lastid<=topid){
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 //type別処理は例によって割愛
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

できた。1100postくらいまでは動いてたけどerror code=12が出たのでメモリ関連の処理を調べる。postのuiviewで古いやつは配列から消したほうがいいのかな。
ランは遅かったので7.22km@37:51だけ走った。ラーメンは今月まだ10杯。ペース半減。体重はそれほど落ちず。

2012年6月19日火曜日

parmanent vacation(改)

引き続きsince_idによる永久掘削を検証してたら、どうも動作がおかしかったので修正。

1.JSONをおかわりする際のsince_idを決めるのに、単にlimit分新しいidとlastidの差分をlastidから引くとむらが大きかったので結局id乖離平均にlimit+1(1足すのはsince_idは自分を含まないため)をかけた数値をlastidから引くようにした。
2.おかわりしたJSONのトップのidがlastidより小さい場合はpostの欠落を防ぐためにわざと重複させるようlastidとトップのidの差をsince_idに+=してたけど、since_idがlastidを超えたり、更には最新のpostidよりでかくなったりと傍若無人な感じになってたのでlastidを超える場合は一旦id乖離平均にlimit+1をかけた数値を半分(それでも多ければ戻ってくるたびに3分の1、4分の1〜)にしてlastidから引いてreqJSON。
3.lastidがおかわりしたJSONの最後のidより小さい場合がなぜか発生するので、その場合の対応を追加。lastidとおかわりしたJSONのトップのidの差分をsince_idに-=してreqJSONする。

これでひとまず1000postsまで検証したが、特に問題なく動いている。と思う。

- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 int skip=0;
 if(!offsinceflg){
  if(lastid>[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
   since_id+=(lastid-[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]);
   if(since_id>=lastid) since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1))/adj++;
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else if(lastid<=[[[bd valueForKeyPath:@"response.posts.id"]lastObject]longLongValue]){
   since_id-=([[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]-lastid);
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else{
   adj=2;
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }else if(lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 //type 別の処理は割愛
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  if(offset+limit<=50){
   offsinceval=[NSString stringWithFormat:@"%d",offset+=limit];
   }else{ 
   offsinceflg=NO;
   //since_id=2*lastid-[[idArr objectAtIndex:[idArr count]-limit-2]longLongValue];
   since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1));
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];
  }
   [self reqJSON];
 }
}
できた。次はついにreblogとlikeやるか。ジェスチャーの埋め込みもやらねば。

夜は台風の中の神戸出張帰りにかねてより気になってたすっぽんラーメン太尊で超すっぽんラーメン+雑炊。雑炊が150円の割りにどっさり3人前くらい出てきたので焦ったが相当美味かったが体重が昨日より1.4kg増えた。

2012年6月17日日曜日

parmanent vacation

offsetが250を超えた場合の処理を完了。意外にあっさり動いた。
id間の差の平均がどうのこうのとややこしいことはせず、
最後のidとそこからlimit分新しいidとの差を最後のidから引いてsince_idにセットしてreqJSON。
帰ってきたidのトップが最後のidより古ければ最後のidとトップのidとの差をsince_idに足して再度reqJSON。
最後のidよりトップのidが新しくなるまで繰り返し、重複分をスキップしてパース。
アルゴリズムとして無駄なことはしていない、はず。

- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if(offsinceflg&&lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  NSLog(@"cho-fuku");
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 int skip=0;
 if(!offsinceflg){
  if(lastid>[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
   since_id+=lastid-[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue];
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else{
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }
 for(int i=skip;i<[[bd valueForKeyPath:@"response.posts.type"]count];i++){
  [idArr addObject:[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:i]];
  [typeArr addObject:[[bd valueForKeyPath:@"response.posts.type"]objectAtIndex:i]];
  [bnameArr addObject:[[bd valueForKeyPath:@"response.posts.blog_name"]objectAtIndex:i]];   
  //type別の処理は割愛
 }
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  if(offset+limit<=250){
   offset+=limit;
   offsinceval=[NSString stringWithFormat:@"%d",offset];
   [self reqJSON];
  }
  else{ 
   offsinceflg=NO;
   since_id=2*[[idArr lastObject]longLongValue]-[[idArr objectAtIndex:[idArr count]-1-limit]longLongValue];
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];
   [self reqJSON];
  }
 }
}
できた。これで永遠にdashboardの深淵へ潜っていけるはず。
ifのネストとか嫌いやけどとりあえずこれで様子を見ていけてそうならコードは綺麗にする。
では今から小一時間ランしてくる。

2012年6月15日金曜日

idの取り扱い

最近仕事がハードでぜんぜんxcodeできない。
とりあえず、idの重複は回避。
- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if(lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  offset++;[self reqJSON];return;
 }
 //以下略
}
あとはoffsetが250を超えた場合の処理。 まずは250個のidがそれぞれどれくらい離れてるかの平均をとって、それにlimitの件数をかけた値をlastidから引いてsince_idにセットしてreqJSONして、帰ってきたJSONの一番上のidがlastidより古ければ平均を足して再度reqJSONしてlastid(またはそれより新しいid)と重複したら重複した分だけ削ってパースしてpostのviewを作る感じでいけば、postの欠落は防げるかな。とりあえず平均を取るところまで。
- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  offset+=limit;
  if(offset<=250) [self reqJSON];
  else{ 
   if(iddiff==0) iddiff=([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1);
   NSLog(@"%lld",iddiff);
  }
 }
}
できた。
今日は丹後半島出張で、帰ってきて福島のみつ星製麺所で濃厚らーめん半玉増し白ごはん。濃い。今年100杯超えてるのでペースは押さえ気味(100杯目は姫路の丸十)。今月5杯目。しかも夜久々にランした。10km弱。半年ラーメン三昧だった分、絞るぞ。

2012年6月5日火曜日

single post

とりあえずsingle postのUIViewをサブクラスにしてみた。linkを開くためにUIWebViewを上位のviewに貼り付けるのにちょっとまごついたけど、self.superview.superviewにaddsubviewすることでrootのviewに無事貼れた。
夜は久々に芦原橋のふじいで中華そばと半チャーハン。美味い。前回は誰もおらんかったけど今日はすげー混んでた。

2012年6月4日月曜日

tasks

とりあえず現時点での積み残しをまとめておく。

・single postのUIViewをサブクラスに抜き出す
・閲覧中にdashboardにpostが追加された場合のoffset調整(ID重複の解消)
・offset250以上のdashboardの対応
・reblog,likeの実装
・スワイプでのpost移動の実装
・ダブルタップでのreblog,トリプルでのlikeの実装
・keyboardからjktでのオペレーション実装
・timelineスタイルのpostのUIViewサブクラスを作成
・setting画面の実装

山盛り。いっこいっこ潰していくしか無いなー。

2012年6月3日日曜日

type:answer

ついに最後のpost typeに到達。長かった。ものすごく冗長な感じがするが、仕方ない。

}else if([[typeArr objectAtIndex:i]isEqualToString:@"answer"]){
   UIView *ansBase=[[UIView alloc]init];
   UIImage *queimg=[UIImage imageNamed:@"question"];
   UIImageView *queimgv=[[UIImageView alloc]initWithImage:queimg];
   [queimgv setFrame:CGRectMake(10,0,queimg.size.width,queimg.size.height)];
   [ansBase addSubview:queimgv];
   UILabel *que=[[UILabel alloc]init];que.numberOfLines=0;
   que.text=[self deltag:[[contArr objectAtIndex:i]objectAtIndex:0]];que.font=sf(15);
   CGSize quesize=[que.text sizeWithFont:que.font constrainedToSize:CGSizeMake(sw-80,sh*3) lineBreakMode:UILineBreakModeTailTruncation];
   que.textColor=[UIColor blackColor];que.backgroundColor=[UIColor clearColor];que.frame=CGRectMake(70,0,quesize.width,quesize.height);
   [ansBase addSubview:que];
   UIImage *line=[UIImage imageNamed:@"gray"];
   UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.7;
   [linev setFrame:CGRectMake(10,(quesize.height>50?quesize.height:50)+10,sw-20,4)];
   [ansBase addSubview:linev];
   UIImage *ansimg=[UIImage imageNamed:@"answer"];
   UIImageView *ansimgv=[[UIImageView alloc]initWithImage:ansimg];
   [ansimgv setFrame:CGRectMake(10,linev.frame.origin.y+14,ansimg.size.width,ansimg.size.height)];
   [ansBase addSubview:ansimgv];
   UILabel *ans=[[UILabel alloc]init];ans.numberOfLines=0;
   ans.text=[self deltag:[[contArr objectAtIndex:i]objectAtIndex:1]];ans.font=sf(15);
   CGSize anssize=[ans.text sizeWithFont:ans.font constrainedToSize:CGSizeMake(sw-80,sh*3) lineBreakMode:UILineBreakModeTailTruncation];
   ans.textColor=[UIColor blackColor];ans.backgroundColor=[UIColor clearColor];
   ans.frame=CGRectMake(70,linev.frame.origin.y+14,anssize.width,anssize.height);
   [ansBase addSubview:ans];   
   //npy+=npsize.height+20;j++;
   UIScrollView *ansScr=[[UIScrollView alloc]initWithFrame:CGRectMake(0,30,sw,sh-50)];[postv addSubview:ansScr];
   [ansScr addSubview:ansBase];
   [ansScr setContentSize:CGSizeMake(sw-20,(quesize.height>50?quesize.height:50)+4+(anssize.height>50?anssize.height:50)+40)];
  }

できた。
あとはreblog,likeの処理、ID重複処理を終わらせて、timeline styleも作ってみるか。

type:video(続)

UIWebViewにvideoのエンベッド用コードを表示させた。Flash requiredなvideoはwidth、heightが埋め込まれてないので、そいつはnot availableとして対処。それ以外はクリックしたらムービーのプレーヤーが立ち上がる。

  }else if([[typeArr objectAtIndex:i]isEqualToString:@"video"]){
   NSError *error;
   NSRegularExpression *wpat=[NSRegularExpression regularExpressionWithPattern:@"width=['\"]?([0-9]+)" options:0 error:&error];
   NSRegularExpression *hpat=[NSRegularExpression regularExpressionWithPattern:@"height=['\"]?([0-9]+)" options:0 error:&error];
   int w,h;
   if(!error) {
    NSTextCheckingResult *wmatch=reg(wpat),*hmatch=reg(hpat);
    w=[[[contArr objectAtIndex:i]substringWithRange:[wmatch rangeAtIndex:1]]intValue],
    h=[[[contArr objectAtIndex:i]substringWithRange:[hmatch rangeAtIndex:1]]intValue];
   }
   if(!w||!h){
    UIImage *na=[UIImage imageNamed:@"na"];
    UIImageView *nav=[[UIImageView alloc]initWithImage:na];
    [nav setFrame:CGRectMake((sw-na.size.width)/2,(sh-na.size.height)/2,na.size.width,na.size.height)];
    [postv addSubview:nav];
   }else{
    UIWebView *video=[[UIWebView alloc]initWithFrame:CGRectMake((sw-w)/2,(sh-h)/2,w,h)];
    [video loadHTMLString:[contArr objectAtIndex:i] baseURL:nil];
    [postv addSubview:video];
   }
  }

できた。
昨日は職場の歓送迎会でラーメンは食べれず。先月末はさるぱぱ@河内永和で中華そば味付玉子ネギ。高井田系ではかなり上位の美味しさ。これで先月24杯、今年に入って96杯。やばいな。100杯いったらちょっと自粛するか。