swift – itunes store にサインイン画面が何度も表示される

iPhoneアプリ開発中にStoreKitの購入処理をデバッグしていたら、アプリ起動時にサインインを求められる画面が表示されるようになった。これが表示されるようになった原因は、直前の購入処理がうまくいかず、購入の処理が終わってないことが原因だと思われる。トランザクションの終了処理が未実行となっているのだと予想。

だが、アプリ起動時にいきなりストアへのサインイン画面が呼ばれるというのがわからない。何のメソッドが呼ばれてサインイン画面が表示されているのかというのがわからないので手の打ちようがない。アカウントをテストユーザーでサインインしていたが、別のテストユーザーでサインインしたところ起動時のサインイン画面には前のユーザー名が表示されて消えないという現象も確認した。

現在のトランザクションの数を数えてみる。下記がレストア終了した後に呼ばれるメソッドでキューの数をコンソールに表示してみたら6という数字になった。おそらくこれが原因で起動時にサインイン画面が表示されていたのではないかと思う。

    func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {

        print(queue.transactions.count)

    }

溜まったトランザクションに対して終了処理を実行する。とりあえず上記メソッドに一回だけ下記のようなコードを追加・実行する

    func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
        
        for transaction:SKPaymentTransaction in queue.transactions{
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        }
        
        print(queue.transactions.count)
    }

上記を実行すると溜まっていたトランザクションはとりあえず消える。で、再度アプリを起動させてみたところ、やはりサインイン画面は消えなくて相変わらず表示される。その後サインアウトしたり別のアカウントでサインインしてみたりとやっているうちに、起動時に表示されていた意味不明のサインイン画面は表示されなくなった。結局、明確な解決方法はわからずじまい。未終了のトランザクションが溜まっていてそれを削除したのは関係がありそうだけれども、なんとも言えない。

xcode – appstore – file must contain a higher version than that of the previously uploaded version

This bundle is invalid. The key CFBundleVersion in the Info.plist file must 
contain a higher version than that of the previously uploaded version.

バージョン1.13のバイナリを新しくアップデートしてバージョン1.2をapp storeに登録しようとしたら上記エラーが出て怒られた。appstoreの解釈では1.13 > 1.2ということになっているらしい。不本意ながら仕方がないのでバージョンを1.20としてビルドしてアップロードしたらエラーなく通った。

swift – SKNodeからSKSpriteNodeへ変換

SKViewからnodeAtPointで取得したノードはSKNodeとして受け取るため、それをSKSpriteNodeにキャストする。Objective-cの時はAnyObjectに突っ込んでからキャストしてればよかったがswiftだと型の判定が厳しくなっている模様

下記コードはSKNodeからSKSpriteNodeへ変換・・・しているというわけではない。SKSpriteNodeであると確信があるので決め打ちしてキャストしているだけ。

サンプルコード

override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
        
        for touch: AnyObject in touches {
            
            var touchPos: CGPoint = touch.locationInNode(self)
            var node = self.nodeAtPoint(touchPos)

            if (node.name == "test"){
                        let sknode :SKSpriteNode = (node as? SKSpriteNode)!
            }
        }
}

swift – app内課金の実装

itunes connectやdeveloper centerで下準備は済ませているものとして。コードの実装部分のみ。課金の商品は1度きりの購入のよくある広告非表示の商品をイメージ。
処理の流れとしては、viewDidLoadにて初めの画面表示の時に製品IDで商品情報を取得して、ボタンを押したら購入処理を開始するというもの。商品が一つだけなら下記のような感じにできるが、普通は複数でテーブルビューなどに表示して配列で商品管理をやっている模様。出来るだけ簡単で少ないコードでの実装を目指す・・・

購入処理

デリゲートの追加

class ClassName: UIViewController SKProductsRequestDelegate, SKPaymentTransactionObserver{

}

必要なデリゲートメソッド

func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

}

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){

}

実装例

import UIKit
import StoreKit

class ClassName: UIViewController SKProductsRequestDelegate, SKPaymentTransactionObserver{

var storeBt:UIButton!
var productID = “com.sample.product100”
var vProduct: SKProduct!

override func viewDidLoad() {

storeBt = UIButton()
storeBt.frame = CGRectMake(0, 0, 200, 22)
storeBt.setTitle(“”, forState: UIControlState.Normal)
storeBt.setTitleColor(UIColor.orangeColor(), forState: UIControlState.Normal)
storeBt.setTitle(“”, forState: UIControlState.Highlighted)
storeBt.setTitleColor(UIColor.blackColor(), forState: UIControlState.Highlighted)
storeBt.titleLabel!.adjustsFontSizeToFitWidth = true
storeBt.addTarget(self, action: “byAc:”, forControlEvents: .TouchUpInside)
self.view.addSubview(storeBt)

SKPaymentQueue.defaultQueue().addTransactionObserver(self)
getProductInfo()

}

func getProductInfo(){
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers:
NSSet(objects: self.productID) as! Set)
request.delegate = self
request.start()
} else {
print(“Please enable In App Purchase in Settings”)
}

