HealthKitへのアクセス許可
HKHealthStore
クラスのrequestAuthorization(toShare:read:completion:)
メソッドは、HealthKitフレームワークを使用してユーザーからアプリへのデータアクセス許可をリクエストするために使用されます。このメソッドは、データの共有(書き込み)と読み取りの種類を指定し、ユーザーに対してそれらのデータへのアクセス許可を求めます。具体的には、次のようなパラメータを取ります:
typesToShare
: アプリがHealthKitに書き込むことを要求するデータの種類を指定するHKSampleType
のセットです。例えば、身体活動データ、栄養データ、睡眠データなどが含まれます。typesToRead
: アプリがHealthKitから読み取ることを要求するデータの種類を指定するHKObjectType
のセットです。例えば、心拍数、血圧、歩数などが含まれます。completion
: リクエストが完了した後に呼び出されるクロージャです。このクロージャは、次のパラメータを取ります:success
: ユーザーがすべての要求されたアクセス許可を与えた場合はtrue
、それ以外の場合はfalse
です。error
: リクエストが失敗した場合に返されるエラーオブジェクトです。
このメソッドを呼び出すと、HealthKitがユーザーにアクセス許可を求める標準的なシステムダイアログが表示されます。ユーザーがアクセス許可を与えると、指定された種類のデータにアクセスできるようになります。データアクセス許可が与えられない場合、success
パラメータはfalse
になり、適切なエラーオブジェクトがerror
パラメータに渡されます。
このメソッドはopen
で修飾されており、サブクラスがオーバーライドできるようになっています。これにより、HKHealthStore
をサブクラス化してカスタム機能を追加したり、振る舞いを変更したりすることができます。
func authorizeHealthKit(completion: @escaping () -> Void) {
let healthKitTypesToRead = HealthKitStore.healthKitCharacteristicType + HealthKitStore.healthKitActivityType + HealthKitStore.healthKitEventType + HealthKitStore.healthKitCategoryType
if !HKHealthStore.isHealthDataAvailable() {
let error = NSError(domain: "", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"])
return;
}
healthKitStore.requestAuthorization(toShare: nil, read: Set(healthKitTypesToRead)) { [weak self] result, error in
if let error = error {
print("Error = \(error)")
return
} else if result {
self?.userDefault.setBool(value: true, key: Constaints.kHealkitAuthorization)
completion()
}
}
}
バックグラウンドでのアップロード
BackgroundTaskServiceクラス:startObservingDataChanges関数の定義
- uploadDatas配列にデータが入っている場合に、アップロードを実行する
- uploadDatas配列にデータを追加するのは、主にstoreDataToDatabase関数
- getMostRecentHealthKitSample関数、getMostRecentHealthKitSample関数などでデータが取得できた場合、storeDataToDatabase関数を実行する
- localDatabase.getLatestUpdatedTime()を開始日時、現在日時を終了日時とし、その間のデータを取得する
- localDatabaseは、RealmをDBとして使用している
func handleBackgroundTask(task: BGProcessingTask) {
self.queue.async { [weak self] in
guard let self else { return }
self.startObservingDataChanges() {
self.writeStringToLocalFile(text: "Success get HealthKit data", fileName: "HealthKit\(Date())")
task.setTaskCompleted(success: true)
self.scheduleBackgroundTask()
}
}
task.expirationHandler = {
// Handle task expiration
self.queue.async { [weak self] in
guard let self else { return }
task.setTaskCompleted(success: false)
self.scheduleBackgroundTask()
}
}
}
public func startObservingDataChanges(completion: @escaping () -> Void) {
self.queue.async { [weak self] in
// 省略
self.group.notify(queue: self.queue) { [weak self] in
guard let self else { return }
self.updateCharacteristicIdentifiers()
if self.uploadDatas.count != 0 {
let deviceUUID = self.userDefault.stringValue(key: Constaints.kDeviceUUID) ?? ""
self.dataSyncAPI.requestBGUploadData(data: self.uploadDatas, deviceUUID: deviceUUID, sendingIdentifiers: self.sendingIdentifier)
}
completion()
}
}
}
private func storeDataToDatabase<T>(type: String, models: [T]) where T: BaseUploadDataModel {
if models.count != 0 {
self.uploadDatas.append(UploadFileDataModel(type: type, body: models))
}
}
Realmは、モバイルアプリケーションや組み込みシステム向けのデータベースであり、特にiOSやAndroidアプリケーションの開発に広く使用されています。Realmは、軽量かつ高速なオブジェクト指向データベースであり、モバイルアプリケーションのデータの永続化や管理を容易にします。
Realmの特徴は次のとおりです:
- オブジェクト指向データベース: Realmは、オブジェクト指向プログラミングの考え方に基づいています。つまり、データベースのエントリはクラスのインスタンスで表され、オブジェクト指向プログラミングの概念(継承、ポリモーフィズムなど)がそのまま適用されます。
- 高速なデータアクセス: Realmは非常に高速で効率的なデータアクセスを提供します。このため、大規模なデータセットにも適しています。
- クロスプラットフォーム: RealmはiOS、Android、React Native、Unityなど、さまざまなプラットフォームで利用できます。さらに、これらのプラットフォーム間でデータを同期することも可能です。
- 簡単なデータベース操作: Realmはシンプルで直感的なAPIを提供し、データベースの作成、更新、削除などの操作を簡単に行うことができます。
- リアルタイムデータ同期: Realmはリアルタイムでのデータ同期をサポートしており、複数のデバイス間でデータの同期を行うことができます。
これらの特徴により、Realmはモバイルアプリケーションのデータ管理において非常に便利なツールとなっています。
DataSyncAPIServiceクラス:requestBGUploadData関数の定義
func requestBGUploadData(data: [UploadFileDataModel], deviceUUID: String, sendingIdentifiers: [String: Date]) {
self.networkPublisher.uploadBG(UploadFileDataAPI(data: data, deviceUUID: deviceUUID, sendingIdentifiers: sendingIdentifiers))
}
NetworkPublisherクラス:uploadBG関数の定義
func uploadBG<T>(_ request: T) where T: BaseAPIProtocol {
let config = URLSessionConfiguration.background(withIdentifier: "backgroundSession")
let delegate = URLSessionDelegate()
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
delegate.callback = { data, error in
if let data = data {
self.writeStringToLocalFile(text: String(data: data, encoding: .utf8) ?? "", fileName: "sent\(Date())")
if let requestModel = request.requestModel as? RequestUploadFileDataModel {
for key in requestModel.sendingIdentifiers.keys {
self.localDatabase.updateLatestUpdatedTime(dataType: key, date: requestModel.sendingIdentifiers[key] ?? Date())
}
self.writeStringToLocalFile(text: "write to DB", fileName: "writedb\(Date())")
}
} else {
print("\n Error \(error.debugDescription)")
self.writeStringToLocalFile(text:"\n Error \(error.debugDescription)", fileName: "sentError\(Date())")
}
}
print(String(format: "-------------------\n\n\n\n host: = %@ \n path: = %@ \n method = %@ \n params: = %@ \n time: %@ \n", self.networkConstants.baseURL, request.path, request.method.rawValue, request.params, DateUtil.dateToStr(date: Date(), format: "yyyy/MM/dd HH:mm:ss:sss")))
let boundary = "Boundary-\(UUID().uuidString)"
var newRequest = self.convertToURLRequest(request: request)
let fileData = request.fileData ?? Data()
let fileName = request.fileName
let folderName = fileName.replacingOccurrences(of: ".json.gz", with: "")
do {
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
let folderURL = tempDirectory.appendingPathComponent(folderName, isDirectory: true)
try FileManager.default.createDirectory(
at: folderURL,
withIntermediateDirectories: true,
attributes: nil
)
let fileURL = folderURL.appendingPathExtension(boundary)
guard let outputStream = OutputStream(url: fileURL, append: false) else {
throw OutputStream.OutputStreamError.unableToCreateFile(fileURL)
}
outputStream.open()
try outputStream.write("--\(boundary)\r\n")
try outputStream.write("Content-Disposition: form-data; name=\"\(request.fileParam)\"; filename=\"\(fileName)\"\r\n")
try outputStream.write("Content-Type: application/octet-stream\r\n\r\n")
try outputStream.write(fileData)
try outputStream.write("\r\n")
try outputStream.write("--\(boundary)--\r\n")
outputStream.close()
newRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: newRequest, fromFile: fileURL)
task.resume()
} catch {
}
}
フォアグラウンドでのアップロード
HealthKitStoreクラス:startObservingDataChanges関数の定義
public func startObservingDataChanges(queue: DispatchQueue?, isBackgroundTask: Bool, completion: @escaping () -> Void) {
self.group.notify(queue: self.queue) { [weak self] in
guard let self else { return }
self.updateCharacteristicIdentifiers()
self.dataSyncService.startSync(data: self.uploadDatas, sendingIdentifiers: self.sendingIdentifier, isBackgroundTask: isBackgroundTask)
self.sendingIdentifier = [:]
self.uploadDatas = []
self.isHealkitLoadingData = false
completion()
}
}
DataSyncServiceクラス:getAndUploadData関数の定義
func startSync(data: [UploadFileDataModel], sendingIdentifiers: [String: Date], isBackgroundTask: Bool) {
self.sendingIdentifiers = sendingIdentifiers
self.currentData = data
self.getAndUploadData()
self.syncStatusPublisher.send()
}
//省略
extension DataSyncService {
func getAndUploadData() {
if self.isReadyToSync && self.currentData.count != 0 {
self.dataSyncAPI.requestUploadData(data: self.currentData, deviceUUID: self.deviceUUID, sendingIdentifiers: self.sendingIdentifiers)
}
}
}
DataSyncAPIServiceクラス:requestUploadData関数の定義
func requestUploadData(data: [UploadFileDataModel], deviceUUID: String, sendingIdentifiers: [String: Date]) {
Task {
await self.networkPublisher.upload(UploadFileDataAPI(data: data, deviceUUID: deviceUUID, sendingIdentifiers: sendingIdentifiers))
.sink(receiveCompletion: { [weak self] completion in
self?.handleError(completion: completion)
}, receiveValue: { [weak self] _ in
self?.uploadDataPublisher.send()
}).store(in: &tasks)
}
}
NetworkPublisherクラス:upload関数の定義
func upload<T, V>(_ request: T) async -> Future<V, ResponseBaseModel> where T: BaseAPIProtocol, V: ResponseBaseModel, T.ResponseModel == V {
return Future() { promise in
let config = URLSessionConfiguration.background(withIdentifier: "backgroundSession")
let delegate = URLSessionDelegate()
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
delegate.callback = { [weak self] data, error in
if let data = data {
print("\n response: \(String(data: data, encoding: .utf8) ?? "")")
let response = V.init()
response.mapJSONString(data: data)
self?.writeStringToLocalFile(text: String(data: data, encoding: .utf8) ?? "", fileName: "sent\(Date())")
if let requestModel = request.requestModel as? RequestUploadFileDataModel {
for key in requestModel.sendingIdentifiers.keys {
self?.localDatabase.updateLatestUpdatedTime(dataType: key, date: requestModel.sendingIdentifiers[key] ?? Date())
}
}
promise(.success(response))
} else {
print("\n Error \(error.debugDescription)")
promise(.failure(ResponseBaseModel()))
}
}
print(String(format: "-------------------\n\n\n\n host: = %@ \n path: = %@ \n method = %@ \n params: = %@ \n time: %@ \n", self.networkConstants.baseURL, request.path, request.method.rawValue, request.params, DateUtil.dateToStr(date: Date(), format: "yyyy/MM/dd HH:mm:ss:sss")))
let boundary = "Boundary-\(UUID().uuidString)"
var newRequest = self.convertToURLRequest(request: request)
let fileData = request.fileData ?? Data()
let fileName = request.fileName
let folderName = fileName.replacingOccurrences(of: ".json.gz", with: "")
do {
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
let folderURL = tempDirectory.appendingPathComponent(folderName, isDirectory: true)
try FileManager.default.createDirectory(
at: folderURL,
withIntermediateDirectories: true,
attributes: nil
)
let fileURL = folderURL.appendingPathExtension(boundary)
guard let outputStream = OutputStream(url: fileURL, append: false) else {
throw OutputStream.OutputStreamError.unableToCreateFile(fileURL)
}
outputStream.open()
try outputStream.write("--\(boundary)\r\n")
try outputStream.write("Content-Disposition: form-data; name=\"\(request.fileParam)\"; filename=\"\(fileName)\"\r\n")
try outputStream.write("Content-Type: application/octet-stream\r\n\r\n")
try outputStream.write(fileData)
try outputStream.write("\r\n")
try outputStream.write("--\(boundary)--\r\n")
outputStream.close()
newRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: newRequest, fromFile: fileURL)
task.resume()
self.writeStringToLocalFile(text: " task.resume()", fileName: "taskresume\(Date())")
} catch {
}
}
}