From 6211af6a9b5901fd6c6bbbde771e34f9890946a4 Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sun, 14 Feb 2021 10:39:42 -0500 Subject: [PATCH 1/4] Support for PushNotifications --- CHANGELOG.md | 12 +++++- ParseSwift.podspec | 2 +- ParseSwift.xcodeproj/project.pbxproj | 26 ++++++++---- Scripts/jazzy.sh | 2 +- .../ParseSwift/Internal/Data+hexString.swift | 42 +++++++++++++++++++ .../Objects/ParseInstallation.swift | 11 +++++ .../ParseInstallationTests.swift | 8 ++++ 7 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 Sources/ParseSwift/Internal/Data+hexString.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index c1854a690..161944d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,19 @@ # Parse-Swift Changelog ### main -[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.3...main) +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.4...main) * _Contributing to this repo? Add info about your change here to be included in next release_ +### 1.1.4 +[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.3...1.1.4) + +__New features__ +- Support for push notifications through `ParseInstallation` ([#78](https://github.com/parse-community/Parse-Swift/pull/78)), thanks to [Corey Baker](https://github.com/cbaker6). +- Fetch with include ([#74](https://github.com/parse-community/Parse-Swift/pull/74)), thanks to [Corey Baker](https://github.com/cbaker6). + +__Improvements__ +- Added `ParseLiveQuery` SwiftUI example to Playgrounds ([#77](https://github.com/parse-community/Parse-Swift/pull/77)), thanks to [Corey Baker](https://github.com/cbaker6). + ### 1.1.3 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.2...1.1.3) diff --git a/ParseSwift.podspec b/ParseSwift.podspec index fa204bd7b..a437c7677 100644 --- a/ParseSwift.podspec +++ b/ParseSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ParseSwift" - s.version = "1.1.3" + s.version = "1.1.4" s.summary = "Parse Pure Swift SDK" s.homepage = "https://github.com/parse-community/Parse-Swift" s.authors = { diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index 0d0bcdfd6..e944190a4 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -95,6 +95,10 @@ 7033ECB825584A83009770F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECB625584A83009770F3 /* Main.storyboard */; }; 7033ECBA25584A85009770F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECB925584A85009770F3 /* Assets.xcassets */; }; 7033ECBD25584A85009770F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7033ECBB25584A85009770F3 /* LaunchScreen.storyboard */; }; + 70386A0625D9718C0048EC1B /* Data+hexString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70386A0525D9718C0048EC1B /* Data+hexString.swift */; }; + 70386A0725D9718C0048EC1B /* Data+hexString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70386A0525D9718C0048EC1B /* Data+hexString.swift */; }; + 70386A0825D9718C0048EC1B /* Data+hexString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70386A0525D9718C0048EC1B /* Data+hexString.swift */; }; + 70386A0925D9718C0048EC1B /* Data+hexString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70386A0525D9718C0048EC1B /* Data+hexString.swift */; }; 7044C17525C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; }; 7044C17625C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; }; 7044C17725C4ECFF0011F6E7 /* ParseCloud+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */; }; @@ -536,6 +540,7 @@ 7033ECB925584A85009770F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7033ECBC25584A85009770F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7033ECBE25584A85009770F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 70386A0525D9718C0048EC1B /* Data+hexString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+hexString.swift"; sourceTree = ""; }; 7044C17425C4ECFF0011F6E7 /* ParseCloud+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseCloud+combine.swift"; sourceTree = ""; }; 7044C18225C4EFC10011F6E7 /* ParseConfig+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseConfig+combine.swift"; sourceTree = ""; }; 7044C19025C4F5B60011F6E7 /* ParseFile+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseFile+combine.swift"; sourceTree = ""; }; @@ -884,6 +889,7 @@ 70110D562506CE890091CC1D /* BaseParseInstallation.swift */, F97B45C224D9C6F200F4A88B /* NoBody.swift */, 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */, + 70386A0525D9718C0048EC1B /* Data+hexString.swift */, ); path = Internal; sourceTree = ""; @@ -1520,6 +1526,7 @@ 7044C1C825C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */, 707A3BF125B0A4F0000D215C /* ParseAuthentication.swift in Sources */, 70D1BE7325BB43EB00A42E7C /* BaseConfig.swift in Sources */, + 70386A0625D9718C0048EC1B /* Data+hexString.swift in Sources */, F97B465F24D9C7B500F4A88B /* KeychainStore.swift in Sources */, 705726E02592C2A800F0ADD5 /* ParseHash.swift in Sources */, 70110D52250680140091CC1D /* ParseConstants.swift in Sources */, @@ -1651,6 +1658,7 @@ 7044C1C925C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */, 707A3BF225B0A4F0000D215C /* ParseAuthentication.swift in Sources */, 70D1BE7425BB43EB00A42E7C /* BaseConfig.swift in Sources */, + 70386A0725D9718C0048EC1B /* Data+hexString.swift in Sources */, F97B466024D9C7B500F4A88B /* KeychainStore.swift in Sources */, 705726E12592C2A800F0ADD5 /* ParseHash.swift in Sources */, 70110D53250680140091CC1D /* ParseConstants.swift in Sources */, @@ -1838,6 +1846,7 @@ 7044C1CB25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */, 707A3BF425B0A4F0000D215C /* ParseAuthentication.swift in Sources */, 70D1BE7625BB43EB00A42E7C /* BaseConfig.swift in Sources */, + 70386A0925D9718C0048EC1B /* Data+hexString.swift in Sources */, F97B460524D9C6F200F4A88B /* NoBody.swift in Sources */, 705726E32592C2A800F0ADD5 /* ParseHash.swift in Sources */, F97B45E124D9C6F200F4A88B /* AnyCodable.swift in Sources */, @@ -1922,6 +1931,7 @@ 7044C1CA25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */, 707A3BF325B0A4F0000D215C /* ParseAuthentication.swift in Sources */, 70D1BE7525BB43EB00A42E7C /* BaseConfig.swift in Sources */, + 70386A0825D9718C0048EC1B /* Data+hexString.swift in Sources */, F97B460424D9C6F200F4A88B /* NoBody.swift in Sources */, 705726E22592C2A800F0ADD5 /* ParseHash.swift in Sources */, F97B45E024D9C6F200F4A88B /* AnyCodable.swift in Sources */, @@ -2225,7 +2235,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; @@ -2249,7 +2259,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; @@ -2315,7 +2325,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SDKROOT = macosx; @@ -2341,7 +2351,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift; PRODUCT_NAME = ParseSwift; SDKROOT = macosx; @@ -2488,7 +2498,7 @@ INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS"; @@ -2517,7 +2527,7 @@ INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS"; PRODUCT_NAME = ParseSwift; @@ -2544,7 +2554,7 @@ INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS"; @@ -2572,7 +2582,7 @@ INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.4; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS"; PRODUCT_NAME = ParseSwift; diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index 63ca4af7c..535011c86 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -5,7 +5,7 @@ bundle exec jazzy \ --author_url http://parseplatform.org \ --github_url https://github.com/parse-community/Parse-Swift \ --root-url http://parseplatform.org/Parse-Swift/api/ \ - --module-version 1.1.3 \ + --module-version 1.1.4 \ --theme fullwidth \ --skip-undocumented \ --output ./docs/api \ diff --git a/Sources/ParseSwift/Internal/Data+hexString.swift b/Sources/ParseSwift/Internal/Data+hexString.swift new file mode 100644 index 000000000..5171ee17d --- /dev/null +++ b/Sources/ParseSwift/Internal/Data+hexString.swift @@ -0,0 +1,42 @@ +// +// Data+hexString.swift +// ParseSwift +// +// Created by Corey Baker on 2/14/21. +// Copyright © 2021 Parse Community. All rights reserved. +// + +import Foundation + +// Credit to: https://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex-string-in-swift +internal extension Data { + struct HexEncodingOptions: OptionSet { + let rawValue: Int + static let upperCase = HexEncodingOptions(rawValue: 1 << 0) + } + + func hexEncodedString(options: HexEncodingOptions = []) -> String { + let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef" + if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { + let utf8Digits = Array(hexDigits.utf8) + return String(unsafeUninitializedCapacity: 2 * count) { (ptr) -> Int in + var mutablePtr = ptr.baseAddress! + for byte in self { + mutablePtr[0] = utf8Digits[Int(byte / 16)] + mutablePtr[1] = utf8Digits[Int(byte % 16)] + mutablePtr += 2 + } + return 2 * count + } + } else { + let utf16Digits = Array(hexDigits.utf16) + var chars: [unichar] = [] + chars.reserveCapacity(2 * count) + for byte in self { + chars.append(utf16Digits[Int(byte / 16)]) + chars.append(utf16Digits[Int(byte % 16)]) + } + return String(utf16CodeUnits: chars, count: chars.count) + } + } +} diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index 445617df9..ec612bd22 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -95,6 +95,17 @@ extension ParseInstallation { return .installations } + + /** + Sets the device token string property from an `Data`-encoded token. + - parameter data: A token that identifies the device. + */ + mutating public func setDeviceToken(_ data: Data) { + let deviceTokenString = data.hexEncodedString() + if deviceToken != deviceTokenString { + deviceToken = deviceTokenString + } + } } // MARK: CurrentInstallationContainer diff --git a/Tests/ParseSwiftTests/ParseInstallationTests.swift b/Tests/ParseSwiftTests/ParseInstallationTests.swift index d00dd6de5..df6599d36 100644 --- a/Tests/ParseSwiftTests/ParseInstallationTests.swift +++ b/Tests/ParseSwiftTests/ParseInstallationTests.swift @@ -155,6 +155,12 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l wait(for: [expectation1], timeout: 20.0) } + func testDeviceTokenAsString() throws { + let data = Data([0, 1, 127, 128, 255]) + XCTAssertEqual(data.hexEncodedString(), "00017f80ff") + XCTAssertEqual(data.hexEncodedString(options: .upperCase), "00017F80FF") + } + func testInstallationMutableValuesCanBeChangedInMemory() { let expectation1 = XCTestExpectation(description: "Update installation1") DispatchQueue.main.async { @@ -165,7 +171,9 @@ class ParseInstallationTests: XCTestCase { // swiftlint:disable:this type_body_l } Installation.current?.customKey = "Changed" + Installation.current?.setDeviceToken(Data([0, 1, 127, 128, 255])) XCTAssertNotEqual(originalInstallation.customKey, Installation.current?.customKey) + XCTAssertNotEqual(originalInstallation.deviceToken, Installation.current?.customKey) expectation1.fulfill() } wait(for: [expectation1], timeout: 20.0) From c455522082fcdacfab6dafde159f95fbd764235a Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sun, 14 Feb 2021 10:44:48 -0500 Subject: [PATCH 2/4] Update docs --- Sources/ParseSwift/Objects/ParseInstallation.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index ec612bd22..fc39e179d 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -29,7 +29,8 @@ import AppKit latest device state if the installation has not recently been saved. `ParseInstallation` installations which have a valid `deviceToken` and are saved to - the Parse cloud can be used to target push notifications. + the Parse cloud can be used to target push notifications. Use `setDeviceToken` to set the + `deviceToken` properly. - warning: Only use `ParseInstallation.current` installations on the main thread as they require UIApplication for `badge` From e63a39e8dade403e8e53e7a16f91c2e826a0838f Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 14 Feb 2021 12:03:02 -0500 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> --- Sources/ParseSwift/Objects/ParseInstallation.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ParseSwift/Objects/ParseInstallation.swift b/Sources/ParseSwift/Objects/ParseInstallation.swift index fc39e179d..dfbd9512c 100644 --- a/Sources/ParseSwift/Objects/ParseInstallation.swift +++ b/Sources/ParseSwift/Objects/ParseInstallation.swift @@ -28,8 +28,8 @@ import AppKit when the `ParseInstallation` is saved, thus these fields might not reflect the latest device state if the installation has not recently been saved. - `ParseInstallation` installations which have a valid `deviceToken` and are saved to - the Parse cloud can be used to target push notifications. Use `setDeviceToken` to set the + `ParseInstallation`s which have a valid `deviceToken` and are saved to + the Parse Server can be used to target push notifications. Use `setDeviceToken` to set the `deviceToken` properly. - warning: Only use `ParseInstallation.current` installations on the main thread as they From 609ec3863cdb857874ac4cda0bb917e5320b95ca Mon Sep 17 00:00:00 2001 From: Corey's iMac Date: Sun, 14 Feb 2021 12:16:09 -0500 Subject: [PATCH 4/4] Compiler check and enable codecov on Swift 5.2 --- .github/workflows/ci.yml | 6 ++++++ Sources/ParseSwift/Internal/Data+hexString.swift | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fe2f86c7..915bdfabf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,12 @@ jobs: run: swift test --enable-code-coverage -v env: DEVELOPER_DIR: ${{ env.CI_XCODE_VER }} + - name: Prepare codecov + run: xcrun llvm-cov export -format="lcov" .build/debug/ParseSwiftPackageTests.xctest/Contents/MacOS/ParseSwiftPackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov + env: + DEVELOPER_DIR: ${{ env.CI_XCODE_VER }} + - name: Send codecov + run: bash <(curl https://codecov.io/bash) linux: runs-on: ubuntu-18.04 diff --git a/Sources/ParseSwift/Internal/Data+hexString.swift b/Sources/ParseSwift/Internal/Data+hexString.swift index 5171ee17d..25855f6a6 100644 --- a/Sources/ParseSwift/Internal/Data+hexString.swift +++ b/Sources/ParseSwift/Internal/Data+hexString.swift @@ -17,6 +17,7 @@ internal extension Data { func hexEncodedString(options: HexEncodingOptions = []) -> String { let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef" + #if compiler(>=5.3) if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { let utf8Digits = Array(hexDigits.utf8) return String(unsafeUninitializedCapacity: 2 * count) { (ptr) -> Int in @@ -38,5 +39,15 @@ internal extension Data { } return String(utf16CodeUnits: chars, count: chars.count) } + #else + let utf16Digits = Array(hexDigits.utf16) + var chars: [unichar] = [] + chars.reserveCapacity(2 * count) + for byte in self { + chars.append(utf16Digits[Int(byte / 16)]) + chars.append(utf16Digits[Int(byte % 16)]) + } + return String(utf16CodeUnits: chars, count: chars.count) + #endif } }