func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

let count : Int = response.products.count
if (count>0) {
var validProducts = response.products
var validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.produto_value) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
vProduct = validProduct

//
storeBt.setTitle(“\(validProduct.localizedTitle) \(validProduct.price)円”, forState: UIControlState.Normal)
storeBt.setTitle(“\(validProduct.localizedTitle) \(validProduct.price)円”, forState: UIControlState.Highlighted)

} else {
print(validProduct.productIdentifier)
}

} else {
print(“not found”)
}
}

func byAc(sender: AnyObject){

if(vProduct != nil){
let payment = SKPayment(product:vProduct)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
}

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){

for transaction: SKPaymentTransaction in transactions {
switch transaction.transactionState {
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “失敗”)

case .Purchasing:
NSLog(“%@”, “購入中”)

case .Restored:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “リストア”)

case .Purchased:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “成功”)

// レシートを取得して検証
var receiptURL: NSURL = NSBundle.mainBundle().appStoreReceiptURL!
var receiptData: NSData = NSData(contentsOfURL: receiptURL)!
var base64receiptData: String = receiptData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
NSLog(“%@”, base64receiptData)

default:
print(transaction.transactionState.rawValue)
break
}
}
}
}

touchesBeganとtouchesEndedの間にある遅延について

iOSでtouchesBeganイベントが呼ばれてtouchesEndedが呼ばれるまでの間に約0.4秒ほどの遅延があることがわかった。(マシンの調子が悪い時は0.5秒ほど)
touchesBeganからtouchesMovedイベントまでの遅延はほとんど気にならない程度。
この0.4秒の遅延は体感ではまあ普通のアプリでは反応悪いなと許容できる範囲の時間ではあるけれども、応答の即時性が求められるアプリではちょっと長すぎる。この時間をなんとかゼロにできないものかといろいろ調べてみたが、見つからなかった。touchesEndedイベントに必要なディレイ時間として設定されていてプログラミングでプロパティを変更とかできないものかもしれない。
例えば画面をポンッとタップしてその結果がすぐにわかる必要があるような実装をする場合、処理をtouchesEndedに書いていたら0.4秒の遅延が発生するので使えない。この操作だとtouchesMovedイベントも呼ばれない。仕方がないのでtouchesBeganだけで完結するような実装にすることにした。

四国銀行がオンラインバンキングでハードウェアトークンを始めた様子

ワンタイムパスワード(ハードウェアトークン)の取扱い開始 | 四国銀行

四国銀行が振り込みと振替をオンラインバンキングでする場合にはワンタイムパスワードを必須にする様子。ハードウェアで短時間だけ有効なパスワードを生成する仕組みで、はっきりいうとジャパンネット銀行のパクリである。ハードウェアトークンの見た目もシール張り替えただけのそのままのように見える。

ワンタイムパスワード(ハードウェアトークン)とは

パスワード生成機(ハードウェアトークン)により生成・表示される1分ごとに変化する使い捨てのパスワードです。ワンタイムパスワードは1分後には使えなくなることから、第三者に遠隔操作等で不正にログインされるリスクを低減することができます。

振り込みの時にこのワンタイムパスワードを使うらしいから不正ログインのリスクとは関係がないような気がするが・・・。個人的には四国銀行のオンラインバンキングでこんなハイテクそうなことをやって大丈夫なのかという心配がある。昔の四国銀行のオンラインバンキングのUIは素人丸出しで絶対ホームページ・ビルダーで作ってるだろこれと思いながら私は使っていた。それを知っているからか残高照会以外の取引を四国銀行のオンラインバンキングでやるのはちょっと怖い。オンラインバンキングのアドレスを見るとwww.parasol.anser.ne.jpというドメインで運用されている様子でセキュリティ証明書はNTT DATA となっている。ジャパンネット銀行みたいなオンライン専門の銀行じゃないんだからあまり無茶はしないほうがいいとおもうんだけれども。

