From d783343ad302a7f28eadf9b84eb151d9b8857f67 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Thu, 19 Mar 2026 18:57:15 +0200 Subject: [PATCH 1/5] fix(retry-policy): fix excluded endpoints lost after copy Fix `PNRequestRetryConfiguration` `copyWithZone:` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy. fix(subscribe): enable transport-level retry Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests. fix(subscribe): prevent duplicate subscribe loops Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started. refactor(file-sharing): prevent retry of file upload requests Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send. refactor(presence): prevent retry of heartbeat requests Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism. refactor(subscribe): remove reachability ping module Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery. BREAKING CHANGE: Automatic reconnection after prolonged network outage removed; applications must handle `PNUnexpectedDisconnectCategory` and re-subscribe manually. build(spm): use custom modulemap and header search paths for SPM compatibility Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift. test(retry-policy): add retry configuration tests Add unit tests for `copyWithZone:` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable. test(subscribe): add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception. --- .../PubNub Example.xcodeproj/project.pbxproj | 116 --------- .../project.pbxproj | 24 -- Framework/scripts/export_for_spm.sh | 25 +- Package.swift | 6 +- PubNub.podspec | 1 - PubNub/Core/PubNub+Core.m | 68 +---- PubNub/Data/Managers/PNSubscriber.h | 6 - PubNub/Data/Managers/PNSubscriber.m | 109 +++----- .../Data/Service Objects/PNStatus+Private.h | 3 - PubNub/Data/Service Objects/PNStatus.m | 1 - .../Transport/PNRequestRetryConfiguration.m | 1 + PubNub/Network/PNReachability.h | 64 ----- PubNub/Network/PNReachability.m | 192 -------------- .../Requests/Files/PNFileUploadRequest.m | 1 + .../Presence/PNPresenceHeartbeatRequest.m | 8 + .../Requests/Subscribe/PNSubscribeRequest.m | 1 - Tests/Podfile.lock | 8 +- .../Unit/Core/Subscribe/PNSubscribeTest.m | 235 +++++++++++++++++- .../Network/PNRequestRetryConfigurationTest.m | 120 +++++++++ 19 files changed, 423 insertions(+), 566 deletions(-) delete mode 100644 PubNub/Network/PNReachability.h delete mode 100644 PubNub/Network/PNReachability.m diff --git a/Example/PubNub Example.xcodeproj/project.pbxproj b/Example/PubNub Example.xcodeproj/project.pbxproj index 2edf7cb0f..9a9444a4d 100644 --- a/Example/PubNub Example.xcodeproj/project.pbxproj +++ b/Example/PubNub Example.xcodeproj/project.pbxproj @@ -20,16 +20,13 @@ 6003F59E195388D20070C39A /* PNAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* PNAppDelegate.m */; }; 6003F5A7195388D20070C39A /* PNViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* PNViewController.m */; }; 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; }; - 6E62272CF5614DB346C8AFCF /* Pods_PubNub_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F572CBC0DC97DD3F470035D2 /* Pods_PubNub_Example.framework */; }; 793D26AF1B44049400509447 /* VERSION in Resources */ = {isa = PBXBuildFile; fileRef = 793D26AE1B44049400509447 /* VERSION */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; A530F8C524085644001B2B08 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = A530F8C424085644001B2B08 /* CHANGELOG.md */; }; A530F8C624085644001B2B08 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = A530F8C424085644001B2B08 /* CHANGELOG.md */; }; - E074AE87BDB6C44276D1D512 /* Pods_PubNub_Mac_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0451137235DA1596688C4D44 /* Pods_PubNub_Mac_Example.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0451137235DA1596688C4D44 /* Pods_PubNub_Mac_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PubNub_Mac_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 511725D41BE92F7B008F069E /* PubNub Mac Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PubNub Mac Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 511725D61BE92F7B008F069E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 511725D71BE92F7B008F069E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -39,7 +36,6 @@ 511725DF1BE92F7B008F069E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 511725E21BE92F7B008F069E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 511725E41BE92F7B008F069E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 5AE2B490CFDD8C02EECF89C7 /* Pods-PubNub_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNub_Example.debug.xcconfig"; path = "Target Support Files/Pods-PubNub_Example/Pods-PubNub_Example.debug.xcconfig"; sourceTree = ""; }; 6003F58A195388D20070C39A /* PubNub_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PubNub_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -57,12 +53,8 @@ 793D26AE1B44049400509447 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = VERSION; path = ../VERSION; sourceTree = ""; }; 7A9945019D33BCC2C3B3B7C1 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; - A149D49D150B7B757D55D167 /* Pods-PubNub_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNub_Example.release.xcconfig"; path = "Target Support Files/Pods-PubNub_Example/Pods-PubNub_Example.release.xcconfig"; sourceTree = ""; }; A530F8C424085644001B2B08 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; C4C1ED79724D56B45766CFFA /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - DEEB307881C0C803272E5996 /* Pods-PubNub Mac Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNub Mac Example.release.xcconfig"; path = "Target Support Files/Pods-PubNub Mac Example/Pods-PubNub Mac Example.release.xcconfig"; sourceTree = ""; }; - F572CBC0DC97DD3F470035D2 /* Pods_PubNub_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PubNub_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FDE08A50B1DD0A6FF09F6257 /* Pods-PubNub Mac Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PubNub Mac Example.debug.xcconfig"; path = "Target Support Files/Pods-PubNub Mac Example/Pods-PubNub Mac Example.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -70,7 +62,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E074AE87BDB6C44276D1D512 /* Pods_PubNub_Mac_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,7 +72,6 @@ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, - 6E62272CF5614DB346C8AFCF /* Pods_PubNub_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -119,7 +109,6 @@ 511725D51BE92F7B008F069E /* PubNub Mac Example */, 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, - 64EBBFA0F7BF43305BE7BEF0 /* Pods */, ); sourceTree = ""; }; @@ -138,8 +127,6 @@ 6003F58D195388D20070C39A /* Foundation.framework */, 6003F58F195388D20070C39A /* CoreGraphics.framework */, 6003F591195388D20070C39A /* UIKit.framework */, - 0451137235DA1596688C4D44 /* Pods_PubNub_Mac_Example.framework */, - F572CBC0DC97DD3F470035D2 /* Pods_PubNub_Example.framework */, ); name = Frameworks; sourceTree = ""; @@ -182,18 +169,6 @@ name = "Podspec Metadata"; sourceTree = ""; }; - 64EBBFA0F7BF43305BE7BEF0 /* Pods */ = { - isa = PBXGroup; - children = ( - FDE08A50B1DD0A6FF09F6257 /* Pods-PubNub Mac Example.debug.xcconfig */, - DEEB307881C0C803272E5996 /* Pods-PubNub Mac Example.release.xcconfig */, - 5AE2B490CFDD8C02EECF89C7 /* Pods-PubNub_Example.debug.xcconfig */, - A149D49D150B7B757D55D167 /* Pods-PubNub_Example.release.xcconfig */, - ); - name = Pods; - path = ../Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -201,11 +176,9 @@ isa = PBXNativeTarget; buildConfigurationList = 511725F41BE92F7B008F069E /* Build configuration list for PBXNativeTarget "PubNub Mac Example" */; buildPhases = ( - 3E97D35FEE390FF4192C0DFC /* [CP] Check Pods Manifest.lock */, 511725D01BE92F7B008F069E /* Sources */, 511725D11BE92F7B008F069E /* Frameworks */, 511725D21BE92F7B008F069E /* Resources */, - 2CD31650FEB28D6485FD69C1 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -220,11 +193,9 @@ isa = PBXNativeTarget; buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "PubNub_Example" */; buildPhases = ( - 027CEE2CC9DCB8600172CD3E /* [CP] Check Pods Manifest.lock */, 6003F586195388D20070C39A /* Sources */, 6003F587195388D20070C39A /* Frameworks */, 6003F588195388D20070C39A /* Resources */, - 2C7E52831852C5D96DA8A5E8 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -297,89 +268,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 027CEE2CC9DCB8600172CD3E /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PubNub_Example-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 2C7E52831852C5D96DA8A5E8 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PubNub_Example/Pods-PubNub_Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/PubNub-iOS/PubNub.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PubNub.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubNub_Example/Pods-PubNub_Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 2CD31650FEB28D6485FD69C1 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PubNub Mac Example/Pods-PubNub Mac Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/PubNub-macOS/PubNub.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PubNub.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PubNub Mac Example/Pods-PubNub Mac Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 3E97D35FEE390FF4192C0DFC /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PubNub Mac Example-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 511725D01BE92F7B008F069E /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -425,7 +313,6 @@ /* Begin XCBuildConfiguration section */ 511725F01BE92F7B008F069E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FDE08A50B1DD0A6FF09F6257 /* Pods-PubNub Mac Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -448,7 +335,6 @@ }; 511725F11BE92F7B008F069E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DEEB307881C0C803272E5996 /* Pods-PubNub Mac Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -606,7 +492,6 @@ }; 6003F5C0195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5AE2B490CFDD8C02EECF89C7 /* Pods-PubNub_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; @@ -640,7 +525,6 @@ }; 6003F5C1195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A149D49D150B7B757D55D167 /* Pods-PubNub_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; diff --git a/Framework/PubNub Framework.xcodeproj/project.pbxproj b/Framework/PubNub Framework.xcodeproj/project.pbxproj index 805dcc3aa..78aa07853 100644 --- a/Framework/PubNub Framework.xcodeproj/project.pbxproj +++ b/Framework/PubNub Framework.xcodeproj/project.pbxproj @@ -64,7 +64,6 @@ 7915820A1BD709C60084FC70 /* PNPresenceGlobalHereNowResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0951BD03DE4001FC34D /* PNPresenceGlobalHereNowResult.m */; }; 7915820B1BD709C60084FC70 /* PNString.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BC1BD03DE4001FC34D /* PNString.m */; }; 7915820C1BD709C60084FC70 /* PubNub+Core.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB05B1BD03DE4001FC34D /* PubNub+Core.m */; }; - 7915820D1BD709C60084FC70 /* PNReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0F31BD03DE4001FC34D /* PNReachability.m */; }; 7915820E1BD709C60084FC70 /* PNData.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B31BD03DE4001FC34D /* PNData.m */; }; 791582101BD709C60084FC70 /* PNDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B51BD03DE4001FC34D /* PNDictionary.m */; }; 791582111BD709C60084FC70 /* PNSubscribeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0A61BD03DE4001FC34D /* PNSubscribeStatus.m */; }; @@ -142,7 +141,6 @@ 7915827B1BD709C60084FC70 /* PNJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B91BD03DE4001FC34D /* PNJSON.h */; }; 7915827D1BD709C60084FC70 /* PNGZIP.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B61BD03DE4001FC34D /* PNGZIP.h */; }; 7915827F1BD709C60084FC70 /* PNHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B81BD03DE4001FC34D /* PNHelpers.h */; }; - 791582811BD709C60084FC70 /* PNReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0F21BD03DE4001FC34D /* PNReachability.h */; }; 791582831BD709C60084FC70 /* PNURLRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0BD1BD03DE4001FC34D /* PNURLRequest.h */; }; 791582841BD709C60084FC70 /* PNStatus+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0A01BD03DE4001FC34D /* PNStatus+Private.h */; }; 791582871BD709C60084FC70 /* PNServiceData+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB09D1BD03DE4001FC34D /* PNServiceData+Private.h */; }; @@ -165,7 +163,6 @@ 791582B31BD709D10084FC70 /* PNPresenceGlobalHereNowResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0951BD03DE4001FC34D /* PNPresenceGlobalHereNowResult.m */; }; 791582B41BD709D10084FC70 /* PNString.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BC1BD03DE4001FC34D /* PNString.m */; }; 791582B51BD709D10084FC70 /* PubNub+Core.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB05B1BD03DE4001FC34D /* PubNub+Core.m */; }; - 791582B61BD709D10084FC70 /* PNReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0F31BD03DE4001FC34D /* PNReachability.m */; }; 791582B71BD709D10084FC70 /* PNData.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B31BD03DE4001FC34D /* PNData.m */; }; 791582B91BD709D10084FC70 /* PNDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B51BD03DE4001FC34D /* PNDictionary.m */; }; 791582BA1BD709D10084FC70 /* PNSubscribeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0A61BD03DE4001FC34D /* PNSubscribeStatus.m */; }; @@ -243,7 +240,6 @@ 791583241BD709D10084FC70 /* PNJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B91BD03DE4001FC34D /* PNJSON.h */; }; 791583261BD709D10084FC70 /* PNGZIP.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B61BD03DE4001FC34D /* PNGZIP.h */; }; 791583281BD709D10084FC70 /* PNHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B81BD03DE4001FC34D /* PNHelpers.h */; }; - 7915832A1BD709D10084FC70 /* PNReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0F21BD03DE4001FC34D /* PNReachability.h */; }; 7915832C1BD709D10084FC70 /* PNURLRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0BD1BD03DE4001FC34D /* PNURLRequest.h */; }; 7915832D1BD709D10084FC70 /* PNStatus+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0A01BD03DE4001FC34D /* PNStatus+Private.h */; }; 791583301BD709D10084FC70 /* PNServiceData+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB09D1BD03DE4001FC34D /* PNServiceData+Private.h */; }; @@ -456,8 +452,6 @@ 798842911C18F292003E8948 /* PNJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BA1BD03DE4001FC34D /* PNJSON.m */; }; 798842921C18F292003E8948 /* PNString.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BC1BD03DE4001FC34D /* PNString.m */; }; 798842931C18F292003E8948 /* PNURLRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BE1BD03DE4001FC34D /* PNURLRequest.m */; }; - 7988429D1C18F2BD003E8948 /* PNReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0F21BD03DE4001FC34D /* PNReachability.h */; }; - 798842A21C18F2C2003E8948 /* PNReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0F31BD03DE4001FC34D /* PNReachability.m */; }; 798842EC1C18FC54003E8948 /* PubNub-iOS-Info.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7915839D1BD7119E0084FC70 /* PubNub-iOS-Info.plist */; }; 799D60EA24C1B33900171C29 /* PNSendFileStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 799D60E824C1B33900171C29 /* PNSendFileStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; 799D60EB24C1B33900171C29 /* PNSendFileStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 799D60E824C1B33900171C29 /* PNSendFileStatus.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -763,7 +757,6 @@ 79A8BC1F1C58F93900015BDE /* PNPresenceGlobalHereNowResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0951BD03DE4001FC34D /* PNPresenceGlobalHereNowResult.m */; }; 79A8BC201C58F93900015BDE /* PNString.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0BC1BD03DE4001FC34D /* PNString.m */; }; 79A8BC211C58F93900015BDE /* PubNub+Core.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB05B1BD03DE4001FC34D /* PubNub+Core.m */; }; - 79A8BC221C58F93900015BDE /* PNReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0F31BD03DE4001FC34D /* PNReachability.m */; }; 79A8BC231C58F93900015BDE /* PNData.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B31BD03DE4001FC34D /* PNData.m */; }; 79A8BC251C58F93900015BDE /* PNDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0B51BD03DE4001FC34D /* PNDictionary.m */; }; 79A8BC261C58F93900015BDE /* PNSubscribeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0A61BD03DE4001FC34D /* PNSubscribeStatus.m */; }; @@ -842,7 +835,6 @@ 79A8BC911C58F93900015BDE /* PNJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B91BD03DE4001FC34D /* PNJSON.h */; }; 79A8BC931C58F93900015BDE /* PNGZIP.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B61BD03DE4001FC34D /* PNGZIP.h */; }; 79A8BC961C58F93900015BDE /* PNHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0B81BD03DE4001FC34D /* PNHelpers.h */; }; - 79A8BC981C58F93900015BDE /* PNReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0F21BD03DE4001FC34D /* PNReachability.h */; }; 79A8BC9A1C58F93900015BDE /* PNURLRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0BD1BD03DE4001FC34D /* PNURLRequest.h */; }; 79A8BC9B1C58F93900015BDE /* PNStatus+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0A01BD03DE4001FC34D /* PNStatus+Private.h */; }; 79A8BC9E1C58F93900015BDE /* PNServiceData+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB09D1BD03DE4001FC34D /* PNServiceData+Private.h */; }; @@ -963,8 +955,6 @@ 79CBB1651BD03DE4001FC34D /* PNStructures.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0CA1BD03DE4001FC34D /* PNStructures.h */; settings = {ATTRIBUTES = (Public, ); }; }; 79CBB1661BD03DE4001FC34D /* PNEventsListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0CC1BD03DE4001FC34D /* PNEventsListener.h */; settings = {ATTRIBUTES = (Public, ); }; }; 79CBB1671BD03DE4001FC34D /* PNParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0CD1BD03DE4001FC34D /* PNParser.h */; }; - 79CBB18A1BD03DE4001FC34D /* PNReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CBB0F21BD03DE4001FC34D /* PNReachability.h */; }; - 79CBB18B1BD03DE4001FC34D /* PNReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 79CBB0F31BD03DE4001FC34D /* PNReachability.m */; }; 79CBB1A11BD03EEA001FC34D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 79CBB1A01BD03EEA001FC34D /* libz.tbd */; }; 79CFA2AD26DE0AEB00D206D4 /* PNCBORDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CFA2AB26DE0AEB00D206D4 /* PNCBORDecoder.h */; }; 79CFA2AE26DE0AEB00D206D4 /* PNCBORDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 79CFA2AB26DE0AEB00D206D4 /* PNCBORDecoder.h */; }; @@ -3558,8 +3548,6 @@ 79CBB0CA1BD03DE4001FC34D /* PNStructures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PNStructures.h; sourceTree = ""; }; 79CBB0CC1BD03DE4001FC34D /* PNEventsListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PNEventsListener.h; sourceTree = ""; }; 79CBB0CD1BD03DE4001FC34D /* PNParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PNParser.h; sourceTree = ""; }; - 79CBB0F21BD03DE4001FC34D /* PNReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PNReachability.h; sourceTree = ""; }; - 79CBB0F31BD03DE4001FC34D /* PNReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PNReachability.m; sourceTree = ""; }; 79CBB19E1BD03EE4001FC34D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 79CBB1A01BD03EEA001FC34D /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 79CBB1DC1BD043BD001FC34D /* build_universal.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_universal.sh; sourceTree = ""; }; @@ -4527,8 +4515,6 @@ A55A874522FD8272002D0A72 /* Requests */, A523EEDD2BFE80C0009F45AA /* Responses */, A53248DF2C28E3AB003510FF /* Parsers */, - 79CBB0F21BD03DE4001FC34D /* PNReachability.h */, - 79CBB0F31BD03DE4001FC34D /* PNReachability.m */, ); name = Network; path = ../../PubNub/Network; @@ -5766,7 +5752,6 @@ A55A861022FD80B9002D0A72 /* PNRemoveUUIDMetadataAPICallBuilder.h in Headers */, 79A0D8661DC22C950039A264 /* PNSubscribeChannelsOrGroupsAPIBuilder.h in Headers */, 791582641BD709C60084FC70 /* PubNub+Presence.h in Headers */, - 791582811BD709C60084FC70 /* PNReachability.h in Headers */, 791582691BD709C60084FC70 /* PubNub+History.h in Headers */, 79A0D8461DC22C950039A264 /* PNAPNSModificationAPICallBuilder.h in Headers */, A51B4A192BF0AE57008C3370 /* PNDecoder.h in Headers */, @@ -6138,7 +6123,6 @@ A55A886A22FD8272002D0A72 /* PNManageMembershipsRequest.h in Headers */, A55A883222FD8272002D0A72 /* PNRemoveChannelMetadataRequest.h in Headers */, 7915830D1BD709D10084FC70 /* PubNub+Presence.h in Headers */, - 7915832A1BD709D10084FC70 /* PNReachability.h in Headers */, 791583121BD709D10084FC70 /* PubNub+History.h in Headers */, A55680452C229D8D003C974F /* PNFileSendData.h in Headers */, A55A861222FD80B9002D0A72 /* PNRemoveUUIDMetadataAPICallBuilder.h in Headers */, @@ -6570,7 +6554,6 @@ A55680DF2C2428E8003C974F /* PNSubscribeFileEventData.h in Headers */, A5567F9B2C220E32003C974F /* PNFetchMembershipsResult.h in Headers */, 7988422D1C18F08D003E8948 /* PubNub+Publish.h in Headers */, - 7988429D1C18F2BD003E8948 /* PNReachability.h in Headers */, 7988422B1C18F081003E8948 /* PubNub+History.h in Headers */, A5A451EF246F2679008ECC74 /* PNRemoveChannelMembersAPICallBuilder.h in Headers */, A5567ED62C208652003C974F /* PNSubscribeEventData.h in Headers */, @@ -6893,7 +6876,6 @@ A55A886922FD8272002D0A72 /* PNManageMembershipsRequest.h in Headers */, A55A883122FD8272002D0A72 /* PNRemoveChannelMetadataRequest.h in Headers */, 79A8BC7A1C58F93900015BDE /* PubNub+Presence.h in Headers */, - 79A8BC981C58F93900015BDE /* PNReachability.h in Headers */, 79A8BC7F1C58F93900015BDE /* PubNub+History.h in Headers */, A55680442C229D8D003C974F /* PNFileSendData.h in Headers */, A55A861122FD80B9002D0A72 /* PNRemoveUUIDMetadataAPICallBuilder.h in Headers */, @@ -7271,7 +7253,6 @@ 79A0D8731DC22F090039A264 /* PNAPICallBuilder+Private.h in Headers */, A55A886B22FD8272002D0A72 /* PNManageMembershipsRequest.h in Headers */, A55A883322FD8272002D0A72 /* PNRemoveChannelMetadataRequest.h in Headers */, - 79CBB18A1BD03DE4001FC34D /* PNReachability.h in Headers */, 79CBB0FF1BD03DE4001FC34D /* PubNub+History.h in Headers */, 79CBB1041BD03DE4001FC34D /* PubNub+Publish.h in Headers */, A55680472C229D8D003C974F /* PNFileSendData.h in Headers */, @@ -8076,7 +8057,6 @@ A504E15924AA9CFB006DCF5B /* PNFilesAPICallBuilder.m in Sources */, 7915824B1BD709C60084FC70 /* PubNub+Publish.m in Sources */, 79A0D85F1DC22C950039A264 /* PNStreamAPICallBuilder.m in Sources */, - 7915820D1BD709C60084FC70 /* PNReachability.m in Sources */, A5A7B0072349330F0060113B /* PNBaseMessageActionRequest.m in Sources */, 79A0D8611DC22C950039A264 /* PNStreamAuditAPICallBuilder.m in Sources */, A5567E8F2C1FDEF7003C974F /* PNMessageActionsFetchData.m in Sources */, @@ -8371,7 +8351,6 @@ 791582F41BD709D10084FC70 /* PubNub+Publish.m in Sources */, 79A0D94A1DC230E80039A264 /* PNStreamModificationAPICallBuilder.m in Sources */, A5A7B0092349330F0060113B /* PNBaseMessageActionRequest.m in Sources */, - 791582B61BD709D10084FC70 /* PNReachability.m in Sources */, A5567E942C1FDEF7003C974F /* PNMessageActionsFetchData.m in Sources */, 791582B21BD709D10084FC70 /* PubNub+History.m in Sources */, 791582DF1BD709D10084FC70 /* PNServiceData.m in Sources */, @@ -8661,7 +8640,6 @@ A5046E4324784CA90008C81E /* PNSetMembershipsAPICallBuilder.m in Sources */, A504E15D24AA9CFB006DCF5B /* PNFilesAPICallBuilder.m in Sources */, 79A0D96B1DC231370039A264 /* PNSubscribeAPIBuilder.m in Sources */, - 798842A21C18F2C2003E8948 /* PNReachability.m in Sources */, 79A0D94D1DC230EA0039A264 /* PNStreamModificationAPICallBuilder.m in Sources */, A5A7B00C2349330F0060113B /* PNBaseMessageActionRequest.m in Sources */, 798842511C18F1AE003E8948 /* PubNub+History.m in Sources */, @@ -8957,7 +8935,6 @@ 79A8BC611C58F93900015BDE /* PubNub+Publish.m in Sources */, 79A0D9491DC230E80039A264 /* PNStreamModificationAPICallBuilder.m in Sources */, A5A7B0082349330F0060113B /* PNBaseMessageActionRequest.m in Sources */, - 79A8BC221C58F93900015BDE /* PNReachability.m in Sources */, A5567E912C1FDEF7003C974F /* PNMessageActionsFetchData.m in Sources */, 79A8BC1E1C58F93900015BDE /* PubNub+History.m in Sources */, 79A8BC4C1C58F93900015BDE /* PNServiceData.m in Sources */, @@ -9252,7 +9229,6 @@ A5A7B00A2349330F0060113B /* PNBaseMessageActionRequest.m in Sources */, 79CBB1001BD03DE4001FC34D /* PubNub+History.m in Sources */, A5567E962C1FDEF7003C974F /* PNMessageActionsFetchData.m in Sources */, - 79CBB18B1BD03DE4001FC34D /* PNReachability.m in Sources */, A51B4A8C2BF0B197008C3370 /* PNFunctions.m in Sources */, A5046E8C24784CAA0008C81E /* PNRemoveChannelMetadataRequest.m in Sources */, A57A30E4238DC87400DE8C68 /* PNAPNSNotificationTarget.m in Sources */, diff --git a/Framework/scripts/export_for_spm.sh b/Framework/scripts/export_for_spm.sh index 9ad427924..e2479567f 100755 --- a/Framework/scripts/export_for_spm.sh +++ b/Framework/scripts/export_for_spm.sh @@ -114,20 +114,15 @@ else done fi -[[ -e "PubNub.h" ]] && rm "PubNub.h" - cd "../" -if [[ $PUBLIC_ONLY == 1 ]]; then - for HEADER_PATH in "${PUBLIC_HEADERS[@]}"; do - FILENAME="$(echo "$HEADER_PATH" | rev | cut -d/ -f1 | rev)" - ! [[ -e "$FILENAME" ]] && ln -s "../$HEADER_PATH" - done -else - for HEADER_PATH in "${ALL_HEADERS[@]}"; do - FILENAME="$(echo "$HEADER_PATH" | rev | cut -d/ -f1 | rev)" - ! [[ -e "$FILENAME" ]] && ln -s "../$HEADER_PATH" "$FILENAME" - done -fi - -[[ -e "PubNub.h" ]] && rm "PubNub.h" \ No newline at end of file +# Create module.modulemap that uses a directory umbrella instead of an umbrella header. +# This avoids SPM enforcing that all public headers must be imported through PubNub.h, +# while still allowing `#import ` and `#import ` to resolve. +cat > module.modulemap <<'MODULEMAP' +module PubNub { + umbrella "PubNub" + export * + module * { export * } +} +MODULEMAP \ No newline at end of file diff --git a/Package.swift b/Package.swift index e4eae4e86..32b445ebb 100644 --- a/Package.swift +++ b/Package.swift @@ -53,7 +53,11 @@ let package = Package( name: "PubNub", dependencies: [], path: "Sources/PubNub", - resources: [.copy("../../Framework/PubNub/PrivacyInfo.xcprivacy")] + resources: [.copy("../../Framework/PubNub/PrivacyInfo.xcprivacy")], + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("include/PubNub") + ] ) ], swiftLanguageVersions: [.v5] diff --git a/PubNub.podspec b/PubNub.podspec index bf4a9aa6f..a6034bbe5 100644 --- a/PubNub.podspec +++ b/PubNub.podspec @@ -50,7 +50,6 @@ Pod::Spec.new do |spec| "PubNub/Modules/Serializer/Object/{PNJSONDecoder,PNJSONEncoder}.h", "PubNub/Modules/Crypto/Cryptors/AES/PNCCCryptorWrapper.h", "PubNub/Modules/Crypto/Header/*.h", - 'PubNub/Network/PNReachability.h', 'PubNub/Network/Requests/Files/PNGenerateFileUploadURLRequest.h', 'PubNub/Network/Parsers/**/*.h', 'PubNub/Network/Streams/*.h', diff --git a/PubNub/Core/PubNub+Core.m b/PubNub/Core/PubNub+Core.m index 30f463bc4..aa49fdf02 100644 --- a/PubNub/Core/PubNub+Core.m +++ b/PubNub/Core/PubNub+Core.m @@ -20,7 +20,6 @@ #import "PNEventsListener.h" #import "PNConfiguration.h" #import "PNConsoleLogger.h" -#import "PNReachability.h" #import "PNConstants.h" #import "PNHelpers.h" @@ -113,12 +112,6 @@ @interface PubNub () /// Resources access lock. @property(strong, nonatomic) PNLock *lock; -/// Reachability helper. -/// -/// Helper used by client to know about when something happened with network and when it is safe to issue requests to -/// the **PubNub** network. -@property (nonatomic, strong) PNReachability *reachability; - #pragma mark - Initialization @@ -141,13 +134,8 @@ - (instancetype)initWithConfiguration:(PNConfiguration *)configuration /// Update current client state. /// -/// Use subscription status to translate it to proper client state and launch service reachability check if will be -/// required. -/// -/// - Parameters: -/// - recentClientStatus: Recent subscriber state which should be used to set proper client state. -/// - shouldCheckReachability: Whether service reachability check should be started or not. -- (void)setRecentClientStatus:(PNStatusCategory)recentClientStatus withReachabilityCheck:(BOOL)shouldCheckReachability; +/// - Parameter recentClientStatus: Recent subscriber state which should be used to set proper client state. +- (void)setRecentClientStatus:(PNStatusCategory)recentClientStatus; #pragma mark - Crypto module @@ -156,12 +144,6 @@ - (void)setRecentClientStatus:(PNStatusCategory)recentClientStatus withReachabil - (void)prepareCryptoModule; -#pragma mark - Reachability - -/// Complete reachability helper configuration. -- (void)prepareReachability; - - #pragma mark - PubNub Network managers /// Initialize and configure required **PubNub** network managers. @@ -264,7 +246,6 @@ - (instancetype)initWithConfiguration:(PNConfiguration *)configuration callbackQ _heartbeatManager = [PNHeartbeat heartbeatForClient:self]; [self addListener:self]; - [self prepareReachability]; #if TARGET_OS_OSX || TARGET_OS_IOS && !defined(TARGET_IS_EXTENSION) SEL selector = @selector(handleContextTransition:); @@ -348,28 +329,17 @@ - (void)setLogLevel:(PNLogLevel)level { self.logger.logLevel = level; } -- (void)setRecentClientStatus:(PNStatusCategory)recentClientStatus withReachabilityCheck:(BOOL)shouldCheckReachability { - PNStatusCategory previousState = self.recentClientStatus; +- (void)setRecentClientStatus:(PNStatusCategory)recentClientStatus { PNStatusCategory currentState = recentClientStatus; - + if (currentState == PNReconnectedCategory) currentState = PNConnectedCategory; if (currentState == PNDisconnectedCategory && ([[self channels] count] || [[self channelGroups] count] || [[self presenceChannels] count])) { currentState = PNConnectedCategory; } - + self->_recentClientStatus = currentState; - - if (currentState == PNUnexpectedDisconnectCategory && shouldCheckReachability) { - if (previousState == PNDisconnectedCategory) return; - - __weak __typeof(self) weakSelf = self; - dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); - dispatch_after(delay, self.callbackQueue, ^{ - [weakSelf.reachability startServicePing]; - }); - } } @@ -393,32 +363,6 @@ - (void)prepareCryptoModule { } -#pragma mark - Reachability - -- (void)prepareReachability { - __weak __typeof(self) weakSelf = self; - _reachability = [PNReachability reachabilityForClient:self withPingStatus:^(BOOL pingSuccessful) { - if (pingSuccessful) { - /** - * Silence static analyzer warnings. - * Code is aware about this case and at the end will simply call on 'nil' object method. - * In most cases if referenced object become 'nil' it mean what there is no more need in - * it and probably whole client instance has been deallocated. - */ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" - [weakSelf.reachability stopServicePing]; - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_MSEC), queue, ^{ - [weakSelf cancelSubscribeOperations]; - [weakSelf.subscriberManager restoreSubscriptionCycleIfRequiredWithCompletion:nil]; - }); - #pragma clang diagnostic pop - } - }]; -} - - #pragma mark - PubNub Network managers - (void)prepareNetworkManagers { @@ -576,7 +520,7 @@ - (void)client:(PubNub *)__unused client didReceiveStatus:(PNSubscribeStatus *)s status.category == PNReconnectedCategory || status.category == PNDisconnectedCategory || status.category == PNUnexpectedDisconnectCategory) { - [self setRecentClientStatus:status.category withReachabilityCheck:status.requireNetworkAvailabilityCheck]; + [self setRecentClientStatus:status.category]; } } diff --git a/PubNub/Data/Managers/PNSubscriber.h b/PubNub/Data/Managers/PNSubscriber.h index f898229cd..4faed4f76 100644 --- a/PubNub/Data/Managers/PNSubscriber.h +++ b/PubNub/Data/Managers/PNSubscriber.h @@ -97,12 +97,6 @@ typedef void(^PNSubscriberCompletionBlock)(PNSubscribeStatus * _Nullable status) /// - Parameter request: Request with information about resources from which client will receive real-time updates. - (void)subscribeWithRequest:(PNSubscribeRequest *)request; -/// Try restore subscription cycle by using **0** time token and if required try to catch up on previous subscribe time -/// token (basing on user configuration). -/// -/// - Parameter block: Subscription completion block which is used to notify code. -- (void)restoreSubscriptionCycleIfRequiredWithCompletion:(nullable PNSubscriberCompletionBlock)block; - /// Continue subscription cycle using `currentTimeToken` value and channels, stored in cache. /// /// - Parameter block: Subscription completion block which is used to notify code. diff --git a/PubNub/Data/Managers/PNSubscriber.m b/PubNub/Data/Managers/PNSubscriber.m index 7862509fd..25be09af5 100644 --- a/PubNub/Data/Managers/PNSubscriber.m +++ b/PubNub/Data/Managers/PNSubscriber.m @@ -96,18 +96,15 @@ @interface PNSubscriber () /// **0** for initial subscription loop and non-zero for long-poll requests. @property(copy, nonatomic, readonly) NSNumber *lastTimeTokenRegion; -/// Whether subscriber potentially should expect for subscription restore call or not. -/// -/// In case if client tried to connect and failed or disconnected because of network issues this flag should be set to -/// `YES`. -@property(assign, nonatomic) BOOL mayRequireSubscriptionRestore; - -/// Whether subscriber recovers after network issues. -@property(assign, nonatomic) BOOL restoringAfterNetworkIssues; - /// Current subscriber state. @property(assign, nonatomic) PNSubscriberState currentState; +/// Generation counter incremented on each new initial subscribe cycle. +/// +/// Used to detect and discard stale continuation calls that were dispatched asynchronously before a new subscribe +/// cycle started, preventing duplicate subscribe loops. +@property(assign, nonatomic) NSUInteger subscribeCycleGeneration; + /// Time token which is used for current subscribe loop iteration. /// /// **0** for initial subscription loop and non-zero for long-poll requests. @@ -345,7 +342,6 @@ - (void)appendSubscriberInformation:(PNStatus *)status; @implementation PNSubscriber -@synthesize restoringAfterNetworkIssues = _restoringAfterNetworkIssues; @synthesize currentTimeTokenRegion = _currentTimeTokenRegion; @synthesize lastTimeTokenRegion = _lastTimeTokenRegion; @synthesize overrideTimeToken = _overrideTimeToken; @@ -355,21 +351,6 @@ @implementation PNSubscriber #pragma mark - State Information and Manipulation -- (BOOL)restoringAfterNetworkIssues { - __block BOOL restoring = NO; - - [self.lock readAccessWithBlock:^{ - restoring = self->_restoringAfterNetworkIssues; - }]; - - return restoring; -} - -- (void)setRestoringAfterNetworkIssues:(BOOL)restoringAfterNetworkIssues { - [self.lock writeAccessWithBlock:^{ - self->_restoringAfterNetworkIssues = restoringAfterNetworkIssues; - }]; -} - (NSArray *)allObjects { return [[[self channels] arrayByAddingObjectsFromArray:[self presenceChannels]] @@ -543,7 +524,6 @@ - (void)updateStateTo:(PNSubscriberState)state BOOL shouldHandleTransition = NO; if (targetState == PNConnectedSubscriberState) { - self.mayRequireSubscriptionRestore = YES; category = PNConnectedCategory; // Check whether client transit from 'disconnected' -> 'connected' state. @@ -581,23 +561,19 @@ - (void)updateStateTo:(PNSubscriberState)state targetState == currentState)); category = ((targetState == PNDisconnectedSubscriberState) ? PNDisconnectedCategory : PNUnexpectedDisconnectCategory); - self.mayRequireSubscriptionRestore = shouldHandleTransition; } else if (targetState == PNAccessRightsErrorSubscriberState) { - self.mayRequireSubscriptionRestore = NO; shouldHandleTransition = YES; category = PNAccessDeniedCategory; } else if (targetState == PNMalformedFilterExpressionErrorSubscriberState) { // Change state to 'Unexpected disconnect' targetState = PNDisconnectedUnexpectedlySubscriberState; - self.mayRequireSubscriptionRestore = NO; shouldHandleTransition = YES; category = PNMalformedFilterExpressionCategory; } else if (targetState == PNPNRequestURITooLongErrorSubscriberState) { // Change state to 'Unexpected disconnect' targetState = PNDisconnectedUnexpectedlySubscriberState; - - self.mayRequireSubscriptionRestore = NO; + shouldHandleTransition = YES; category = PNRequestURITooLongCategory; } @@ -694,8 +670,7 @@ - (void)subscribeWithRequest:(PNSubscribeRequest *)request { } } - self.restoringAfterNetworkIssues = NO; - [self subscribe:YES + [self subscribe:YES usingTimeToken:request.timetoken withState:request.state queryParameters:request.arbitraryQueryParameters @@ -719,12 +694,12 @@ - (void)subscribe:(BOOL)initialSubscribe #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" if ([self allObjects].count) { - if (!self.restoringAfterNetworkIssues) self.overrideTimeToken = timeToken; + self.overrideTimeToken = timeToken; if (initialSubscribe) { [self.lock writeAccessWithBlock:^{ - self.mayRequireSubscriptionRestore = NO; - + self->_subscribeCycleGeneration++; + if (self->_currentTimeToken && [self->_currentTimeToken compare:@0] != NSOrderedSame) { self->_lastTimeToken = self->_currentTimeToken; } @@ -774,7 +749,6 @@ - (void)subscribe:(BOOL)initialSubscribe self->_currentTimeToken = @0; self->_lastTimeTokenRegion = @(-1); self->_currentTimeTokenRegion = @(-1); - self->_restoringAfterNetworkIssues = NO; self->_overrideTimeToken = nil; }]; @@ -799,24 +773,6 @@ - (void)subscribe:(BOOL)initialSubscribe #pragma clang diagnostic pop } -- (void)restoreSubscriptionCycleIfRequiredWithCompletion:(PNSubscriberCompletionBlock)block { - __block BOOL shouldRestore; - __block BOOL ableToRestore; - - [self.lock readAccessWithBlock:^{ - shouldRestore = ((self.currentState == PNDisconnectedUnexpectedlySubscriberState && - self.mayRequireSubscriptionRestore)); - ableToRestore = [self.channelsSet count] || [self.channelGroupsSet count] || - [self.presenceChannelsSet count]; - }]; - - if (shouldRestore && ableToRestore) { - self.restoringAfterNetworkIssues = YES; - - [self subscribe:YES usingTimeToken:nil withState:nil queryParameters:nil completion:block]; - } else if (block) block(nil); -} - - (void)continueSubscriptionCycleIfRequiredWithCompletion:(PNSubscriberCompletionBlock)block { [self subscribe:NO usingTimeToken:nil withState:nil queryParameters:nil completion:block]; } @@ -909,11 +865,10 @@ - (void)unsubscribeFromChannels:(NSArray *)channels self->_currentTimeToken = @0; self->_lastTimeTokenRegion = @(-1); self->_currentTimeTokenRegion = @(-1); - self->_restoringAfterNetworkIssues = NO; self->_overrideTimeToken = nil; }]; } - + if (channelsWithOutPresence.count || groupsWithOutPresence.count) { PNPresenceLeaveRequest *request = [PNPresenceLeaveRequest requestWithChannels:channelsWithOutPresence channelGroups:groupsWithOutPresence]; @@ -1024,9 +979,6 @@ - (void)handleFailedSubscriptionStatus:(PNSubscribeStatus *)status { statusCategory == PNTLSConnectionFailedCategory) { PNSubscriberState subscriberState = PNAccessRightsErrorSubscriberState; - if (statusCategory != PNMalformedFilterExpressionCategory && statusCategory != PNRequestURITooLongCategory) - ((PNStatus *)status).requireNetworkAvailabilityCheck = NO; - if (statusCategory == PNMalformedFilterExpressionCategory) { subscriberState = PNMalformedFilterExpressionErrorSubscriberState; } else if(statusCategory == PNRequestURITooLongCategory) { @@ -1043,8 +995,6 @@ - (void)handleFailedSubscriptionStatus:(PNSubscribeStatus *)status { [self updateStateTo:subscriberState withStatus:status completion:nil]; } else { - ((PNStatus *)status).requireNetworkAvailabilityCheck = YES; - [self.lock writeAccessWithBlock:^{ if (self.client.configuration.shouldTryCatchUpOnSubscriptionRestore) { if (self->_currentTimeToken && @@ -1085,10 +1035,8 @@ - (void)handleFailedSubscriptionStatus:(PNSubscribeStatus *)status { - (void)handleSubscription:(BOOL)initialSubscription timeToken:(NSNumber *)timeToken region:(NSNumber *)region { [self.lock writeAccessWithBlock:^{ - BOOL restoringAfterNetworkIssues = self->_restoringAfterNetworkIssues; - self->_restoringAfterNetworkIssues = NO; BOOL shouldAcceptNewTimeToken = YES; - + // Whether time token should be overridden despite subscription behavior configuration. BOOL shouldOverrideTimeToken = (initialSubscription && self->_overrideTimeToken && [self->_overrideTimeToken compare:@0] != NSOrderedSame); @@ -1098,13 +1046,8 @@ - (void)handleSubscription:(BOOL)initialSubscription timeToken:(NSNumber *)timeT if (initialSubscription) { // 'shouldKeepTimeTokenOnListChange' property should never allow to reset time tokens in // case if there is a few more subscribe requests is waiting for their turn to be sent. - BOOL shouldUseLastTimeToken = self.client.configuration.shouldKeepTimeTokenOnListChange; - - if (!shouldUseLastTimeToken && restoringAfterNetworkIssues) { - shouldUseLastTimeToken = self.client.configuration.shouldTryCatchUpOnSubscriptionRestore; - } - - shouldUseLastTimeToken = (shouldUseLastTimeToken && !shouldOverrideTimeToken); + BOOL shouldUseLastTimeToken = (self.client.configuration.shouldKeepTimeTokenOnListChange && + !shouldOverrideTimeToken); // Ensure what we already don't use value from previous time token assigned during // previous sessions. @@ -1148,14 +1091,19 @@ - (void)handleLiveFeedEvents:(PNSubscribeStatus *)status forInitialSubscription: NSUInteger messageCountThreshold = self.client.configuration.requestMessageCountThreshold; NSMutableArray *events = [status.data.updates mutableCopy]; NSUInteger eventsCount = events.count; - + + __block NSUInteger generation; + [self.lock readAccessWithBlock:^{ + generation = self->_subscribeCycleGeneration; + }]; + if (!events.count) { [self continueSubscriptionCycleIfRequiredWithCompletion:nil]; return; } [self.client.listenersManager notifyWithBlock:^{ - // Check whether after initial subscription client should use user-provided timetoken to catch up on messages + // Check whether after initial subscription client should use user-provided timetoken to catch up on messages // since specified date. if (initialSubscription && overrideTimeToken && [overrideTimeToken compare:@0] != NSOrderedSame) { [self clearCacheFromMessagesNewerThan:overrideTimeToken]; @@ -1163,7 +1111,18 @@ - (void)handleLiveFeedEvents:(PNSubscribeStatus *)status forInitialSubscription: // Remove message duplicates from received events list. [self deDuplicateMessages:events]; - [self continueSubscriptionCycleIfRequiredWithCompletion:nil]; + + __block BOOL shouldContinue; + [self.lock readAccessWithBlock:^{ + shouldContinue = (generation == self->_subscribeCycleGeneration); + }]; + + if (shouldContinue) { + [self continueSubscriptionCycleIfRequiredWithCompletion:nil]; + } else { + NSLog(@"~~~~~> HOD UP"); + NSLog(@"~~~~~> HOD UP BOI"); + } // Check whether number of messages exceed specified threshold or not. if (messageCountThreshold > 0 && eventsCount >= messageCountThreshold) { diff --git a/PubNub/Data/Service Objects/PNStatus+Private.h b/PubNub/Data/Service Objects/PNStatus+Private.h index feafe248e..f0144fc09 100644 --- a/PubNub/Data/Service Objects/PNStatus+Private.h +++ b/PubNub/Data/Service Objects/PNStatus+Private.h @@ -20,9 +20,6 @@ NS_ASSUME_NONNULL_BEGIN /// List of channels on which client currently subscribed. @property(copy, nonatomic) NSArray *subscribedChannels; -/// Whether request require network availability check or not. -@property(assign, nonatomic) BOOL requireNetworkAvailabilityCheck; - /// **PubNub** server region identifier (which generated `currentTimetoken` value). @property(strong, nonatomic) NSNumber *currentTimeTokenRegion; diff --git a/PubNub/Data/Service Objects/PNStatus.m b/PubNub/Data/Service Objects/PNStatus.m index 5b84d41a9..b43731dfc 100644 --- a/PubNub/Data/Service Objects/PNStatus.m +++ b/PubNub/Data/Service Objects/PNStatus.m @@ -92,7 +92,6 @@ - (instancetype)initWithOperation:(PNOperationType)operation category:(PNStatusC - (id)copyWithZone:(NSZone *)zone { PNStatus *status = [super copyWithZone:zone]; - status.requireNetworkAvailabilityCheck = self.requireNetworkAvailabilityCheck; status.category = self.category; status.subscribedChannels = self.subscribedChannels; status.subscribedChannelGroups = self.subscribedChannelGroups; diff --git a/PubNub/Data/Transport/PNRequestRetryConfiguration.m b/PubNub/Data/Transport/PNRequestRetryConfiguration.m index c1908895c..ef34c93fe 100644 --- a/PubNub/Data/Transport/PNRequestRetryConfiguration.m +++ b/PubNub/Data/Transport/PNRequestRetryConfiguration.m @@ -172,6 +172,7 @@ - (instancetype)initWithPolicy:(PNRequestRetryPolicy)policy - (id)copyWithZone:(NSZone *)zone { PNRequestRetryConfiguration *configuration = [[PNRequestRetryConfiguration allocWithZone:zone] init]; + configuration.excludedEndpoints = self.excludedEndpoints; configuration.maximumInterval = self.maximumInterval; configuration.minimumDelay = self.minimumDelay; configuration.maximumRetry = self.maximumRetry; diff --git a/PubNub/Network/PNReachability.h b/PubNub/Network/PNReachability.h deleted file mode 100644 index 4036066ee..000000000 --- a/PubNub/Network/PNReachability.h +++ /dev/null @@ -1,64 +0,0 @@ -#import - - -#pragma mark Class forward - -@class PubNub; - - -NS_ASSUME_NONNULL_BEGIN - -/** - * @brief \b PubNub network reachability / ping utility. - * - * @discussion This class used by \b PubNub client to check whether current \b PubNub network state - * allow to send any requests to it or not. - * Mostly this method used after unexpected disconnection (on network failure) to start remote - * service ping process (at least ping once). - * - * @author Serhii Mamontov - * @since 4.0 - * @copyright © 2010-2019 PubNub, Inc. - */ -@interface PNReachability : NSObject - - -#pragma mark - Initialization and Configuration - -/** - * @brief Construct reachability helper which will allow to identify current \b PubNub network - * state. - * - * @param client \b PubNub client for which this helper has been created. - * @param block Block which is called by helper to inform about current ping round results. - * - * @return Constructed and ready to use reachability helper instance. - */ -+ (instancetype)reachabilityForClient:(PubNub *)client - withPingStatus:(void(^)(BOOL pingSuccessful))block; - - -#pragma mark - Service ping - -/** - * @brief Launch process with remote service pinging. - * - * @discussion Ping process involves process with reachability state correlation to \b time API call - * response. In case if \b time API will return valid response - mean what \b PubNub network service - * ready to process requests. - * - * @note Ping process will remain active till \c -stopServicePing method will be called. - */ -- (void)startServicePing; - -/** - * @brief Stop any active reachability check timers and requests. - */ -- (void)stopServicePing; - -#pragma mark - - - -@end - -NS_ASSUME_NONNULL_END diff --git a/PubNub/Network/PNReachability.m b/PubNub/Network/PNReachability.m deleted file mode 100644 index 6d7cb3414..000000000 --- a/PubNub/Network/PNReachability.m +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @author Serhii Mamontov - * @copyright © 2010-2019 PubNub, Inc. - */ -#import "PNReachability.h" -#import "PubNub+CorePrivate.h" -#import "PNStringLogEntry.h" -#import "PNConfiguration.h" -#import "PNTimeResult.h" -#import "PNStructures.h" -#import "PubNub+Time.h" -#import "PubNub+Core.h" - - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Protected interface declaration - -@interface PNReachability () - - -#pragma mark - Information - -/** - * @brief Client which created this reachability helper instance and will be used to call \b time - * API for \c ping requests. - */ -@property (nonatomic, weak) PubNub *client; - -/** - * @brief Queue which is used to serialize access to shared reachability helper resources. - */ -@property (nonatomic, strong) dispatch_queue_t resourceAccessQueue; - -/** - * @brief Whether remote service ping active at this moment or not. - */ -@property (nonatomic, assign, getter = pingingRemoteService) BOOL pingRemoteService; - -/** - * @brief Copy of block which should be called every time when \b ping API receives response from - * \b PubNub network or network sub-system. - */ -@property (nonatomic, copy) void(^pingCompleteBlock)(BOOL pingSuccessful); - -/** - * @brief Reachability monitor used to track state of network connection. - */ -@property (nonatomic, assign) BOOL reachable; - - -#pragma mark - Initialization and Configuration - -/** - * @brief Initialize reachability helper which will allow to identify current \b PubNub network - * state. - * - * @param client \b PubNub client for which this helper has been created. - * @param block Block which is called by helper to inform about current ping round results. - * - * @return Initialized and ready to use reachability helper instance. - */ -- (instancetype)initForClient:(PubNub *)client withPingStatus:(void(^)(BOOL pingSuccessful))block; - - -#pragma mark - Handlers - -/** - * @brief Process service response. - * - * @note In case if there is no response object or it's content malformed reachability will be set - * to 'not available'. - * - * @param result Time API calling result object. - */ -- (void)handleServicePingResult:(PNTimeResult *)result; - -#pragma mark - - - -@end - -NS_ASSUME_NONNULL_END - - -#pragma mark - Interface implementation - -@implementation PNReachability - -@synthesize pingRemoteService = _pingRemoteService; - - -#pragma mark - Initialization and Configuration - -+ (instancetype)reachabilityForClient:(PubNub *)client withPingStatus:(void (^)(BOOL))block { - - return [[self alloc] initForClient:client withPingStatus:block]; -} - -- (instancetype)initForClient:(PubNub *)client withPingStatus:(void(^)(BOOL pingSuccessful))block { - - // Check whether initialization was successful or not. - if ((self = [super init])) { - _client = client; - _pingCompleteBlock = [block copy]; - _resourceAccessQueue = dispatch_queue_create("com.pubnub.reachability", DISPATCH_QUEUE_CONCURRENT); - _reachable = YES; - } - - return self; -} - -- (BOOL)pingingRemoteService { - - __block BOOL pingingRemoteService = NO; - - dispatch_sync(self.resourceAccessQueue, ^{ - pingingRemoteService = self->_pingRemoteService; - }); - - return pingingRemoteService; -} - -- (void)setPingRemoteService:(BOOL)pingRemoteService { - - dispatch_barrier_async(self.resourceAccessQueue, ^{ - self->_pingRemoteService = pingRemoteService; - }); -} - - -#pragma mark - Service ping - -- (void)startServicePing { - - if (!self.pingingRemoteService) { - self.pingRemoteService = YES; - // Try to request 'time' API to ensure what network really available. - __weak __typeof(self) weakSelf = self; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self.client timeWithCompletion:^(PNTimeResult *result, __unused PNErrorStatus *status) { - [weakSelf handleServicePingResult:result]; - }]; -#pragma clang diagnostic pop - } -} - -- (void)stopServicePing { - - self.pingRemoteService = NO; -} - - -#pragma mark - Handlers - -- (void)handleServicePingResult:(PNTimeResult *)result { - - BOOL successfulPing = result.data != nil; - - if (self.reachable && !successfulPing) { - [self.client.logger debugWithLocation:@"PNReachability" andMessageFactory:^PNLogEntry * _Nullable{ - return [PNStringLogEntry entryWithMessage:@"Connection went down."]; - }]; - } - - if (!self.reachable && successfulPing) { - [self.client.logger debugWithLocation:@"PNReachability" andMessageFactory:^PNLogEntry * _Nullable{ - return [PNStringLogEntry entryWithMessage:@"Connection restored."]; - }]; - } - - if (self.pingCompleteBlock) { - self.pingCompleteBlock(successfulPing); - } - - if (self.pingingRemoteService) { - NSTimeInterval delay = ((self.reachable && !successfulPing) ? 1.f : 10.0f); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - - self.pingRemoteService = NO; - [self startServicePing]; - }); - } - self.reachable = successfulPing; -} - -#pragma mark - - - -@end diff --git a/PubNub/Network/Requests/Files/PNFileUploadRequest.m b/PubNub/Network/Requests/Files/PNFileUploadRequest.m index 8680b73ad..3f7925cab 100644 --- a/PubNub/Network/Requests/Files/PNFileUploadRequest.m +++ b/PubNub/Network/Requests/Files/PNFileUploadRequest.m @@ -117,6 +117,7 @@ - (PNOperationType)operation { - (PNTransportRequest *)request { PNTransportRequest *request = super.request; request.timeout = self.subscribeMaximumIdleTime; + request.retriable = NO; return request; } diff --git a/PubNub/Network/Requests/Presence/PNPresenceHeartbeatRequest.m b/PubNub/Network/Requests/Presence/PNPresenceHeartbeatRequest.m index 2e15c6731..61cca0aa0 100644 --- a/PubNub/Network/Requests/Presence/PNPresenceHeartbeatRequest.m +++ b/PubNub/Network/Requests/Presence/PNPresenceHeartbeatRequest.m @@ -1,5 +1,6 @@ #import "PNPresenceHeartbeatRequest.h" #import "PNBaseRequest+Private.h" +#import "PNTransportRequest.h" #import "PNFunctions.h" #import "PNHelpers.h" @@ -52,6 +53,13 @@ @implementation PNPresenceHeartbeatRequest #pragma mark - Properties +- (PNTransportRequest *)request { + PNTransportRequest *request = super.request; + request.retriable = NO; + + return request; +} + - (PNOperationType)operation { return PNHeartbeatOperation; } diff --git a/PubNub/Network/Requests/Subscribe/PNSubscribeRequest.m b/PubNub/Network/Requests/Subscribe/PNSubscribeRequest.m index 525b6499f..645779ec8 100644 --- a/PubNub/Network/Requests/Subscribe/PNSubscribeRequest.m +++ b/PubNub/Network/Requests/Subscribe/PNSubscribeRequest.m @@ -66,7 +66,6 @@ - (PNTransportRequest *)request { PNTransportRequest *request = super.request; request.timeout = self.subscribeMaximumIdleTime; request.cancellable = YES; - request.retriable = NO; return request; } diff --git a/Tests/Podfile.lock b/Tests/Podfile.lock index b7a132267..1b20b3a5b 100644 --- a/Tests/Podfile.lock +++ b/Tests/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Cucumberish (1.4.0) - OCMock (3.6) - - PubNub (5.8.0): - - PubNub/Core (= 5.8.0) - - PubNub/Core (5.8.0) + - PubNub (6.1.1): + - PubNub/Core (= 6.1.1) + - PubNub/Core (6.1.1) - YAHTTPVCR (1.5.1) DEPENDENCIES: @@ -25,7 +25,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Cucumberish: 6cbd0c1f50306b369acebfe7d9f514c9c287d26c OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 - PubNub: 97be21b701517f8bf1ae962ecb25e6f056b03e6b + PubNub: a394e17be27ef477feffaef373f9699299284ad1 YAHTTPVCR: 000a59a2ce38ae24dea6ca2a769e1cf81d629093 PODFILE CHECKSUM: 7ee58f1df93d3b0f3d5c9c3d7fcf22aa7e7b071d diff --git a/Tests/Tests/Unit/Core/Subscribe/PNSubscribeTest.m b/Tests/Tests/Unit/Core/Subscribe/PNSubscribeTest.m index f6b9aae2b..841b418d0 100644 --- a/Tests/Tests/Unit/Core/Subscribe/PNSubscribeTest.m +++ b/Tests/Tests/Unit/Core/Subscribe/PNSubscribeTest.m @@ -1,7 +1,81 @@ #import "PNRecordableTestCase.h" #import #import +#import "PNSubscribeMessageEventData+Private.h" +#import "PNSubscribeEventData+Private.h" +#import "PNSubscribeStatus+Private.h" +#import "PNTransportConfiguration+Private.h" +#import "PNTransportRequest+Private.h" #import "PNBaseRequest+Private.h" +#import "PNURLSessionTransport.h" + + +#pragma mark - Subscribe retry test protocol + +static NSInteger _PNSubscribeRetryProtocolRequestCount = 0; + +/// URL protocol that returns HTTP 500 on the first subscribe request and HTTP 200 on the retry. +@interface PNSubscribeRetryTestProtocol : NSURLProtocol +@end + +@implementation PNSubscribeRetryTestProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + return [request.URL.path containsString:@"/v2/subscribe/"]; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { + return request; +} + +- (void)startLoading { + _PNSubscribeRetryProtocolRequestCount++; + + if (_PNSubscribeRetryProtocolRequestCount == 1) { + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:500 + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [self.client URLProtocolDidFinishLoading:self]; + } else { + NSData *data = [@"{\"t\":{\"t\":\"17000000000000000\",\"r\":2},\"m\":[]}" + dataUsingEncoding:NSUTF8StringEncoding]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL + statusCode:200 + HTTPVersion:@"HTTP/1.1" + headerFields:@{@"Content-Type": @"application/json"}]; + [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [self.client URLProtocol:self didLoadData:data]; + [self.client URLProtocolDidFinishLoading:self]; + } +} + +- (void)stopLoading {} + +@end + + +#pragma mark - Private subscriber methods exposure + +@interface PNSubscriber (TestAccess) + +/// Generation counter for subscribe cycle. +@property (assign, nonatomic) NSUInteger subscribeCycleGeneration; + +/// Process live feed events from subscribe response. +- (void)handleLiveFeedEvents:(PNSubscribeStatus *)status + forInitialSubscription:(BOOL)initialSubscription + overrideTimeToken:(nullable NSNumber *)overrideTimeToken; + +/// Start subscribe cycle. +- (void)subscribe:(BOOL)initialSubscribe + usingTimeToken:(nullable NSNumber *)timeToken + withState:(nullable NSDictionary *)state + queryParameters:(nullable NSDictionary *)queryParameters + completion:(nullable PNSubscriberCompletionBlock)block; + +@end NS_ASSUME_NONNULL_BEGIN @@ -44,7 +118,7 @@ - (void)testItShouldSetProperRequestValues { XCTAssertEqual(transportRequest.timeout, self.client.configuration.subscribeMaximumIdleTime); XCTAssertTrue(transportRequest.cancellable); - XCTAssertFalse(transportRequest.retriable); + XCTAssertTrue(transportRequest.retriable); }); [self waitForObject:clientTransportMock recordedInvocationCall:recorded afterBlock:^{ @@ -52,6 +126,165 @@ - (void)testItShouldSetProperRequestValues { }]; } + +#pragma mark - Tests :: Subscribe cycle continuation + +/// Test that the subscribe loop continues normally when no new subscribe cycle interrupts event processing. +- (void)testItShouldContinueSubscriptionCycleWhenGenerationUnchanged { + PNSubscriber *subscriber = self.client.subscriberManager; + [subscriber addChannels:@[@"ch-a"]]; + + NSUInteger generationBefore = subscriber.subscribeCycleGeneration; + + __block NSInteger sendRequestCount = 0; + id transportMock = [self mockForObject:self.client.subscriptionNetwork]; + OCMStub([transportMock sendRequest:[OCMArg any] withCompletionBlock:[OCMArg any]]) + .andDo(^(__unused NSInvocation *invocation) { sendRequestCount++; }); + + id mockStatus = [self mockSubscribeStatusWithEvents]; + + // Trigger event handling without starting a new cycle (generation stays the same). + [subscriber handleLiveFeedEvents:mockStatus forInitialSubscription:NO overrideTimeToken:nil]; + + // Wait for the async block on listenersManager.resourceAccessQueue to complete. + [self waitForAsyncOperationsWithDelay:0.5]; + + XCTAssertEqual(subscriber.subscribeCycleGeneration, generationBefore, + @"Generation should not change when no new cycle starts"); + // Continuation should have fired, triggering one sendRequest call. + XCTAssertEqual(sendRequestCount, 1, @"Normal continuation should trigger a subscribe request"); +} + +/// Test that a stale continuation from an old subscribe cycle is suppressed when a new cycle has started. +/// +/// This reproduces the race condition: +/// 1. Long-poll response arrives with events, `handleLiveFeedEvents:` captures the current generation and dispatches +/// continuation async to `listenersManager.resourceAccessQueue`. +/// 2. User subscribes to new channels — `subscribe:YES` increments the generation counter. +/// 3. The async block from step 1 checks the generation, sees a mismatch, and skips the stale continuation. +/// +/// Without the generation counter fix, step 3 would proceed with the stale continuation, creating a duplicate +/// subscribe loop. +- (void)testItShouldNotContinueStaleSubscriptionCycleWhenNewCycleStarted { + PNSubscriber *subscriber = self.client.subscriberManager; + [subscriber addChannels:@[@"ch-a"]]; + + NSUInteger generationBefore = subscriber.subscribeCycleGeneration; + + __block NSInteger sendRequestCount = 0; + id transportMock = [self mockForObject:self.client.subscriptionNetwork]; + OCMStub([transportMock sendRequest:[OCMArg any] withCompletionBlock:[OCMArg any]]) + .andDo(^(__unused NSInvocation *invocation) { sendRequestCount++; }); + + id mockStatus = [self mockSubscribeStatusWithEvents]; + + // Step 1: Trigger event handling — captures generation and dispatches async continuation. + [subscriber handleLiveFeedEvents:mockStatus forInitialSubscription:NO overrideTimeToken:nil]; + + // Step 2: Start a new subscribe cycle (increments generation under the lock). + // This synchronously sends one subscribe request via the mocked transport. + [subscriber subscribe:YES usingTimeToken:@0 withState:nil queryParameters:nil completion:nil]; + + // Wait for the async block on listenersManager.resourceAccessQueue to complete. + [self waitForAsyncOperationsWithDelay:0.5]; + + XCTAssertGreaterThan(subscriber.subscribeCycleGeneration, generationBefore, + @"Generation should have incremented after subscribe:YES"); + // Only the explicit subscribe:YES request should have been sent. + // The stale continuation from handleLiveFeedEvents should have been suppressed by the generation check. + XCTAssertEqual(sendRequestCount, 1, @"Stale continuation should not trigger an additional subscribe request"); +} + + +#pragma mark - Tests :: Subscribe retry + +/// Test that the transport retries a subscribe request when the server responds with HTTP 500. +/// +/// Uses a custom `NSURLProtocol` to return 500 on the first attempt and 200 on the retry. +/// Verifies that the transport's retry mechanism kicks in and the final response is successful. +- (void)testSubscribeRequestShouldBeRetriedOnServerError { + _PNSubscribeRetryProtocolRequestCount = 0; + + // Set up transport with retry configuration (short delay for fast tests). + PNURLSessionTransport *transport = [PNURLSessionTransport new]; + PNTransportConfiguration *config = [PNTransportConfiguration new]; + config.maximumConnections = 1; + PNRequestRetryConfiguration *retryConfig = [PNRequestRetryConfiguration configurationWithLinearDelay:0.1f + maximumRetry:2 + excludedEndpoints:0]; + config.retryConfiguration = retryConfig; + [transport setupWithConfiguration:config]; + + // Replace the transport's session with one that uses our test protocol. + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + sessionConfig.protocolClasses = @[[PNSubscribeRetryTestProtocol class]]; + NSOperationQueue *queue = [NSOperationQueue new]; + queue.maxConcurrentOperationCount = 1; + NSURLSession *testSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil delegateQueue:queue]; + [transport setValue:testSession forKey:@"session"]; + + // Build a subscribe-like transport request. + PNTransportRequest *request = [PNTransportRequest new]; + request.origin = @"https://ps.pndsn.com"; + request.path = @"/v2/subscribe/demo/test-channel/0"; + request.timeout = 10; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Subscribe retry"]; + + [transport sendRequest:request withCompletionBlock:^(PNTransportRequest *req, + id response, + PNError *error) { + XCTAssertNil(error, @"Final response should not have an error after successful retry"); + XCTAssertEqual(response.statusCode, 200, @"Final response should be 200 after retry"); + XCTAssertEqual(req.retryAttempt, 1, @"Request should have been retried once"); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; + + XCTAssertEqual(_PNSubscribeRetryProtocolRequestCount, 2, + @"Protocol should have seen exactly 2 requests (initial + 1 retry)"); +} + + +#pragma mark - Helpers + +/// Create a mock `PNSubscribeStatus` with one event to exercise the async `notifyWithBlock:` path. +- (id)mockSubscribeStatusWithEvents { + id mockCursor = OCMClassMock([PNSubscribeCursorData class]); + OCMStub([mockCursor timetoken]).andReturn(@"17000000000000000"); + OCMStub([(PNSubscribeCursorData *)mockCursor region]).andReturn(@2); + + id mockEvent = OCMClassMock([PNSubscribeMessageEventData class]); + OCMStub([(PNSubscribeEventData *)mockEvent messageType]).andReturn(@0); + OCMStub([mockEvent timetoken]).andReturn(@17000000000000000); + OCMStub([mockEvent channel]).andReturn(@"ch-a"); + OCMStub([mockEvent subscription]).andReturn(@"ch-a"); + OCMStub([mockEvent message]).andReturn(@{@"text": @"hello"}); + + id mockData = OCMClassMock([PNSubscribeData class]); + OCMStub([mockData updates]).andReturn(@[mockEvent]); + OCMStub([mockData cursor]).andReturn(mockCursor); + + id mockStatus = OCMClassMock([PNSubscribeStatus class]); + OCMStub([mockStatus isError]).andReturn(NO); + OCMStub([(PNSubscribeStatus *)mockStatus category]).andReturn(PNAcknowledgmentCategory); + OCMStub([mockStatus isInitialSubscription]).andReturn(NO); + OCMStub([mockStatus data]).andReturn(mockData); + + return mockStatus; +} + +/// Block the current thread until async operations have had time to complete. +- (void)waitForAsyncOperationsWithDelay:(NSTimeInterval)delay { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_semaphore_signal(semaphore); + }); + dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay + 2) * NSEC_PER_SEC))); +} + #pragma mark - @end diff --git a/Tests/Tests/Unit/Network/PNRequestRetryConfigurationTest.m b/Tests/Tests/Unit/Network/PNRequestRetryConfigurationTest.m index af1ece4de..b2c2a7fb9 100644 --- a/Tests/Tests/Unit/Network/PNRequestRetryConfigurationTest.m +++ b/Tests/Tests/Unit/Network/PNRequestRetryConfigurationTest.m @@ -1,4 +1,9 @@ #import +#import +#import +#import "PNPresenceHeartbeatRequest.h" +#import "PNFileUploadRequest.h" +#import "PNBaseRequest+Private.h" #import "PNRecordableTestCase.h" @@ -130,6 +135,60 @@ - (void)testItShouldNotProvideConfiguredDelayWhenTargetEndpointExcludedWithLinea XCTAssertEqual([configuration retryDelayForFailedRequest:request withResponse:response retryAttempt:2], -1.f); } +- (void)testItShouldNotRetryNonSubscribeEndpointsWithDefaultConfiguration { + PNConfiguration *clientConfiguration = [PNConfiguration configurationWithPublishKey:@"demo" + subscribeKey:@"demo" + userID:@"test-user"]; + PNConfiguration *copiedConfiguration = [clientConfiguration copy]; + PNRequestRetryConfiguration *retryConfig = copiedConfiguration.requestRetry; + + XCTAssertNotNil(retryConfig); + + NSURLRequest *publishRequest = [self requestForEndpoint:PNMessageSendEndpoint]; + NSURLResponse *publishResponse = [self failedURLResponseForRequest:publishRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([retryConfig retryDelayForFailedRequest:publishRequest withResponse:publishResponse retryAttempt:1], -1.f, + @"Publish should not be retried with default configuration"); + + NSURLRequest *presenceRequest = [self requestForEndpoint:PNPresenceEndpoint]; + NSURLResponse *presenceResponse = [self failedURLResponseForRequest:presenceRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([retryConfig retryDelayForFailedRequest:presenceRequest withResponse:presenceResponse retryAttempt:1], -1.f, + @"Presence should not be retried with default configuration"); + + NSURLRequest *historyRequest = [self requestForEndpoint:PNMessageStorageEndpoint]; + NSURLResponse *historyResponse = [self failedURLResponseForRequest:historyRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([retryConfig retryDelayForFailedRequest:historyRequest withResponse:historyResponse retryAttempt:1], -1.f, + @"Message storage should not be retried with default configuration"); + + NSURLRequest *filesRequest = [self requestForEndpoint:PNFilesEndpoint]; + NSURLResponse *filesResponse = [self failedURLResponseForRequest:filesRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([retryConfig retryDelayForFailedRequest:filesRequest withResponse:filesResponse retryAttempt:1], -1.f, + @"Files should not be retried with default configuration"); + + NSURLRequest *appContextRequest = [self requestForEndpoint:PNAppContextEndpoint]; + NSURLResponse *appContextResponse = [self failedURLResponseForRequest:appContextRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([retryConfig retryDelayForFailedRequest:appContextRequest withResponse:appContextResponse retryAttempt:1], -1.f, + @"App Context should not be retried with default configuration"); +} + +- (void)testItShouldPreserveExcludedEndpointsAfterCopyWithLinearPolicy { + PNRequestRetryConfiguration *configuration = [PNRequestRetryConfiguration configurationWithLinearDelay:4.5f + maximumRetry:5 + excludedEndpoints:PNAppContextEndpoint, PNMessageSendEndpoint, 0]; + PNRequestRetryConfiguration *configurationCopy = [configuration copy]; + + NSURLRequest *appContextRequest = [self requestForEndpoint:PNAppContextEndpoint]; + NSURLResponse *appContextResponse = [self failedURLResponseForRequest:appContextRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([configurationCopy retryDelayForFailedRequest:appContextRequest withResponse:appContextResponse retryAttempt:1], -1.f); + + NSURLRequest *publishRequest = [self requestForEndpoint:PNMessageSendEndpoint]; + NSURLResponse *publishResponse = [self failedURLResponseForRequest:publishRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([configurationCopy retryDelayForFailedRequest:publishRequest withResponse:publishResponse retryAttempt:1], -1.f); + + NSURLRequest *presenceRequest = [self requestForEndpoint:PNPresenceEndpoint]; + NSURLResponse *presenceResponse = [self failedURLResponseForRequest:presenceRequest withStatusCode:500 headers:nil]; + XCTAssertEqualWithAccuracy([configurationCopy retryDelayForFailedRequest:presenceRequest withResponse:presenceResponse retryAttempt:1], 4.5f, 1.f); +} + #pragma mark - Tests :: Exponential policy @@ -228,6 +287,67 @@ - (void)testItShouldNotProvideCalculatedDelayWhenTargetEndpointExcludedWithExpon XCTAssertEqual([configuration retryDelayForFailedRequest:request withResponse:response retryAttempt:2], -1.f); } +- (void)testSubscribeRequestShouldBeRetriableWithDefaultConfiguration { + PNConfiguration *clientConfiguration = [PNConfiguration configurationWithPublishKey:@"demo" + subscribeKey:@"demo" + userID:@"test-user"]; + PNConfiguration *copiedConfiguration = [clientConfiguration copy]; + PNRequestRetryConfiguration *retryConfig = copiedConfiguration.requestRetry; + + XCTAssertNotNil(retryConfig); + + NSURLRequest *subscribeRequest = [self requestForEndpoint:PNSubscribeEndpoint]; + NSURLResponse *subscribeResponse = [self failedURLResponseForRequest:subscribeRequest withStatusCode:500 headers:nil]; + NSTimeInterval delay = [retryConfig retryDelayForFailedRequest:subscribeRequest withResponse:subscribeResponse retryAttempt:1]; + + XCTAssertGreaterThan(delay, 0, + @"Subscribe should be retried with default configuration for 500 errors"); +} + +- (void)testHeartbeatRequestShouldNotBeRetriable { + PNPresenceHeartbeatRequest *heartbeatRequest = [PNPresenceHeartbeatRequest requestWithHeartbeat:300 + channels:@[@"test-channel"] + channelGroups:nil]; + [heartbeatRequest setupWithClientConfiguration:self.client.configuration]; + PNTransportRequest *transportRequest = heartbeatRequest.request; + + XCTAssertFalse(transportRequest.retriable, + @"Heartbeat request should not be retriable because it has its own timer-based retry loop"); +} + +- (void)testFileUploadRequestShouldNotBeRetriable { + NSURL *uploadURL = [NSURL URLWithString:@"https://s3.amazonaws.com/bucket/test-file"]; + PNFileUploadRequest *uploadRequest = [PNFileUploadRequest requestWithURL:uploadURL + httpMethod:@"POST" + formData:@[]]; + [uploadRequest setupWithClientConfiguration:self.client.configuration]; + PNTransportRequest *transportRequest = uploadRequest.request; + + XCTAssertFalse(transportRequest.retriable, + @"File upload request should not be retriable because body streams cannot be rewound"); +} + +- (void)testItShouldPreserveExcludedEndpointsAfterCopyWithExponentialPolicy { + PNRequestRetryConfiguration *configuration = [PNRequestRetryConfiguration configurationWithExponentialDelay:4.5f + maximumDelay:20.f + maximumRetry:5 + excludedEndpoints:PNAppContextEndpoint, PNMessageSendEndpoint, 0]; + PNRequestRetryConfiguration *configurationCopy = [configuration copy]; + + NSURLRequest *appContextRequest = [self requestForEndpoint:PNAppContextEndpoint]; + NSURLResponse *appContextResponse = [self failedURLResponseForRequest:appContextRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([configurationCopy retryDelayForFailedRequest:appContextRequest withResponse:appContextResponse retryAttempt:1], -1.f); + + NSURLRequest *publishRequest = [self requestForEndpoint:PNMessageSendEndpoint]; + NSURLResponse *publishResponse = [self failedURLResponseForRequest:publishRequest withStatusCode:500 headers:nil]; + XCTAssertEqual([configurationCopy retryDelayForFailedRequest:publishRequest withResponse:publishResponse retryAttempt:1], -1.f); + + NSTimeInterval expectedDelay = [self exponentialDelayWithBaseInterval:4.5f maxInterval:20.f retryAttempt:1]; + NSURLRequest *presenceRequest = [self requestForEndpoint:PNPresenceEndpoint]; + NSURLResponse *presenceResponse = [self failedURLResponseForRequest:presenceRequest withStatusCode:500 headers:nil]; + XCTAssertEqualWithAccuracy([configurationCopy retryDelayForFailedRequest:presenceRequest withResponse:presenceResponse retryAttempt:1], expectedDelay, 1.f); +} + #pragma mark - Helpers From 66340158039b094233324e063eca3e53335f1280 Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Fri, 20 Mar 2026 09:52:19 +0200 Subject: [PATCH 2/5] refactor: remove debug logs --- PubNub/Data/Managers/PNSubscriber.m | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/PubNub/Data/Managers/PNSubscriber.m b/PubNub/Data/Managers/PNSubscriber.m index 25be09af5..2817b3302 100644 --- a/PubNub/Data/Managers/PNSubscriber.m +++ b/PubNub/Data/Managers/PNSubscriber.m @@ -1117,12 +1117,7 @@ - (void)handleLiveFeedEvents:(PNSubscribeStatus *)status forInitialSubscription: shouldContinue = (generation == self->_subscribeCycleGeneration); }]; - if (shouldContinue) { - [self continueSubscriptionCycleIfRequiredWithCompletion:nil]; - } else { - NSLog(@"~~~~~> HOD UP"); - NSLog(@"~~~~~> HOD UP BOI"); - } + if (shouldContinue) [self continueSubscriptionCycleIfRequiredWithCompletion:nil]; // Check whether number of messages exceed specified threshold or not. if (messageCountThreshold > 0 && eventsCount >= messageCountThreshold) { From 81cc014de6ff1da12c1a331278c18aafb6b3ad07 Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:55:58 +0000 Subject: [PATCH 3/5] PubNub SDK 6.1.2 release. --- .pubnub.yml | 31 ++++++++++++++++++---- CHANGELOG.md | 16 +++++++++++ Framework/PubNub/Info.plist | 6 ++--- Framework/PubNub/PubNub-iOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-tvOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-watchOS-Info.plist | 6 ++--- PubNub.podspec | 2 +- PubNub/Misc/PNConstants.h | 2 +- README.md | 2 +- VERSION | 2 +- 10 files changed, 58 insertions(+), 21 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index b08252986..9cd92293b 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,30 @@ --- name: objective-c scm: github.com/pubnub/objective-c -version: "6.1.1" +version: "6.1.2" schema: 1 changelog: + - date: 2026-03-23 + version: 6.1.2 + changes: + - type: bug + text: "Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy." + - type: bug + text: "Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests." + - type: bug + text: "Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started." + - type: improvement + text: "Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send." + - type: improvement + text: "Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism." + - type: improvement + text: "Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery." + - type: improvement + text: "Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift." + - type: improvement + text: "Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable." + - type: improvement + text: "Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception." - date: 2025-12-15 version: 6.1.1 changes: @@ -1411,7 +1432,7 @@ sdks: - distribution-type: source distribution-repository: GitHub release package-name: PubNub.framework - location: https://github.com/pubnub/objective-c/archive/refs/tags/v6.1.1.zip + location: https://github.com/pubnub/objective-c/archive/refs/tags/v6.1.2.zip supported-platforms: supported-operating-systems: macOS: @@ -1472,7 +1493,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.ios.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.ios.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.ios.xcframework.tar.gz supported-platforms: supported-operating-systems: iOS: @@ -1491,7 +1512,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.macos.framework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.macos.framework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.macos.framework.tar.gz supported-platforms: supported-operating-systems: macOS: @@ -1507,7 +1528,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.tvos.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.tvos.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.tvos.xcframework.tar.gz supported-platforms: supported-operating-systems: tvOS: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5734c3ef5..f084b4191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 6.1.2 +March 23 2026 + +#### Fixed +- Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy. +- Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests. +- Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started. + +#### Modified +- Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send. +- Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism. +- Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery. +- Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift. +- Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable. +- Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception. + ## 6.1.1 December 15 2025 diff --git a/Framework/PubNub/Info.plist b/Framework/PubNub/Info.plist index 21f610cfd..a43fd30f7 100644 --- a/Framework/PubNub/Info.plist +++ b/Framework/PubNub/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 6.1.2 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 6.1.2 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 6.1.2 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-iOS-Info.plist b/Framework/PubNub/PubNub-iOS-Info.plist index 21f610cfd..a43fd30f7 100644 --- a/Framework/PubNub/PubNub-iOS-Info.plist +++ b/Framework/PubNub/PubNub-iOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 6.1.2 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 6.1.2 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 6.1.2 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-tvOS-Info.plist b/Framework/PubNub/PubNub-tvOS-Info.plist index 21f610cfd..a43fd30f7 100644 --- a/Framework/PubNub/PubNub-tvOS-Info.plist +++ b/Framework/PubNub/PubNub-tvOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 6.1.2 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 6.1.2 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 6.1.2 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-watchOS-Info.plist b/Framework/PubNub/PubNub-watchOS-Info.plist index 21f610cfd..a43fd30f7 100644 --- a/Framework/PubNub/PubNub-watchOS-Info.plist +++ b/Framework/PubNub/PubNub-watchOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 6.1.2 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 6.1.2 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 6.1.2 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/PubNub.podspec b/PubNub.podspec index a6034bbe5..0462f79d7 100644 --- a/PubNub.podspec +++ b/PubNub.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |spec| spec.name = 'PubNub' - spec.version = '6.1.1' + spec.version = '6.1.2' spec.summary = 'The PubNub Real-Time Network. Build real-time apps quickly and scale them globally.' spec.homepage = 'https://github.com/pubnub/objective-c' diff --git a/PubNub/Misc/PNConstants.h b/PubNub/Misc/PNConstants.h index 5dc2e7c6f..c36e06b18 100644 --- a/PubNub/Misc/PNConstants.h +++ b/PubNub/Misc/PNConstants.h @@ -15,7 +15,7 @@ #pragma mark General information constants // Stores client library version number -static NSString * const kPNLibraryVersion = @"6.1.1"; +static NSString * const kPNLibraryVersion = @"6.1.2"; // Stores information about SDK codebase static NSString * const kPNCommit = @"fd5c7ed678527fce07eaf7eb162935caf1bfd303"; diff --git a/README.md b/README.md index e97385cae..83156de0e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PubNub 6.1.1 for iOS 9+ +# PubNub 6.1.2 for iOS 9+ [![Twitter](https://img.shields.io/badge/twitter-%40PubNub-blue.svg?style=flat)](https://twitter.com/PubNub) [![Twitter Releases](https://img.shields.io/badge/twitter-%40PubNubRelease-blue.svg?style=flat)](https://twitter.com/PubNubRelease) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/PubNub.svg?style=flat)](https://img.shields.io/cocoapods/v/PubNub.svg) diff --git a/VERSION b/VERSION index f3b5af39e..5e3254243 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.1 +6.1.2 From 70d39bb8d0accc7b5585dcedfdce027188adb25e Mon Sep 17 00:00:00 2001 From: Serhii Mamontov Date: Mon, 23 Mar 2026 11:00:03 +0200 Subject: [PATCH 4/5] doc(changelog): Revert PubNub SDK 6.1.2 release. --- .pubnub.yml | 31 ++++------------------ CHANGELOG.md | 16 ----------- Framework/PubNub/Info.plist | 6 ++--- Framework/PubNub/PubNub-iOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-tvOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-watchOS-Info.plist | 6 ++--- PubNub.podspec | 2 +- PubNub/Misc/PNConstants.h | 2 +- README.md | 2 +- VERSION | 2 +- 10 files changed, 21 insertions(+), 58 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 9cd92293b..b08252986 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,30 +1,9 @@ --- name: objective-c scm: github.com/pubnub/objective-c -version: "6.1.2" +version: "6.1.1" schema: 1 changelog: - - date: 2026-03-23 - version: 6.1.2 - changes: - - type: bug - text: "Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy." - - type: bug - text: "Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests." - - type: bug - text: "Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started." - - type: improvement - text: "Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send." - - type: improvement - text: "Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism." - - type: improvement - text: "Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery." - - type: improvement - text: "Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift." - - type: improvement - text: "Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable." - - type: improvement - text: "Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception." - date: 2025-12-15 version: 6.1.1 changes: @@ -1432,7 +1411,7 @@ sdks: - distribution-type: source distribution-repository: GitHub release package-name: PubNub.framework - location: https://github.com/pubnub/objective-c/archive/refs/tags/v6.1.2.zip + location: https://github.com/pubnub/objective-c/archive/refs/tags/v6.1.1.zip supported-platforms: supported-operating-systems: macOS: @@ -1493,7 +1472,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.ios.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.ios.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.ios.xcframework.tar.gz supported-platforms: supported-operating-systems: iOS: @@ -1512,7 +1491,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.macos.framework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.macos.framework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.macos.framework.tar.gz supported-platforms: supported-operating-systems: macOS: @@ -1528,7 +1507,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.tvos.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.2/PubNub.tvos.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.tvos.xcframework.tar.gz supported-platforms: supported-operating-systems: tvOS: diff --git a/CHANGELOG.md b/CHANGELOG.md index f084b4191..5734c3ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,3 @@ -## 6.1.2 -March 23 2026 - -#### Fixed -- Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy. -- Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests. -- Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started. - -#### Modified -- Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send. -- Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism. -- Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery. -- Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift. -- Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable. -- Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception. - ## 6.1.1 December 15 2025 diff --git a/Framework/PubNub/Info.plist b/Framework/PubNub/Info.plist index a43fd30f7..21f610cfd 100644 --- a/Framework/PubNub/Info.plist +++ b/Framework/PubNub/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.2 + 6.1.1 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.2 + 6.1.1 CFBundleSignature ???? CFBundleVersion - 6.1.2 + 6.1.1 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-iOS-Info.plist b/Framework/PubNub/PubNub-iOS-Info.plist index a43fd30f7..21f610cfd 100644 --- a/Framework/PubNub/PubNub-iOS-Info.plist +++ b/Framework/PubNub/PubNub-iOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.2 + 6.1.1 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.2 + 6.1.1 CFBundleSignature ???? CFBundleVersion - 6.1.2 + 6.1.1 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-tvOS-Info.plist b/Framework/PubNub/PubNub-tvOS-Info.plist index a43fd30f7..21f610cfd 100644 --- a/Framework/PubNub/PubNub-tvOS-Info.plist +++ b/Framework/PubNub/PubNub-tvOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.2 + 6.1.1 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.2 + 6.1.1 CFBundleSignature ???? CFBundleVersion - 6.1.2 + 6.1.1 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-watchOS-Info.plist b/Framework/PubNub/PubNub-watchOS-Info.plist index a43fd30f7..21f610cfd 100644 --- a/Framework/PubNub/PubNub-watchOS-Info.plist +++ b/Framework/PubNub/PubNub-watchOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.2 + 6.1.1 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.2 + 6.1.1 CFBundleSignature ???? CFBundleVersion - 6.1.2 + 6.1.1 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/PubNub.podspec b/PubNub.podspec index 0462f79d7..a6034bbe5 100644 --- a/PubNub.podspec +++ b/PubNub.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |spec| spec.name = 'PubNub' - spec.version = '6.1.2' + spec.version = '6.1.1' spec.summary = 'The PubNub Real-Time Network. Build real-time apps quickly and scale them globally.' spec.homepage = 'https://github.com/pubnub/objective-c' diff --git a/PubNub/Misc/PNConstants.h b/PubNub/Misc/PNConstants.h index c36e06b18..5dc2e7c6f 100644 --- a/PubNub/Misc/PNConstants.h +++ b/PubNub/Misc/PNConstants.h @@ -15,7 +15,7 @@ #pragma mark General information constants // Stores client library version number -static NSString * const kPNLibraryVersion = @"6.1.2"; +static NSString * const kPNLibraryVersion = @"6.1.1"; // Stores information about SDK codebase static NSString * const kPNCommit = @"fd5c7ed678527fce07eaf7eb162935caf1bfd303"; diff --git a/README.md b/README.md index 83156de0e..e97385cae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PubNub 6.1.2 for iOS 9+ +# PubNub 6.1.1 for iOS 9+ [![Twitter](https://img.shields.io/badge/twitter-%40PubNub-blue.svg?style=flat)](https://twitter.com/PubNub) [![Twitter Releases](https://img.shields.io/badge/twitter-%40PubNubRelease-blue.svg?style=flat)](https://twitter.com/PubNubRelease) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/PubNub.svg?style=flat)](https://img.shields.io/cocoapods/v/PubNub.svg) diff --git a/VERSION b/VERSION index 5e3254243..f3b5af39e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.2 +6.1.1 From 3d2411fe478fd7536e52f0d628e8afe7fed6e3d6 Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:15:41 +0000 Subject: [PATCH 5/5] PubNub SDK 7.0.0 release. --- .pubnub.yml | 31 ++++++++++++++++++---- CHANGELOG.md | 16 +++++++++++ Framework/PubNub/Info.plist | 6 ++--- Framework/PubNub/PubNub-iOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-tvOS-Info.plist | 6 ++--- Framework/PubNub/PubNub-watchOS-Info.plist | 6 ++--- PubNub.podspec | 2 +- PubNub/Misc/PNConstants.h | 2 +- README.md | 2 +- VERSION | 2 +- 10 files changed, 58 insertions(+), 21 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index b08252986..a4021b82e 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,30 @@ --- name: objective-c scm: github.com/pubnub/objective-c -version: "6.1.1" +version: "7.0.0" schema: 1 changelog: + - date: 2026-03-23 + version: 7.0.0 + changes: + - type: bug + text: "Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy." + - type: bug + text: "Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests." + - type: bug + text: "Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started." + - type: improvement + text: "Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send." + - type: improvement + text: "Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism." + - type: improvement + text: "BREAKING CHANGES: Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery." + - type: improvement + text: "Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift." + - type: improvement + text: "Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable." + - type: improvement + text: "Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception." - date: 2025-12-15 version: 6.1.1 changes: @@ -1411,7 +1432,7 @@ sdks: - distribution-type: source distribution-repository: GitHub release package-name: PubNub.framework - location: https://github.com/pubnub/objective-c/archive/refs/tags/v6.1.1.zip + location: https://github.com/pubnub/objective-c/archive/refs/tags/v7.0.0.zip supported-platforms: supported-operating-systems: macOS: @@ -1472,7 +1493,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.ios.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.ios.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v7.0.0/PubNub.ios.xcframework.tar.gz supported-platforms: supported-operating-systems: iOS: @@ -1491,7 +1512,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.macos.framework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.macos.framework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v7.0.0/PubNub.macos.framework.tar.gz supported-platforms: supported-operating-systems: macOS: @@ -1507,7 +1528,7 @@ sdks: - distribution-type: library distribution-repository: GitHub release package-name: PubNub.tvos.xcframework.tar.gz - location: https://github.com/pubnub/objective-c/releases/download/v6.1.1/PubNub.tvos.xcframework.tar.gz + location: https://github.com/pubnub/objective-c/releases/download/v7.0.0/PubNub.tvos.xcframework.tar.gz supported-platforms: supported-operating-systems: tvOS: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5734c3ef5..2e61748ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## 7.0.0 +March 23 2026 + +#### Fixed +- Fix `PNRequestRetryConfiguration` `copyWithZone` not copying `excludedEndpoints`, causing all endpoint groups to become eligible for retry after `PNConfiguration` copy. +- Remove hardcoded `retriable = NO` from `PNSubscribeRequest` so transport-level retry configuration is applied to subscribe long-poll requests. +- Add `subscribeCycleGeneration` counter to `PNSubscriber` to detect and discard stale continuation calls dispatched asynchronously before a new subscribe cycle started. + +#### Modified +- Add `retriable = NO` to `PNFileUploadRequest` because body streams cannot be rewound after a partial send. +- Add `request` method override with `retriable = NO` to `PNPresenceHeartbeatRequest` because presence heartbeat has its own timer-based periodic mechanism. +- BREAKING CHANGES: Delete `PNReachability` class and remove references from `PubNub.podspec` and Framework project in favor of transport-level retry for network recovery. +- Update export script to generate a directory umbrella modulemap instead of flat header symlinks. Configure publicHeadersPath and cSettings in Package.swift. +- Add unit tests for `copyWithZone` preserving excluded endpoints, default configuration retrying only subscribe, and heartbeat / file upload requests not being retriable. +- Add generation counter and retry tests Add unit tests verifying generation counter prevents stale subscribe continuations and transport-level retry works for subscribe requests using `NSURLProtocol` interception. + ## 6.1.1 December 15 2025 diff --git a/Framework/PubNub/Info.plist b/Framework/PubNub/Info.plist index 21f610cfd..8fcfa1d8a 100644 --- a/Framework/PubNub/Info.plist +++ b/Framework/PubNub/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 7.0.0 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 7.0.0 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 7.0.0 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-iOS-Info.plist b/Framework/PubNub/PubNub-iOS-Info.plist index 21f610cfd..8fcfa1d8a 100644 --- a/Framework/PubNub/PubNub-iOS-Info.plist +++ b/Framework/PubNub/PubNub-iOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 7.0.0 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 7.0.0 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 7.0.0 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-tvOS-Info.plist b/Framework/PubNub/PubNub-tvOS-Info.plist index 21f610cfd..8fcfa1d8a 100644 --- a/Framework/PubNub/PubNub-tvOS-Info.plist +++ b/Framework/PubNub/PubNub-tvOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 7.0.0 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 7.0.0 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 7.0.0 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/Framework/PubNub/PubNub-watchOS-Info.plist b/Framework/PubNub/PubNub-watchOS-Info.plist index 21f610cfd..8fcfa1d8a 100644 --- a/Framework/PubNub/PubNub-watchOS-Info.plist +++ b/Framework/PubNub/PubNub-watchOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable PubNub CFBundleGetInfoString - 6.1.1 + 7.0.0 CFBundleIdentifier com.pubnub.pubnub-objc CFBundleInfoDictionaryVersion @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.1 + 7.0.0 CFBundleSignature ???? CFBundleVersion - 6.1.1 + 7.0.0 NSHumanReadableCopyright © 2010 - 2020 PubNub, Inc. NSPrincipalClass diff --git a/PubNub.podspec b/PubNub.podspec index a6034bbe5..10f78e77a 100644 --- a/PubNub.podspec +++ b/PubNub.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |spec| spec.name = 'PubNub' - spec.version = '6.1.1' + spec.version = '7.0.0' spec.summary = 'The PubNub Real-Time Network. Build real-time apps quickly and scale them globally.' spec.homepage = 'https://github.com/pubnub/objective-c' diff --git a/PubNub/Misc/PNConstants.h b/PubNub/Misc/PNConstants.h index 5dc2e7c6f..fddfe8ae5 100644 --- a/PubNub/Misc/PNConstants.h +++ b/PubNub/Misc/PNConstants.h @@ -15,7 +15,7 @@ #pragma mark General information constants // Stores client library version number -static NSString * const kPNLibraryVersion = @"6.1.1"; +static NSString * const kPNLibraryVersion = @"7.0.0"; // Stores information about SDK codebase static NSString * const kPNCommit = @"fd5c7ed678527fce07eaf7eb162935caf1bfd303"; diff --git a/README.md b/README.md index e97385cae..fbb417077 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PubNub 6.1.1 for iOS 9+ +# PubNub 7.0.0 for iOS 9+ [![Twitter](https://img.shields.io/badge/twitter-%40PubNub-blue.svg?style=flat)](https://twitter.com/PubNub) [![Twitter Releases](https://img.shields.io/badge/twitter-%40PubNubRelease-blue.svg?style=flat)](https://twitter.com/PubNubRelease) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/PubNub.svg?style=flat)](https://img.shields.io/cocoapods/v/PubNub.svg) diff --git a/VERSION b/VERSION index f3b5af39e..66ce77b7e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.1 +7.0.0