-
Notifications
You must be signed in to change notification settings - Fork 2
fix: 커스텀 시간표 변경사항 워치 동기화 #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import WatchConnectivity | |
| final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { | ||
| @Dependency(\.userDefaultsClient) var userDefaultsClient | ||
| @Dependency(\.localDatabaseClient) var localDatabaseClient | ||
| @Published private(set) var syncVersion: Int = 0 | ||
|
|
||
| var isReachable: Bool { | ||
| session.isReachable | ||
|
|
@@ -24,34 +25,7 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { | |
| error: Error? | ||
| ) { | ||
| sendMessage(message: [:]) { [weak self] items in | ||
| guard let self else { return } | ||
| guard | ||
| let code = items["code"] as? String, | ||
| let orgCode = items["orgCode"] as? String, | ||
| let grade = items["grade"] as? Int, | ||
| let `class` = items["class"] as? Int, | ||
| let type = items["type"] as? String, | ||
| let isOnModifiedTimeTable = items["isOnModifiedTimeTable"] as? Bool, | ||
| let timeTablesData = items["timeTables"] as? Data | ||
| else { | ||
| return | ||
| } | ||
| let timeTables = self.decodeTimeTables(data: timeTablesData) | ||
| let dict: [UserDefaultsKeys: Any] = [ | ||
| .grade: grade, | ||
| .class: `class`, | ||
| .schoolType: type, | ||
| .orgCode: orgCode, | ||
| .schoolCode: code, | ||
| .isOnModifiedTimeTable: isOnModifiedTimeTable | ||
| ] | ||
| dict.forEach { key, value in | ||
| self.userDefaultsClient.setValue(key, value) | ||
| } | ||
| if let major = items["major"] as? String { | ||
| self.userDefaultsClient.setValue(.major, major) | ||
| } | ||
| try? self.localDatabaseClient.save(records: timeTables) | ||
| self?.applyIncomingItems(items) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -78,33 +52,15 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { | |
| replyHandler: @escaping ([String: Any]) -> Void | ||
| ) { | ||
| print("RECEIVE : \(message)") | ||
| guard | ||
| let code = message["code"] as? String, | ||
| let orgCode = message["orgCode"] as? String, | ||
| let grade = message["grade"] as? Int, | ||
| let `class` = message["class"] as? Int, | ||
| let type = message["type"] as? String, | ||
| let isOnModifiedTimeTable = message["isOnModifiedTimeTable"] as? Bool, | ||
| let timeTablesData = message["timeTables"] as? Data | ||
| else { | ||
| return | ||
| } | ||
| let timeTables = decodeTimeTables(data: timeTablesData) | ||
| let dict: [UserDefaultsKeys: Any] = [ | ||
| .grade: grade, | ||
| .class: `class`, | ||
| .schoolType: type, | ||
| .orgCode: orgCode, | ||
| .schoolCode: code, | ||
| .isOnModifiedTimeTable: isOnModifiedTimeTable | ||
| ] | ||
| dict.forEach { key, value in | ||
| self.userDefaultsClient.setValue(key, value) | ||
| } | ||
| if let major = message["major"] as? String { | ||
| self.userDefaultsClient.setValue(.major, major) | ||
| } | ||
| try? self.localDatabaseClient.save(records: timeTables) | ||
| applyIncomingItems(message) | ||
| } | ||
|
|
||
| func session( | ||
| _ session: WCSession, | ||
| didReceiveMessage message: [String: Any] | ||
| ) { | ||
| print("RECEIVE : \(message)") | ||
| applyIncomingItems(message) | ||
| } | ||
|
|
||
| func sendMessage( | ||
|
|
@@ -123,10 +79,52 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { | |
| session.sendMessage(message, replyHandler: reply, errorHandler: error) | ||
| } | ||
|
|
||
| func session( | ||
| _ session: WCSession, | ||
| didReceiveApplicationContext applicationContext: [String: Any] | ||
| ) { | ||
| applyIncomingItems(applicationContext) | ||
| } | ||
|
|
||
| // swiftlint: disable force_try | ||
| private func decodeTimeTables(data: Data) -> [ModifiedTimeTableLocalEntity] { | ||
| let entities = try! JSONDecoder().decode([ModifiedTimeTableLocalEntity].self, from: data) | ||
| return entities | ||
| } | ||
|
|
||
| private func applyIncomingItems(_ items: [String: Any]) { | ||
| guard | ||
| let code = items["code"] as? String, | ||
| let orgCode = items["orgCode"] as? String, | ||
| let grade = items["grade"] as? Int, | ||
| let `class` = items["class"] as? Int, | ||
| let type = items["type"] as? String, | ||
| let isOnModifiedTimeTable = items["isOnModifiedTimeTable"] as? Bool, | ||
| let timeTablesData = items["timeTables"] as? Data | ||
| else { | ||
| return | ||
| } | ||
|
|
||
| let timeTables = decodeTimeTables(data: timeTablesData) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 외부 페이로드 디코딩에 watch 통신 데이터가 손상되거나 스키마가 어긋나면 즉시 크래시 납니다. 안전 디코딩으로 바꿔야 합니다. 제안 패치- private func decodeTimeTables(data: Data) -> [ModifiedTimeTableLocalEntity] {
- let entities = try! JSONDecoder().decode([ModifiedTimeTableLocalEntity].self, from: data)
- return entities
- }
+ private func decodeTimeTables(data: Data) -> [ModifiedTimeTableLocalEntity]? {
+ try? JSONDecoder().decode([ModifiedTimeTableLocalEntity].self, from: data)
+ }
...
- let timeTables = decodeTimeTables(data: timeTablesData)
+ guard let timeTables = decodeTimeTables(data: timeTablesData) else { return }Also applies to: 90-93 🤖 Prompt for AI Agents |
||
| let dict: [UserDefaultsKeys: Any] = [ | ||
| .grade: grade, | ||
| .class: `class`, | ||
| .schoolType: type, | ||
| .orgCode: orgCode, | ||
| .schoolCode: code, | ||
| .isOnModifiedTimeTable: isOnModifiedTimeTable | ||
| ] | ||
| dict.forEach { key, value in | ||
| self.userDefaultsClient.setValue(key, value) | ||
| } | ||
| if let major = items["major"] as? String { | ||
| self.userDefaultsClient.setValue(.major, major) | ||
| } | ||
| try? self.localDatabaseClient.deleteAll(record: ModifiedTimeTableLocalEntity.self) | ||
| try? self.localDatabaseClient.save(records: timeTables) | ||
| DispatchQueue.main.async { | ||
| self.syncVersion += 1 | ||
| } | ||
|
Comment on lines
+123
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삭제/저장 실패를 무시하면 데이터 유실 후에도
제안 패치- try? self.localDatabaseClient.deleteAll(record: ModifiedTimeTableLocalEntity.self)
- try? self.localDatabaseClient.save(records: timeTables)
- DispatchQueue.main.async {
- self.syncVersion += 1
- }
+ do {
+ try self.localDatabaseClient.deleteAll(record: ModifiedTimeTableLocalEntity.self)
+ try self.localDatabaseClient.save(records: timeTables)
+ DispatchQueue.main.async {
+ self.syncVersion += 1
+ }
+ } catch {
+ // 필요 시 로깅 추가
+ return
+ }🤖 Prompt for AI Agents |
||
| } | ||
|
Comment on lines
+95
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because watch sync payloads are delivered via both This causes redundant database deletions/writes and triggers duplicate UI reloads. Adding an equality check to compare the incoming payload with the current local state will prevent this redundancy and improve performance on the Apple Watch. private func applyIncomingItems(_ items: [String: Any]) {
guard
let code = items["code"] as? String,
let orgCode = items["orgCode"] as? String,
let grade = items["grade"] as? Int,
let classValue = items["class"] as? Int,
let type = items["type"] as? String,
let isOnModifiedTimeTable = items["isOnModifiedTimeTable"] as? Bool,
let timeTablesData = items["timeTables"] as? Data
else {
return
}
let timeTables = decodeTimeTables(data: timeTablesData)
let currentGrade = userDefaultsClient.getValue(.grade) as? Int
let currentClass = userDefaultsClient.getValue(.class) as? Int
let currentSchoolType = userDefaultsClient.getValue(.schoolType) as? String
let currentOrgCode = userDefaultsClient.getValue(.orgCode) as? String
let currentSchoolCode = userDefaultsClient.getValue(.schoolCode) as? String
let currentIsOnModifiedTimeTable = userDefaultsClient.getValue(.isOnModifiedTimeTable) as? Bool
let currentMajor = userDefaultsClient.getValue(.major) as? String
let incomingMajor = items["major"] as? String
let currentLocalTimeTables = (try? localDatabaseClient.readRecords(as: ModifiedTimeTableLocalEntity.self)) ?? []
if currentGrade == grade,
currentClass == classValue,
currentSchoolType == type,
currentOrgCode == orgCode,
currentSchoolCode == code,
currentIsOnModifiedTimeTable == isOnModifiedTimeTable,
currentMajor == incomingMajor,
currentLocalTimeTables == timeTables {
return
}
let dict: [UserDefaultsKeys: Any] = [
.grade: grade,
.class: classValue,
.schoolType: type,
.orgCode: orgCode,
.schoolCode: code,
.isOnModifiedTimeTable: isOnModifiedTimeTable
]
dict.forEach { key, value in
self.userDefaultsClient.setValue(key, value)
}
if let major = items["major"] as? String {
self.userDefaultsClient.setValue(.major, major)
}
try? self.localDatabaseClient.deleteAll(record: ModifiedTimeTableLocalEntity.self)
try? self.localDatabaseClient.save(records: timeTables)
DispatchQueue.main.async {
self.syncVersion += 1
}
} |
||
| // swiftlint: enable force_try | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On devices where
WCSessionis not supported (such as iPads) or when the session is not yet activated, callingupdateApplicationContextwill throw an error and flood the logs withWCErrorDomain Code=7001("Session is not activated.").To prevent this, guard against
WCSession.isSupported()and check thatsession.activationState == .activatedbefore attempting to update the application context.