それにしても、ジャパンネット銀行をオンライン決済で利用している私はこのトークンをすでに使っていて、さらに四国銀行のトークンも管理しなければならないというのは、なんかセキュリティのためとはいえ馬鹿っぽい気がする。私は他にゆうちょ銀行や楽天銀行の口座も持っているが、それらの銀行がもしもトークンの利用開始を始めてしまったらたまったもんではない。財布に溜まっていくコンビニや量販店のポイントカードじゃあるまいし。

メインビューとサブビューの座標の違い

メインビューでaddsubviewにてuiviewを追加して、ボタンを押したら別のビューが下からビョーンって出てくるようなものを考えていたが、座標の問題で困った。メインビューの座標とサブビューの座標はそれぞれ存在しているため、メインビューでタップしたポジションでサブビュー上のパーツを動かそうとしたら、当然おかしなことになる。このサブビューを下から少しだけ飛び出すような使い方だと座標の変換が多分必要になる。あと、サブビューのパーツを画面外のメインビューへ飛び越えるような使い方は、多分無理な予感。試してはないけれども、普通の感覚では不自然。

下からビューを飛び出してきてその上のパーツをいろいろ動かすのは、擬似的にすることにした。つまりビューだけ下から表示して、その上のパーツはメインビューにaddsubview()するということ。つまり見た目の上ではテクニカルなことをなんかやっているように見えるだけ。たいていの場合こういうのは設計が悪くて他にもっとスマートな解決方法があるはず。なんか無理やり実装してる感が出てきたら経験上よろしくない。後々のメンテナンス性が劣悪になったりとか。

Ubuntu ServerでBusyBoxとか出てフリーズ、起動できなくなった

mount: mounting /sys on /root/sys failed: No such file or directory
mount: mounting /proc on /root/proc failed: No such file or directory
Target filesystem doesn't have requested /sbin/init.
No init found. Try passing init = bootarg.

BusyBox v1.1.3 (Ubuntu 1:1.1.3-1ubuntu11) built-in shell (ash)
Enter 'help' for a list o built-in commands.

(initranfs)

エラーが出るまでは普通に稼働していたUbuntu Server12.04が突然フリーズして上記エラーを起動時に表示するようになり、そこから進まなくなった。No such file or directoryの部分を見て直感でこのエラーはかなりまずいかなと思ってググってみたら、LiveCDでfsckを実行すれば治るとかあったので、参考に自分もやってみることにした。まずはHDDが壊れていないことを祈って今取り付けているポンコツより高性能なマシンに載せかえる(intel atomという低スペcpuでサーバー稼働していて、復旧作業が長くなりそうな気がしたので)

で、載せ替えた後に念のためもう一度起動してみたところ、起動時にHDDのアクセスランプが長いこと点灯したままになってなかなか画面が表示されない現象が発生。しばらく待ってたら画面が表示されて、さっきとは違う画面が表示されて、一瞬だったから覚えていないがblockがなんとかあってfsckが実行されたような感じになった。(この時はまだLiveCDは挿入していない)

その後は、ログイン画面が表示されて前と変わらないように稼働できるようになった。まるでさっきのエラーがなかったかのように。かなり古いHDDを使っていたので壊れちゃったかなと完全にあきらめモードで作業していたが、実際にやったことといえばHDDを別の高性能なマシンに載せ替えて起動してみただけ。もしかしたら、HDDが熱暴走気味になっていて、交換作業中に冷めて正常になったという可能性も、なくはない。(排熱が十分とはいえないマシンを使っていたのは事実)

上記のBusyBoxというようなエラーが出た時、あまりごちゃごちゃいじらない方がいいかもしれない。一旦落ち着いて(あきらめモードで)、HDDの熱が冷めるのを待ってもう一度起動するなど気長にやる。どのみち完全に故障なら復旧作業はそれなりに時間がかかるのだから。

それにしてもfailed: No such file or directoryというエラー文は怖すぎる。サーバー管理者を絶望させるに十分なインパクトがある。

翌日

また不具合が起こった

fsck.ext4: bad magic number in super-block while trying to re-ope
e2fsck: io manager magic bad! 

こんどは上記のようなエラーが発生した。昨日はfsckっぽいものを走らせたら直ったから今回もfsckを実行してみようかと思ってコンソールに入力すると、

Error reading block **** (Attempt to read block from filesystem resulted in short read) while getting next inode from scan.  Ignore error? yes

Force rewrite? yes

みたいな確認が膨大な量出てきた。なんかわからないがyesにするしかないよなぁ・・・と思いながらエンターキーを連打していてちょっと多すぎるからキャンセルしてやり直すかと画面みたら/varとか/etcとかのフォルダに対してdeleteとかrewriteとか実行してしまっていた。

