Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

### main

[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...main)
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.1.0...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

### 4.1.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.1...4.1.0)

__Improvements__
- Let the OS and developer decide if app tracking authorization is required when using ParseAnalytics. ParseAnalytics can now take any Codable value in its' dimensions instead of just strings. Added a new property "date" to ParseAnalytics. The "at" property will be deprecated in ParseSwift 5.0.0, so developers should switch to "date". ParseAnalytics can now be properly decoded after encoding with a JSONEncoder. This is useful if ParseAnalytics need to be stored locally and sent to the server later ([#341](https://github.com/parse-community/Parse-Swift/pull/341)), thanks to [Corey Baker](https://github.com/cbaker6).

### 4.0.1
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.0.0...4.0.1)

Expand All @@ -21,7 +27,7 @@ __New features__
- Add DocC for SDK documentation ([#209](https://github.com/parse-community/Parse-Swift/pull/214)), thanks to [Corey Baker](https://github.com/cbaker6).

__Improvements__
- (Breaking Change) Make ParseRelation conform to Codable and add methods to make decoded stored ParseRelations "usable". ParseObjects can now contain properties of ParseRelation<Self>. In addition, ParseRelations can now be made from ParseObject pointers. For ParseRole, the computed properties: users and roles, are now optional. The queryRoles property has been changed to queryRoles() to improve the handling of thrown errors ([#328](https://github.com/parse-community/Parse-Swift/pull/328)), thanks to @cbaker6.
- (Breaking Change) Make ParseRelation conform to Codable and add methods to make decoded stored ParseRelations "usable". ParseObjects can now contain properties of ParseRelation<Self>. In addition, ParseRelations can now be made from ParseObject pointers. For ParseRole, the computed properties: users and roles, are now optional. The queryRoles property has been changed to queryRoles() to improve the handling of thrown errors ([#328](https://github.com/parse-community/Parse-Swift/pull/328)), thanks to [Corey Baker](https://github.com/cbaker6).
- (Breaking Change) Change the following method parameter names: isUsingMongoDB -> usingMongoDB, isIgnoreCustomObjectIdConfig -> ignoringCustomObjectIdConfig, isUsingEQ -> usingEqComparator ([#321](https://github.com/parse-community/Parse-Swift/pull/321)), thanks to [Corey Baker](https://github.com/cbaker6).
- (Breaking Change) Change the following method parameter names: isUsingMongoDB -> usingMongoDB, isIgnoreCustomObjectIdConfig -> ignoringCustomObjectIdConfig, isUsingEQ -> usingEqComparator ([#321](https://github.com/parse-community/Parse-Swift/pull/321)), thanks to [Corey Baker](https://github.com/cbaker6).
- (Breaking Change) Change the following method parameter names: isUsingTransactions -> usingTransactions, isAllowingCustomObjectIds -> allowingCustomObjectIds, isUsingEqualQueryConstraint -> usingEqualQueryConstraint, isMigratingFromObjcSDK -> migratingFromObjcSDK, isDeletingKeychainIfNeeded -> deletingKeychainIfNeeded ([#323](https://github.com/parse-community/Parse-Swift/pull/323)), thanks to [Corey Baker](https://github.com/cbaker6).
Expand Down
42 changes: 21 additions & 21 deletions ParseSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Sources/ParseSwift/Coding/ParseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public struct ParseEncoder {
}

// MARK: _ParseEncoder
private class _ParseEncoder: JSONEncoder, Encoder {
internal class _ParseEncoder: JSONEncoder, Encoder {
var codingPath: [CodingKey]
let dictionary: NSMutableDictionary
let skippedKeys: Set<String>
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/LiveQuery/ParseLiveQuery+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import Foundation

extension ParseLiveQuery {
// MARK: Async/Await
// MARK: Connection - Async/Await

/**
Manually establish a connection to the `ParseLiveQuery` Server.
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
import Combine

extension ParseLiveQuery {
// MARK: Combine
// MARK: Connection - Combine

/**
Manually establish a connection to the `ParseLiveQuery` Server. Publishes when established.
Expand Down
1 change: 0 additions & 1 deletion Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ Not attempting to open ParseLiveQuery socket anymore
}
}

// MARK: Helpers
extension ParseLiveQuery {

/// Current LiveQuery client.
Expand Down
4 changes: 2 additions & 2 deletions Sources/ParseSwift/Objects/ParseRole.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ public extension ParseRole {
}

static func == (lhs: Self, rhs: Self) -> Bool {
lhs.name == rhs.name && lhs.className == rhs.className
lhs.debugDescription == rhs.debugDescription
}

func hash(into hasher: inout Hasher) {
hasher.combine("\(self.className)_\(String(describing: self.name))")
hasher.combine(self.debugDescription)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

enum ParseConstants {
static let sdk = "swift"
static let version = "4.0.1"
static let version = "4.1.0"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
5 changes: 4 additions & 1 deletion Sources/ParseSwift/Types/ParseACL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public struct ParseACL: ParseType,
case write

public init(from decoder: Decoder) throws {
self = Access(rawValue: try decoder.singleValueContainer().decode(String.self))!
guard let decoded = Access(rawValue: try decoder.singleValueContainer().decode(String.self)) else {
throw ParseError(code: .unknownError, message: "Not able to decode ParseACL Access")
}
self = decoded
}

public func encode(to encoder: Encoder) throws {
Expand Down
22 changes: 11 additions & 11 deletions Sources/ParseSwift/Types/ParseAnalytics+async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,19 @@ public extension ParseAnalytics {
- parameter at: Explicitly set the time associated with a given event. If not provided the
server time will be used.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- warning: This method makes a copy of the current `ParseAnalytics` and then mutates
it. You will not have access to the mutated analytic after calling this method.
- throws: An error of type `ParseError`.
*/
func track(dimensions: [String: String]?,
at date: Date? = nil,
options: API.Options = []) async throws {
let _: Void = try await withCheckedThrowingContinuation { continuation in
var analytic = self
analytic.track(dimensions: dimensions,
at: date,
options: options,
completion: continuation.resume)
mutating func track(dimensions: [String: String]?,
at date: Date? = nil,
options: API.Options = []) async throws {
let result = try await withCheckedThrowingContinuation { continuation in
self.track(dimensions: dimensions,
at: date,
options: options,
completion: continuation.resume)
}
if case let .failure(error) = result {
throw error
}
}
}
Expand Down
164 changes: 78 additions & 86 deletions Sources/ParseSwift/Types/ParseAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,46 @@ import Foundation
import UIKit
#endif

#if canImport(AppTrackingTransparency)
import AppTrackingTransparency
#endif

/**
`ParseAnalytics` provides an interface to Parse's logging and analytics
backend.

- warning: For iOS 14.0, macOS 11.0, macCatalyst 14.0, tvOS 14.0, you
will need to request tracking authorization for ParseAnalytics to work.
See Apple's [documentation](https://developer.apple.com/documentation/apptrackingtransparency) for more for details.
`ParseAnalytics` provides an interface to Parse's logging and analytics backend.
*/
public struct ParseAnalytics: ParseType, Hashable {

/// The name of the custom event to report to Parse as having happened.
public let name: String
public var name: String

/// Explicitly set the time associated with a given event. If not provided the server
/// time will be used.
public var at: Date? // swiftlint:disable:this identifier_name
/// - warning: This will be deprecated in ParseSwift 5.0.0 in favor of `date`.
public var at: Date? { // swiftlint:disable:this identifier_name
get {
date
}
set {
date = newValue
}
}

/// Explicitly set the time associated with a given event. If not provided the server
/// time will be used.
public var date: Date?

/// The dictionary of information by which to segment this event.
public var dimensions: [String: String]?
public var dimensions: [String: Codable]? {
get {
convertToString(dimensionsAnyCodable)
}
set {
dimensionsAnyCodable = convertToAnyCodable(newValue)
}
}

var dimensionsAnyCodable: [String: AnyCodable]?

enum CodingKeys: String, CodingKey {
case at, dimensions // swiftlint:disable:this identifier_name
case date = "at"
case dimensions
case name
}

/**
Expand All @@ -47,13 +61,55 @@ public struct ParseAnalytics: ParseType, Hashable {
time will be used. Defaults to `nil`.
*/
public init (name: String,
dimensions: [String: String]? = nil,
at: Date? = nil) { // swiftlint:disable:this identifier_name
dimensions: [String: Codable]? = nil,
at date: Date? = nil) {
self.name = name
self.dimensions = dimensions
self.at = at
self.dimensionsAnyCodable = convertToAnyCodable(dimensions)
self.date = date
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(date, forKey: .date)
try container.encodeIfPresent(dimensionsAnyCodable, forKey: .dimensions)
if !(encoder is _ParseEncoder) {
try container.encode(name, forKey: .name)
}
}

public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.debugDescription == rhs.debugDescription
}

public func hash(into hasher: inout Hasher) {
hasher.combine(self.debugDescription)
}

// MARK: Helpers
func convertToAnyCodable(_ dimensions: [String: Codable]?) -> [String: AnyCodable]? {
guard let dimensions = dimensions else {
return nil
}
var convertedDimensions = [String: AnyCodable]()
for (key, value) in dimensions {
convertedDimensions[key] = AnyCodable(value)
}
return convertedDimensions
}

func convertToString(_ dimensions: [String: AnyCodable]?) -> [String: String]? {
guard let dimensions = dimensions else {
return nil
}
var convertedDimensions = [String: String]()
for (key, value) in dimensions {
convertedDimensions[key] = "\(value.value)"
}
return convertedDimensions
}

// MARK: Intents

#if os(iOS)
/**
Tracks *asynchronously* this application being launched. If this happened as the result of the
Expand All @@ -79,22 +135,6 @@ public struct ParseAnalytics: ParseType, Hashable {
completion: @escaping (Result<Void, ParseError>) -> Void) {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
#if canImport(AppTrackingTransparency)
if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) {
if !ParseSwift.configuration.isTestingSDK {
let status = ATTrackingManager.trackingAuthorizationStatus
if status != .authorized {
callbackQueue.async {
let error = ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "App tracking not authorized. Please request permission from user.")
completion(.failure(error))
}
return
}
}
}
#endif
var userInfo: [String: String]?
if let remoteOptions = launchOptions?[.remoteNotification] as? [String: String] {
userInfo = remoteOptions
Expand All @@ -118,7 +158,7 @@ public struct ParseAnalytics: ParseType, Hashable {
Tracks *asynchronously* this application being launched with additional dimensions.

- parameter dimensions: The dictionary of information by which to segment this
event and can be empty or `nil`.
event. Can be empty or `nil`.
- parameter at: Explicitly set the time associated with a given event. If not provided the
server time will be used.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
Expand All @@ -135,22 +175,6 @@ public struct ParseAnalytics: ParseType, Hashable {
completion: @escaping (Result<Void, ParseError>) -> Void) {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
#if canImport(AppTrackingTransparency)
if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) {
if !ParseSwift.configuration.isTestingSDK {
let status = ATTrackingManager.trackingAuthorizationStatus
if status != .authorized {
callbackQueue.async {
let error = ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "App tracking not authorized. Please request permission from user.")
completion(.failure(error))
}
return
}
}
}
#endif
let appOppened = ParseAnalytics(name: "AppOpened",
dimensions: dimensions,
at: date)
Expand Down Expand Up @@ -180,22 +204,6 @@ public struct ParseAnalytics: ParseType, Hashable {
completion: @escaping (Result<Void, ParseError>) -> Void) {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
#if canImport(AppTrackingTransparency)
if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) {
if !ParseSwift.configuration.isTestingSDK {
let status = ATTrackingManager.trackingAuthorizationStatus
if status != .authorized {
callbackQueue.async {
let error = ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "App tracking not authorized. Please request permission from user.")
completion(.failure(error))
}
return
}
}
}
#endif
self.saveCommand().executeAsync(options: options,
callbackQueue: callbackQueue) { result in
switch result {
Expand All @@ -211,7 +219,7 @@ public struct ParseAnalytics: ParseType, Hashable {
Tracks *asynchronously* the occurrence of a custom event with additional dimensions.

- parameter dimensions: The dictionary of information by which to segment this
event and can be empty or `nil`.
event. Can be empty or `nil`.
- parameter at: Explicitly set the time associated with a given event. If not provided the
server time will be used.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
Expand All @@ -228,24 +236,8 @@ public struct ParseAnalytics: ParseType, Hashable {
completion: @escaping (Result<Void, ParseError>) -> Void) {
var options = options
options.insert(.cachePolicy(.reloadIgnoringLocalCacheData))
#if canImport(AppTrackingTransparency)
if #available(macOS 11.0, iOS 14.0, macCatalyst 14.0, tvOS 14.0, *) {
if !ParseSwift.configuration.isTestingSDK {
let status = ATTrackingManager.trackingAuthorizationStatus
if status != .authorized {
callbackQueue.async {
let error = ParseError(code: .unknownError,
// swiftlint:disable:next line_length
message: "App tracking not authorized. Please request permission from user.")
completion(.failure(error))
}
return
}
}
}
#endif
self.dimensions = dimensions
self.at = date
self.dimensionsAnyCodable = convertToAnyCodable(dimensions)
self.date = date
self.saveCommand().executeAsync(options: options,
callbackQueue: callbackQueue) { result in
switch result {
Expand Down
16 changes: 16 additions & 0 deletions Tests/ParseSwiftTests/ParseACLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@ class ParseACLTests: XCTestCase {
}
}

func testCodingAccess() throws {
let access = ParseACL.Access.read
let encoded = try ParseCoding.jsonEncoder().encode(access)
let decoded = try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: encoded)
XCTAssertEqual(access, decoded)
let access2 = ParseACL.Access.write
let encoded2 = try ParseCoding.jsonEncoder().encode(access2)
let decoded2 = try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: encoded2)
XCTAssertEqual(access2, decoded2)
guard let data = "hello".data(using: .utf8) else {
XCTFail("Should have unwrapped")
return
}
XCTAssertThrowsError(try ParseCoding.jsonDecoder().decode(ParseACL.Access.self, from: data))
}

func testDebugString() {
var acl = ParseACL()
acl.setReadAccess(objectId: "a", value: false)
Expand Down
Loading