私は Kugel を使用して NSNotifications を使用しており、ウォッチ シミュレーターと iPhone および iPhone シミュレーターの両方でうまく機能しており、UI/状態を更新するメッセージを配信していますが、これらはデバイスでテストするときにウォッチ側で配信できません。
私が信じている問題は、iPhone からの WCSession メッセージに基づいて NSNotifications がトリガーされることです。シミュレーターと iPhone 側ですべてが正常に動作するのは、おそらく、sim が監視アプリを常にアクティブに保ち、iPhone が完全なセッションをサポートしているため、接続と通知が常に配信されるためです。ウォッチでは、ウォッチの状態に基づいて、セッションとおそらく通知の両方が失敗する可能性があります。
時計でのデバッグは非常に遅いです。デバッグ プロセスを開始するだけで 5 ~ 10 分かかります。
電話メッセージが時計で受信され、メッセージに基づいて更新する必要があることを時計アプリに通知するための最良の方法について、誰かが私にいくつかの読書を指摘できますか? それとも、後で確認できる WCSession および NSNotification 情報を記録できる優れたデバッグ コードでしょうか?
私のコードはかなり単純ですが、まだ進行中の作業です....
両側で、セッションを管理するシングルトンを作成します。電話側のコードは次のとおりです。
import WatchConnectivity
import UIKit
// This class manages messaging between the Watch and iPhone
class PhoneSession: NSObject, WCSessionDelegate
{
static let manager = PhoneSession()
private var appDelegate: AppDelegate!
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
}
func isEditing() -> Bool
{
if (UIApplication.sharedApplication().applicationState == .Active)
{
if (appDelegate.mainView.visible && appDelegate.mainView.currentDay.isToday())
{
return false
}
return true
}
return false
}
}
extension PhoneSession
{
func sendEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.Editing.rawValue])
}
}
func sendDoneEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.DoneEdit.rawValue])
}
}
func sendTable()
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
else
{
do
{
try updateApplicationContext([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func sendRowDone(row: Int, done: Bool)
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.RowDone.rawValue,
Keys.RowIndex: row, Keys.Done: done])
}
else
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
do
{
try updateApplicationContext( [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue,
Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func receivedRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let done: Bool = info[Keys.Done] as! Bool
PhoneData.manager.updateInfoFromWatch(row, done: done)
}
func receivedRowInfo(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let rest: Int = info[Keys.Rest] as! Int
let reps: Int = info[Keys.Reps] as! Int
let force: Double = info[Keys.Force] as! Double
PhoneData.manager.updateSetInfoFromWatch(row, rest: rest, reps: reps, force: force)
}
func receivedTableDone(info: [String : AnyObject])
{
let date: Int = info[Keys.Date] as! Int
let dones: [Bool] = info[Keys.TableDones] as! [Bool]
PhoneData.manager.updateDones(dones, forDate: date)
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedTableComplete()
{
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedStartRest()
{
Kugel.publish(PhoneNotificationKeys.StartRest)
}
func receivedInfo(info: [String : AnyObject]) -> NSData?
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: WatchUpdateType = WatchUpdateType.getType(messageString)
switch (updateType)
{
case .RowInfo:
receivedRowInfo(info)
case .TableDone:
receivedTableDone(info)
case .RowDone:
receivedRowDone(info)
case .TableComplete:
receivedTableComplete()
case .StartRest:
receivedStartRest()
case .RequestUpdate:
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
return archivedTable
case .Ignore:
print("Opps")
}
return nil
}
}
// MARK: Interactive Messaging
extension PhoneSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(returnMessage: [String : AnyObject]) -> Void in
if let theMessage = returnMessage[Keys.MessageStatus]
{
print("Return Message from Watch: \(theMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Watch: \(error)")
}
)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
let returnMessage = self.receivedInfo(message)
if (returnMessage != nil)
{
if let archivedTable: NSData = returnMessage!
{
let replyValues = [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable] // Data to be returned
replyHandler(replyValues)
}
}
}
}
// MARK: Application Context
// use when your app needs only the latest information, if the data was not sent, it will be replaced
extension PhoneSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if ((session) != nil)
{
do
{
try session!.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
self.receivedInfo(applicationContext)
}
}
そしてこれは時計側です:
import WatchConnectivity
class WatchSession: NSObject, WCSessionDelegate
{
static let manager = WatchSession()
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
}
}
extension WatchSession
{
func sendRowInfo(row:Int, rest: Int, reps: Int, force: Double)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowInfo.rawValue,
Keys.Row : row,
Keys.Rest : rest,
Keys.Reps : reps,
Keys.Force : force]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendRowDone(row:Int, done: Bool)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowDone.rawValue,
Keys.Row : row,
Keys.Done : done]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendTableDone()
{
let tableDones: [Bool] = WatchData.manager.watchTableDone()
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableDone.rawValue,
Keys.Date : date, Keys.TableDones: tableDones])
}
catch _
{
print("error trying to send TableDones")
}
}
func sendTableComplete()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.TableComplete.rawValue])
}
else
{
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableComplete.rawValue,
Keys.Date : date])
}
catch _
{
print("error trying to send TableComplete")
}
}
}
func sendRest() -> Bool
{
var sent: Bool = false
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.StartRest.rawValue])
sent = true
}
return sent
}
func requestUpdate() -> Bool
{
var sent: Bool = false
if session!.reachable
{
print("requesting update reply")
sendMessage([Keys.UpdateType : WatchUpdateType.RequestUpdate.rawValue])
sent = true
}
return sent
}
func receivedUpdateReply(info: [String : AnyObject])
{
}
func receiveRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.RowIndex] as! Int
let done: Bool = info[Keys.Done] as! Bool
WatchData.manager.updateWatchTable(row, done: done)
Kugel.publish(WatchNotificationKeys.UpdateRow)
}
func receivedTable(archivedTable: NSData)
{
let workout: WatchWorkout = NSKeyedUnarchiver.unarchiveObjectWithData(archivedTable) as! WatchWorkout
WatchData.manager.updateWatchWorkout(workout)
Kugel.publish(WatchNotificationKeys.ReloadTable)
}
func receivedStartEditStatus()
{
Kugel.publish(WatchNotificationKeys.StartEdit)
}
func receivedDoneEditStatus()
{
WatchData.manager.retrieveWorkout()
Kugel.publish(WatchNotificationKeys.DoneEdit)
}
func receivedStopRest()
{
Kugel.publish(WatchNotificationKeys.StopRest)
}
func receivedInfo(info: [String : AnyObject])
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: PhoneUpdateType = PhoneUpdateType.getType(messageString)
switch (updateType)
{
case .TableInfo:
receivedTable(info[Keys.Workout] as! NSData)
case .Editing:
receivedStartEditStatus()
case .DoneEdit:
receivedDoneEditStatus()
case .RowDone:
receiveRowDone(info)
case .StopRest:
receivedStopRest()
case .Ignore:
print("Opps")
}
}
func receivedReply(info: [String : AnyObject])
{
if let replyString: String = info[Keys.ReplyType] as? String
{
let replyType: ReplyType = ReplyType.getType(replyString)
switch (replyType)
{
case .Table:
print("received Reply Table")
receivedTable(info[Keys.Workout] as! NSData)
case .NoData:
print("Opps ... nodata in reply")
case .Ignore:
print("Opps replyType message error")
}
}
}
}
// MARK: Interactive Messaging
extension WatchSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(replyMessage: [String : AnyObject]) -> Void in
if let typeMessage: String = replyMessage[Keys.ReplyType] as? String
{
self.receivedReply(replyMessage)
print("Return Message from Phone: \(typeMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Phone: \(error)")
}
)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
self.receivedInfo(message)
let replyValues = [Keys.MessageStatus : "Watch received message"] // Data to be returned
replyHandler(replyValues)
}
}
// MARK: Application Context
extension WatchSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if let session = validSession
{
do
{
try session.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
// handle receiving application context
receivedInfo(applicationContext)
}
}
iPhone 側の AppDelegate と時計側の ExtensionDelegate にシングルトンを作成します。電話側は次のとおりです。
var phoneSession: PhoneSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
self.phoneSession = PhoneSession()
PhoneSession.manager.startSession()
メッセージを送信するときの基本的なロジックは、相手側が到達可能かどうか、到達可能である場合は sendMessage が使用され、到達可能でない場合は sendApplicationContext が使用されることです。
電話側でメッセージが受信されると、アプリがフォアグラウンドにあるかバックグラウンドにあるかを確認する追加のロジックがあり、フォアグラウンドの場合はメイン スレッドに通知をプッシュし、バックグラウンドの場合は情報だけが更新されます。私の理解では、メッセージはバックグラウンドで受信されないため、時計側では常にメインスレッドにプッシュされます。