で、再起動するも昨日のエラー画面のno such file or directoryが出て進まなくなった。→OSの再インストールを決断

私の致命的なミスは、昨日、幸運にもエラーから復旧できた時にHDD内の必要なデータをバックアップするなどの作業を怠ったこと。買ったばかりのHDDはともかく、古いHDD使ってて上記みたいなエラーが出だしたらもう交換の目安かもしれない。いや、すぐ交換すべき。この種エラーが出たらもう長くないと思ってHDDが生きている内にデータバックアップとリプレース作業を済ましておく(私が使っていたHDDはもう7,8年は使っていた)

busyboxのエラーが出たHDDその後、取り外してcrystaldiskinfoで調べてみた。健康状態は当然、注意の表示が出た。使用時間は51000時間で約6年間稼働している。もうこのHDDは引退。よく頑張ったと思う。

レイアウトの位置設定でframeで設定する場合とpositionで設定する場合の違い

iosのUIButtonの配置で気になったのでメモ。


var myButton: UIButton!

// Buttonを生成する.
myButton = UIButton()
        
// サイズを設定する.
myButton.frame = CGRectMake(0,0,200,40)

// ボタンの位置を指定する.
myButton.layer.position = CGPoint(x: self.view.frame.width/2, y:200)

ポジションの設定はCGRectMakeでサイズとポジションを設定する方法と、layer.positionにCGPointで設定する方法がある。この二つの違いは、ポジションの原点の位置が異なる模様。CGRectMakeで設定する場合は原点はビューの左上になり、上記の0,0というポジションなら画面の左上にピタリと配置されることになる。CGPointで設定した場合は、原点はビューの中心となり、CGPoint(x: 0, y:0)と設定すると画面左上に配置されるがビューの中心を基準に配置されるので右下4分の1だけ表示されることになる。

wordpressのパーマリンク設定で404になる問題

ワードプレスのパーマリンク設定をするとページ遷移先が404not foundになる場合。
チェックポイントは下記の3つ。

  • mod_rewriteの有効化
  • .htaccessの設置
  • 管理画面にてパーマリンクの設定・保存

でカスタム構造などのパーマリンクは使うことができるようになる。

私の場合はトップページはなんとか表示されたが(記事一覧は表示しなかった)、個別の記事のページを表示すると404エラーが表示されるという現象だった。mod_rewriteはウェブサーバーのapacheの設定で、レンタルサーバを利用している場合は大抵の場合は有効になっているので気にしなくていい。.htaccessファイルはパーマリンクの設定に不可欠なファイルで、内容はカスタム構造のリンクを変換するものになっている。このファイルをワードプレスの設置しているディレクトに置くことで有効になる。管理画面からパーマリンクの設定を実行すると.htaccessファイルは自動で作られる。対象ディレクトリに書き込み権限がない場合は自分でファイルを作成して置くか、権限を付与してから再度実行する。

mod_rewriteが有効化どうか確認するには、例えば下記のような内容に.htaccessを書き換えてみて、ページを表示してみたらヤフーのトップページが表示されるようならmod_rewriteは有効になっているといえる。

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /
RewriteRule ^.*$ http://yahoo.co.jp [L]
</IfModule>

他には、.htaccessのファイル権限とかもあるが、特には気にすることはなく標準のファイル権限を設定しておけばいい。レンタルサーバの場合は上記の点をチェックしておけばおそらく表示されるようになる。それでも表示されない場合は、サーバの設定(バーチャルホストやリダイレクト設定)に問題がある可能性が高い。

自宅サーバの場合も大体同じだが、httpd.confファイルでバーチャルホストを設定したりしている場合には設定の仕方によっては上記だけでは表示出来ない場合もある(私がそれでかなりハマった)
で、その時はウェブサーバーのログを見る。ログなんかめったに見ない主義だったが、今回ばかりはログを見ることで原因がわかった。そもそも404エラーはアクセスしたアドレスにファイルが見つからないというエラーで、ログにはアクセスしたディレクトリとともに404エラーが確認できる。バーチャルホストの設定が間違っている場合はおかしなディレクトリにアクセスしようとしてだから404なのだということがわかる。

ワードプレスとはあまり関係がないがapacheのhttpd.confでバーチャルホストの設定をする場合、一致したものから順に振り分けるという仕様になっているらしい。一致したものからというのはファイルの上から順にということで、バーチャルホストホストの設定で一番上にアスタリスク(*)で設定した場合はそれ以降は読まれないということになる。