全7件 (7件中 1-7件目)
1
ユーザーと、1人以上のユーザーグループを作って簡易的なグループチャット的な機能を作っていました。 同一グループに所属するユーザー情報の一覧をデータベースから取得する際、ユーザー情報を全件取得して、同一グループのユーザーであるかを選別していたのですが、ユーザーの増加につれ取得件数が増加すること※と、ユーザー情報にパスワードも含んでいることから極力取得する情報を限定的にするために、グループ名をパーティションキーとしたユーザー一覧テーブルを作りました。 ※利用者が、友人枠をつかっても両手で数えたりそうなため不要な心配ですが・・・ そこで、ユーザー一覧テーブルに対して 所属するグループを変更した場合は過去に所属していたグループから自分の情報を削除する グループオーナーが新しいグループを作った場合は、グループに所属する全員の情報を削除する などの処理を作っていました。 ただ、これが削除処理を呼び出しているのにうまく削除されず。 デバッグしていてもperformを呼び出しているにも関わらず、その後の処理が実行されていませんでした。 appDelegate.appSyncClient?.perform(mutation: DeleteTmgpsGroupUserListMutation(input:mutationInput), resultHandler: { (result, error) in if let error = error as? AWSAppSyncClientError { self.errorCode = TMGPSDBProcess.DBERROR_ACCESS_ERROR self.errorMessage = error.localizedDescription } if let resultError = result?.errors { self.errorCode = TMGPSDBProcess.DBERROR_USERLISTDELETE_ERROR for error in resultError { self.errorMessage! += "エラーコード:" + String(error.errorDescription!) self.errorMessage! += "\nエラー詳細:" + error.message + "\n" } } if ( self.errorCode == TMGPSDBProcess.DBERROR_NO_ERROR ) { // エラーが起きていない場合 self.statusCode = TMGPSDBProcess.DBERROR_USERLISTDELETE_NORMALEND self.errorMessage = DBErrorTitle.DBERROR_USERLISTDELETE_NORMALEND.rawValue } //グループユーザー一覧情報の削除結果を通知する NotificationCenter.default.post(name: postName, object: nil) }) ※resultHandlerの内側の処理が全く呼び出されなかった。。。 まだ、理由が特定できていませんが、 いろいろな処理を同時に実行しすぎて、AppSync が処理し切れていなかったせいぽいです。 1つのDBアクセスが終了するまで次のアクセスを行わないようにプログラムを整理をしたところ、ちゃんと全て動くようになりました。 データベースへのコネクションを一つしか作っていなかったので当たり前と言えば当たり前ですかね。 ハマり時間 4時間
2020.10.29
コメント(0)
GraphQL でDynamoDB からデータを取得する場合、Query を使います。プライマリキーを指定して、特定の1件だけを取得する場合はGetテーブル名Queryを使用します。例では、groupName がパーティションキー、userID がソートキーです。appDelegate.appSyncClient?.fetch(query:GetTmgpsGroupUserListQuery( groupName: groupName, userID: userId)) 次にテーブルにあるデータを全件取得する場合は、Listテーブル名sListQueryを使用します。キーを何も指定しなければ全件取得できます。// groupNameでデータベースを検索するappDelegate.appSyncClient?.fetch(ListTmgpsGroupUserListsQuery())最後に、ソートキーを指定せず、特定のパーティションキーのデータを全件取得する場合ですが、Listテーブル名sListQueryを使用するのは変わらず、パーティションキーだけを指定し、他の引数は全てnilにするだけです。 // groupNameでデータベースを検索するappDelegate.appSyncClient?.fetch(query:ListTmgpsGroupUserListsQuery( groupName:appDelegate.GroupData.groupName, userID: nil, filter: nil, limit: nil, nextToken: nil, sortDirection: nil)これにめっちゃはまり時間を浪費しました。API.swift 上、ListTmgpsGroupUserListsQueryのイニシャライザは以下の通り定義されています。public init( groupName: String? = nil, userID: ModelStringKeyConditionInput? = nil, filter: ModelTMGPSGroupUserListFilterInput? = nil, limit: Int? = nil, nextToken: String? = nil, sortDirection: ModelSortDirection? = nil){ self.groupName = groupName self.userID = userID self.filter = filter self.limit = limit self.nextToken = nextToken self.sortDirection = sortDirection}引数を省略したら 自動的に nil を与えるよ、っていう定義です。上記の2つ目のケースは、引数を1つも与えないことで、暗黙的に引数にnilが与えられることによってパーティションキー、ソートキーの指定がされずに全件データを取得しているのです。つまり、、、パーティションキーだけ値をセットして、他の引数は全て明示的にnilを与えてあげればそれでよかったのです。これに気づかずにどはまりしました。ちなみに、ソートキーは以下のようになっています。input ModelStringKeyConditionInput { eq: String le: String lt: String ge: String gt: String between: [String] beginsWith: String}いまいち使い方が理解できていませんが、論理演算で該当するレコードだけが取得できそうです。その他、取得件数の制限や、フィルター、ソート順の指定(昇順、降順)ができそうです。今のところ、ソートしかちゃんと使いこなしていないです。ハマり時間 4時間。。。なんか、Safariのせいなのか、楽天ブログのせいか分かりませんが、文字を変換途中に勝手に変換終了しやがってうざい。見たまま編集 off たらなおったのでブログのせいかな。
2020.10.29
コメント(0)
push 時にこんなエラーが発生しました。Attempting to edit the key schema of the TMGPSCourseTable table in the TMGPSCourse stack. An error occurred during the push operation: Attempting to edit the key schema of the TMGPSCourseTable table in the TMGPSCourse stack. プライマリキーは変えられないよ!的なエラー。そりゃそうですね。というわけで、一回データソースごと削除して再作成しました。AWS上でテーブルを削除しても、データソースが残ってると同じエラーを吐き続けるのでご注意ください。データソースの削除は、schema.graphql から定義を削除してから push するだけです。こんな感じでコメントアウトしてあげるだけでも大丈夫です。#type TMGPSImgMessage @model {# id : ID!# imageName : String!# sendType : String!# message : String!#}削除が終了したら、改めて新しいテーブル定義で push push!以上。ハマり時間 1hGraphQLのコメントアウト方法、今までちゃんと調べてなかったので今日初めて知りましたwww
2020.10.27
コメント(0)
以前にAmplify + GraphQLでテーブルを作成した際は、あまり深いこと調べなかったので、プライマリーキーは必ずid : ID!にしないといけないのか、程度にしか認識してなかったんですが、そんなわけないだろ、と改めて思いちゃんと調べました。type テーブル名 @model @key (fields: ["パーティションキー"] )type テーブル名 @model @key (fields: ["パーティションキー","ソートキー"] )で設定できるようです。1つ目の例では、パーティションキーがプライマリキーになります。2つ目の例では、パーティションキー+ソートキーがプライマリキーになります。以下、実例。1.パーティションキーがプライマリキーのパターンtype TMGPSUser @model @key(fields: ["userID"]) { userID : String! userName : String! useCloud : Boolean! userPass : String! joinGroup : Boolean groupName : String groupPass : String appVersion : String! passLimit : String! ldel_Flg : Boolean! ldel_Date : String}2.パーティションキー+ソートキーがプライマリキーのパターン# Groupに所属しているユーザー一覧type TMGPSGroupUserList @model @key(fields: ["groupName","userID"]) { groupName : String! userID : String! userName : String!}なお、id : ID! でパーティションキーを設定している場合は、以下のようにすることで、パーティションキー+ソートキーをプライマリキーにできます。(たぶん)# サンプルテーブルtype TMGPSSample @model @key(fields: ["sortKey"]) { id : ID! sortKey : String! property : String!}ちなみに、以下のようにすると @key 指定しなくてもパーティションキーとソートキーをプライマリキーにできるはず。試してないから断定できないけど。。。# サンプルテーブルtype TMGPSSample { id : ID! sortKey : ID! property : String!}
2020.10.27
コメント(0)

ビューコントローラー内のオブジェクトを明示的に廃棄しないと、ゴミのようなコントローラーが残っちゃいますよ、というお話です。 先日作成した簡易的なメッセージ送受信画面(ビュー)。 テキストフィールドにメッセージを書き込むか、ビュー下段にあるボタン(イメージビュー)をタップすると、クラウド上のデータベースにメッセージが登録され、受信者&送信者は一定間隔でデータベースからメッセージを受信するという仕組みです。 受信したメッセージは、画面の上段の受信用イメージビューとメッセージラベルにそれぞれ表示されます。 テストをしていたところ、1回目のビュー表示時は正しくメッセージが送受信されるのですが、2回目以降のビュー表示時は、メッセージを送信してもその結果が反映されない事象が発生しました。 先に結論を言ってしまうと、前ビューに戻る際にコントローラー内で使用しているTimerオブジェクトが動きっぱなしになっており、そのせいでコントローラーが廃棄されていないことが原因でした。 表示中のビューに紐づいているコントローラーと、イベントを受けるコントローラーが別々になってしまっていたせいでラベルなどの更新がうまくいっていませんでした。 今後同じツボにはまらないように、調査の中で得た知見等をまとめておきます。 調査の過程 事象を発見した直後に、ビューの動作とデバッガーからわかったことは以下の3点。 ・2回目以降も初期表示時は正しくイメージとラベルは表示される ・送信したメッセージはデータベースに格納されている ・受信処理も正常終了していて、メッセージを格納する配列にも正しく登録されている 以上のことから、メッセージ送受信の仕組みは正しく動いており、ビューの再描画ができていないことがは結構早い段階でわかりました。 ビューに対して、setNeedsDisplay() 発行してみても変わらず。 Debug用にUILabelを継承した以下のようなクラスを作ってみました。 import UIKit// UILabelのデバッグ用。class TMLabel : UILabel { fileprivate let color = UIColor.white // プロパティの初期化 fileprivate func configure() { backgroundColor = color translatesAutoresizingMaskIntoConstraints = false } /// イニシャライザ init() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.init(frame: CGRect.zero) configure() } /// イニシャライザ override init(frame: CGRect) { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.init(frame: frame) configure() } /// イニシャライザ(StoryBoardやIntefaceBuilderでの生成) required init?(coder aDecoder: NSCoder) { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.init(coder: aDecoder) configure() } /// 制約の更新 override func updateConstraints() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.updateConstraints() } /// 制約の更新要否のフラグを立てる override func setNeedsUpdateConstraints() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.setNeedsUpdateConstraints() } /// 必要に応じて制約の更新を即時実行する override func updateConstraintsIfNeeded() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.updateConstraintsIfNeeded() } /// レイアウトの更新 override func layoutSubviews() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.layoutSubviews() } /// レイアウトの更新要否のフラグを立てる override func setNeedsLayout() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.setNeedsLayout() } /// 必要に応じてレイアウトの更新を即時実行する override func layoutIfNeeded() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.layoutIfNeeded() } /// 描画の更新 override func draw(_ rect: CGRect) { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.draw(rect) } /// 描画の更新要否のフラグを立てる override func setNeedsDisplay() { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.setNeedsDisplay() } /// 描画の更新要否のフラグを立てる override func setNeedsDisplay(_ rect: CGRect) { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) super.setNeedsDisplay(rect) } deinit { print(NSStringFromClass(type(of:self)).components(separatedBy: ".")[1] + ":" + #function) }} 実行結果はこんな感じ。(ラベルが3つあるので三重にダンプされてます) 1回目・ビュー初期化時TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:setNeedsLayout()TMLabel:setNeedsLayout()TMLabel:setNeedsLayout()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:draw(_:)TMLabel:draw(_:)TMLabel:draw(_:) 1回目・メッセージ送信から受信時後TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:layoutSubviews()TMLabel:updateConstraints()TMLabel:updateConstraints()TMLabel:layoutSubviews()TMLabel:draw(_:)TMLabel:draw(_:)TMLabel:draw(_:) 2回目・ビュー初期化時TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:init(coder:)TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:layoutSubviews()TMLabel:draw(_:)TMLabel:draw(_:)TMLabel:draw(_:) 2回目・メッセージ送信から受信時後TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay()TMLabel:setNeedsLayout()TMLabel:setNeedsDisplay() 2回目のビュー初期化時まではdraw()が呼び出されているが、メッセージ送受信後はsetNeedsDisplayは呼び出されているものの、drawが呼び出されていないことがわかりました。 ラベルのメモリ上の番地のダンプ結果はこんな感じでした。 1回目・ビュー初期化時 1回目・メッセージ送信から受信時後 2回目・ビュー初期化時 2回目・メッセージ送信から受信時後 これでやっと原因が分かりました。 2回目初期化時は番地が変わっているものの2回目受信時には1回目と同じ番地になってます。 つまり、2回目のイベントを受け取っているコントローラーは1回目と同じコントローラーでした。 概念的にはこんな感じ。 ※iPhoneのメモ帳で手書きしたので全体的に汚くてすみません。 XCodeでメモリの状態を表示してみたところ、本来1つしか生成されないはずのビューコントローラーが2つあるのが分かります。 コントローラーが廃棄されない原因は冒頭で書いた通り、コントローラー内で使用しているTimerが生きて続けていたことでした。 前ビューに戻る操作を自分で実装し、アクション内でTimerを廃棄する処理を生成したら、無事コントローラーが破棄されるようになった。 // MARK: - IBAction // 戻るボタンタップ時の操作 @IBAction func action_BackButton() { // メッセージ受信タイマーを初期化 if( self.msgRcvTimer != nil ) { self.msgRcvTimer.invalidate() self.msgRcvTimer = nil } // ビューのClose用のタイマーを初期化 if( self.closeTimer != nil ) { self.closeTimer.invalidate() self.closeTimer = nil } // 前の画面に戻る self.navigationController?.popViewController(animated: true) return} わかってみたら、当たり前のような話ですが、だいぶはまりました。 解決時間ほぼ3日。
2020.10.24
コメント(0)
![]()
お風呂ってカビ生えますよね。いきなりなんだよって話ですが。カビの種類にも赤カビ、黒カビがありますが、私の場合は黒カビが生えてきたらカビキラーでカビ退治します。赤カビは最悪ゴシゴシすれば落ちるので。ただ、カビキラーでカビ退治しただけではすぐにカビが生えてきますよね。そこで、必ず「おふろの防カビくん」を使って防カビ対策します。これ、地味に優秀で、1回やっておけば1〜2ヶ月はカビ知らずです。1〜2ヶ月としているのは季節や天気によるからです。お風呂の場合、特に浴室乾燥機能を使うとカビが繁殖しやすくなるような気がします。梅雨や台風シーズンなどで外に洗濯物が干せず、浴室乾燥機能を連日使うと最短1ヶ月くらいしか効果が続きません。そうでもなければ、2ヶ月くらいはもつかな〜、という体感です。本当は、1ヶ月毎または1ヶ月半ごとに防カビくんを使っていればカビキラーすら必要ないんじゃないかと思うのですが、そこは持ち前のだらしなさで結局毎回黒カビを生やします。みなさんはそうならないように、こまめに防カビくん施工してくださいね。ライオン(LION) ルックおふろの防カビくん煙剤 3P楽天で購入今日ホームセンターで買ってきましたが、楽天さんちょっとお高めですね。ホームセンターで100円は安く買えました。
2020.10.19
コメント(0)
ただの備忘録です。NavigationController の制御下で前画面に戻る場合は、自オブジェクトで保持しているNavigationController オブジェクトに対して、popViewController を発行する。// 前画面に遷移するself.navigationController?.popViewController(animated: true)NaviController 制御かで Show segue で遷移する場合とかですね。 対して、NavigationController の制御以外で次画面を閉じる場合は自オブジェクトに対して dissmiss メソッドを発行する。// 前画面に遷移するself.dismiss(animated: true, completion: nil)Present Modally でビューを開いた時とかですね。ちなみに、ビューが閉じる時に、オブジェクトの初期化等を行いたいような場合は、viewWill(Did)Disappearイベント内で isBeingDismissed メソッドを発行します。true が帰ってきた場合、ビューを閉じようとしていると判断できます。// ビューが非表示になる直前に呼び出されるoverride func viewWillDisappear(_ animated: Bool) { if ( self.isBeingDismissed == true ) { // 処理を記述する }}ただ、これだと NavigationController で Popした場合検知できないので、それは調べ次第追記します。(そういうケースが出てきた場合に。。。)
2020.10.19
コメント(0)
全7件 (7件中 1-7件目)
1

![]()
