Thanks Quinn for your quick response.
Actually I do define NSLocalNetworkUsageDescription in Info.plist as our app also uses the GoogleCast SDK. The LNA system popup is properly displayed with our message the first time the app (or GCK) accesses the local network.
So if the system does not require the multicast entitlement yet, there would be no reason for the app built with xcode 12 to fail to use BSD sockets, whereas the very same code built with xcode 11.6 works fine (which I've just confirmed with my MBP still using xcode 11.6).
If the change of behaviour you mentioned is confirmed, do you think this is a bug? If so, shall I file a bug to Apple? And do you think there's a chance for it to be fixed in the future?
Finally, as my problem could have a totally different cause, do you have any hints to share to help me debug the CFSocketSendData call returning an error?
Thanks again.
Post
Replies
Boosts
Views
Activity
Hi Quinn and thanks again for your time.
I opened a DTS incident, here is the follow-up : 747881523.
Meanwhile, I checked the errno and it's equal to 65 (<No Route to host> I presume) right after the call to CFSocketSendData fails. It seems the route to the UPnP host:port (239.255.255.250:1900) cannot be established.
Finally, I also observed that I don't reproduce the issue when building the app with xcode 12 and running it on an iPhone 6+ with iOS 12.4.8. So far, the issue only occurs with the most common configuration: xcode 12 and a device on iOS 14.
Hi Doive,
We've indeed investigated furthermore with Quinn through the DTS incident ; he has reproduced the issue and filed a radar for the engineering team. It seems it's a difference of libraries used by Xcode 12 and previous versions of Xcode that brings this regression.
What we've observed using Xcode 12 is that the first message you send to a newly created CFSocket always fails, even if the user has already authorised the app to access the local network. The very same code works fine when built with Xcode 11.
As it was the case for my app, I made two workarounds ("ceinture et bretelles" as we say in France): I reuse the same CFSocket as much as possible. Your app will have to be aware of network state changes and of switches between background and foreground in order to destroy / create the CFSocket, otherwise it will fail to send messages.
If a send fails, I always try it again once
Quinn also confirmed me that, for the time being, CFSockets can still send multicast requests even if you have not (requested, obtained and) included the Multicast entitlement to your app. But, as it's considered a bug, we have to be prepared for a later iOS 14 update where this entitlement becomes required even when using CFSockets.
Hope it helps,
Aurélien.
Quinn, I gave it a try with the test app I made back then and, unfortunately, the first message send still fails for every newly created socket. App build with Xcode 12.2 and run on iOS 14.2.
Here is the test app by the way: https://github.com/acostarz/cfsocket_first_message
Regards,
Aurélien.
So, as I cannot wait for third party XCFrameworks to be delivered, I went through the path of repacking the legacy frameworks into XCFrameworks and it works fine, as long as the XCFrameworks are there when compiling (which is not necessary with common frameworks). But this is another story.
For those interested in how to create these XCFrameworks on the fly, here is how I managed to do so: Create an aggregate target in your project
Add a build phase script to this new target to build the XCFrameworks
Edit the scheme of the primary target (the one for your app) and add the aggregate target to the top of the targets list (build tab)
Uncheck "Parallelize Build" to preserve the targets order
First I create the URLRequest object with a cachePolicy (usually .useProtocolCachePolicy).
var urlRequest = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)
Then I retrieve the cachedResponse from URLCache as follows:
let cachedResponse = URLCache.shared.cachedResponse(for: urlRequest)
Here, cachedResponse(for:) always returns a new object (different pointer) if called several times with the same URLRequest. And it's the same for the HTTPURLResponse that I get as follows:
let cachedHTTPURLResponse = cachedResponse?.response
Finally I create and resume the URLSessionDataTask, assuming the response will cached in URLCache (if the cache policy allows it).
let dataTask = urlSession.dataTask(with: urlRequest) { ... }
dataTask.resume()
Thanks Matt for your support, this is exactly what I was looking for.
Side question: sometimes there are two transactions (the first one hitting the cache, the second one the network). I assume the first transaction finds out that the cached response is stale, hence the second transaction. If I'm correct, is there any property describing the cache transaction result?
Thanks again,
Aurélien.
The underlying error of the transaction error allows you to identify this specific use-case.
if let error = transaction.error as? NSError,
let underlyingError = error.userInfo["NSUnderlyingError"] as? NSError,
underlyingError.code == 3038 {
// General conditions have changed, don't display an error for the interrupted transaction
}
I cannot find any documentation regarding the underlying error domain (ASDServerErrorDomain) but I've not found any other way to discriminate this error.
Hope it helps,
Aurélien.
I experienced the very same issue and figured out that you have to unselect Interrupt Purchases for This Tester on AppStoreConnect right before agreeing the fake general conditions update modal. If you do so, the new transaction is created as described in Apple documentation.
I consider it a bug but if it's not, Apple should mention it as an intermediate step between steps 8. and 9. of the Interrupted Purchase test scenario.
Hope it helps,
Aurélien.
This popup is most certainly displayed because you are using a sandbox account for which Interrupt Purchases for This Tester is selected on AppStoreConnect. To get rid of this popup, unselect this option.
Hope it helps,
Aurélien.
I'm having the same issue with consumable products in Sandbox environment (https://developer.apple.com/forums/thread/678105).
The first purchase for a given product goes well but any subsequent purchase for this product will end up by the system restoring the first transaction instead of creating a new one. In the end, the purchase fails as our backend refuses to validate the receipt (the transaction identifier being already validated once).
I made sure all transactions in purchased or restored states are properly finished (the removedTransactions callback being called each time), although the call to finishTransaction may be asynchronous (after our backend (in)validates the receipt). However, each time the app starts or goes to foreground again, the first successful finished transaction keeps on appearing in the SKPaymentQueue as if it had not been finished. Finishing this transaction right away does not change anything.
One sentence worries me in the SKPaymentQueue finishTransaction documentation page :
In rare circumstances, this call might fail, and you'll receive updates for that transaction again.
It looks like we are experiencing this situation but we have no way to check if the call to finishTransaction failed nor why did it fail. Moreover, there's apparently no way to work around this issue and allow subsequent purchases (for the same product).
Also I'm pretty sure I was not experiencing this issue last week (using the same app distributed via Testflight).
I don't know if it's an issue with the Sandbox environment or with the Storekit libs and I cannot test the production environment yet. But I'm not confident at all releasing our first support of in-app purchases.
Aurélien.
Hi eskimo,
Yes, 2.0 is the expected result.
Here is my detailed configuration:
• MacBook Pro 2020, Intel processor
• MacOS 11.4 (but reproduced yesterday with 11.3.x)
• Xcode 12.5
• Simulators: iPhone 8 (14.5), iPhone 8 (12.4), iPhone 8 Plus (14.5), iPhone 12 Pro Max
I always get the following result:
(lldb) po (398.0 / 165.0).rounded()e-321
You don't even have to set a breakpoint to get an unexpected result (though different) as the instruction
print("Rounded result: \((398.0 / 165.0).rounded())")
logs
Rounded result: 2.412121212121212
FYI I've filled in a bug report (FB9118612).
I ended up cleaning the entire Xcode "iOS DeviceSupport" folder as it proved to be useful in the past and I cannot reproduce the issue anymore! However, the other developer in my team has the same issue so it's not specific to my computer.
I guess we will never know what was going on.
Have you found any solution, workaround or fix for this issue?
I'm having the same issue and I've not found any way to make it work, making the whole solution useless!
Please Apple engineers, could you please let us know how to set the initial selected index when dealing with a TVCollectionViewFullScreenLayout?
Thanks.
Thanks azhukov for your response.
To give a bit of context, the content displayed by our app is mostly defined by the data the app receives from our backend (including the content hierarchy). It does not know beforehand which kinds of cells it will be asked to display. Most of the screens are base on the very same UIViewController (embedding a collectionView) which is highly configurable and controlled by the server data. For now we use 8 different cells types but it regularly increases as we include more various types of content, hence ways to present it.
Our cells registrations lazy instantiation are global and are done once and for all; these instantiations are not local to a UIViewController (in the viewDidLoad() as you assumed).
Moreover, the collectionView data describes the cells to use for each section and it makes no sense to parse the data before building the data source in order to force the lazy instantiation of cells registration that will be used.
Finally, the cell registrations being only necessary in the UICollectionViewDiffableDataSource cell provider block, it seems absurd to instantiate all cell registrations beforehand whereas only some of them may be used during the app lifetime.
// sections cells registration
dataSource = UICollectionViewDiffableDataSource<Section, SectionItem>(collectionView: collectionView) { collectionView, indexPath, item in
let cellRegistration = item.section.widgetConfig.tileConfig.type.cellRegistration // lazily instantiated
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
Instantiating objects once and for all and only when required to is definitely a good practice, just as are Swift class properties. And forbidding cell registrations instantiation inside a diffableDataSource cell provider block seems to me a false good idea introduced in iOS/tvOS 15.