The example code of task modifier of SwiftUI is confusing - swiftui

Here is the code in Apple developer document。
let url = URL(string: "https://example.com")!
#State private var message = "Loading..."
var body: some View {
Text(message)
.task {
do {
var receivedLines = [String]()
for try await line in url.lines {
receivedLines.append(line)
message = "Received \(receivedLines.count) lines"
}
} catch {
message = "Failed to load"
}
}
}
Why don't it update message in the UI thread as code below
DispatchQueue.main.async {
message = "Received \(receivedLines.count) lines"
}
Does the code in task block alway run in the UI thread?
Here is my test code. It sometimes seems that task isn't inherit the actor context of its caller.
func wait() async {
await Task.sleep(1000000000)
}
Thread.current.name = "Main thread"
print("Thread in top-level is \(Thread.current.name)")
Task {
print("Thread in task before wait is \(Thread.current.name)")
if Thread.current.name!.isEmpty {
Thread.current.name = "Task thread"
print("Change thread name \(Thread.current.name)")
}
await wait()
print("Thread in task after wait is \(Thread.current.name)")
}
Thread.sleep(until: .now + 2)
// print as follow
// Thread in top-level is Optional("Main thread")
// Thread in task before wait is Optional("")
// Change thread name Optional("Task thread")
// Thread in task after wait is Optional("")
Thread in task is different before and after wait()
Thread in task is not Main thread

Great question! It looks like a bug, but in fact Apple's sample code is safe. But it is a safe for a sneaky reason.
Open a Terminal window and run this:
cd /Applications/Xcode.app
find . -path */iPhoneOS.platform/*/SwiftUI.swiftmodule/arm64.swiftinterface
The find command may take a while to finish, but it will eventually print a path like this:
./Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64.swiftinterface
Take a look at that swiftinterface file with less and search for func task. You'll find the true definition of the task modifier. I'll reproduce it here and line-wrap it to make it easier to read:
#inlinable
public func task(
priority: _Concurrency.TaskPriority = .userInitiated,
#_inheritActorContext
_ action: #escaping #Sendable () async -> Swift.Void
) -> some SwiftUI.View {
modifier(_TaskModifier(priority: priority, action: action))
}
Notice that the action argument has the #_inheritActorContext attribute. That is a private attribute, but the Underscored Attributes Reference in the Swift repository explains what it does:
Marks that a #Sendable async closure argument should inherit the actor context (i.e. what actor it should be run on) based on the declaration site of the closure. This is different from the typical behavior, where the closure may be runnable anywhere unless its type specifically declares that it will run on a specific actor.
So the task modifier's action closure inherits the actor context surrounding the use of the task modifier. The sample code uses the task modifier inside the body property of a View. You can also find the true declaration of the body property in that swiftinterface file:
#SwiftUI.ViewBuilder #_Concurrency.MainActor(unsafe) var body: Self.Body { get }
The body method has the MainActor attribute, which means it belongs to the MainActor context. MainActor runs on the main thread/queue. So using task inside body means the task closure also runs on the main thread/queue.

Related

How to await for the stream event to get processed in the unit-test?

The unit-test implies that after some item was passed to a Sink object through the add method, than some operation will be performed in response to it.
class Subject {
final StreamController<Item> _itemStreamController = StreamController();
Sink<Item> get itemsSink => _itemStreamController.sink;
Stream<Item> get _itemsStream => _itemStreamController.sink;
final SomeService _service;
Subject(this._service) {
_itemsStream.listen((item) => _service.processItem(item));
}
}
And the unit test itself:
test('description', () async {
final Item givenItem = Item("givenValue");
final SomeService givenService = MockSomeService();
final Subject subject = Subject(givenService);
subject.itemsSink.add(givenItem);
verify(givenService.add(givenItem));
});
And as you can understand the event looping thread firstly processes verify instructions and only then dispatches the event to the listener.
If I just insert await Future.value(null); between sink interaction and method invocation verification than all works like a charm. Still, this solution is quite ugly.
Does the unit-testing framework itself offer something to schedule verification at the end of event loop? Or any measures to get this unit-test passed without resorting to something like await Future.value(null);?

App doesn't handle redemption of in-app purchase promo code of consumable products

My app has in-app purchase built in (I followed this tutorial), the purchase functionality works. However, when I redeem the promo code in App Store for one of the in-app purchase products, my app doesn't response to it. Even the App Store says the product has been successfully redeemed, my app doesn't response to it.
Has anyone who has in-app purchase tested if your app can process the promo code? Would you mind share the solution?
I started with this:
override func viewDidLoad() {
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidBecomeActive(notification:)),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil
)
}
func applicationDidBecomeActive(notification: NSNotification){
let store = IAPHealper()
//what needs to be put here?
}
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
completeTransaction(transaction)
break
case .failed:
failedTransaction(transaction)
break
case .restored:
restoreTransaction(transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
fileprivate func completeTransaction(_ transaction: SKPaymentTransaction) {
print("completeTransaction...")
deliverPurchaseNotificatioForIdentifier(transaction.payment.productIdentifier)
defaultQueue.finishTransaction(transaction)
purchaseCompletionHandler?(true, transaction)
}
fileprivate func restoreTransaction(_ transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restoreTransaction... \(productIdentifier)")
deliverPurchaseNotificatioForIdentifier(productIdentifier)
defaultQueue.finishTransaction(transaction)
}
fileprivate func failedTransaction(_ transaction: SKPaymentTransaction) {
print("failedTransaction...")
if transaction.error!._code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(String(describing: transaction.error?.localizedDescription))")
purchaseCompletionHandler?(false, transaction)
}
defaultQueue.finishTransaction(transaction)
}
fileprivate func deliverPurchaseNotificatioForIdentifier(_ identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
//NSNotificationCenter.defaultCenter().postNotificationName(IAPHelper.IAPHelperPurchaseNotification, object: identifier)
}
public func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]){
print("Removed from queue.")
print(transactions)
}
}
Thanks.
Other than what Andrew says (about promo codes only working for production environments) it seems you might have an issue regarding your "App Purchases" Handling mechanisms.
You should have your purchases handling object initialized in the AppDelegate, so that it immediately receives the pending purchases and just created purchases, (or in this case when a code is redeemed)
If you check the examples shown here:
https://developer.apple.com/library/content/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795-CH1-BEST_PRACTICES-ADD_A_TRANSACTION_QUEUE_OBSERVER_AT_APPLICATION_LAUNCH
You are actually doing what is NOT recommended by apple.
Instead add the StoreKit observer INSIDE your AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
....
// Attach an observer to the payment queue.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Attach an observer to the payment queue.
SKPaymentQueue.default().add(your_observer)
return true
}
// Called when the application is about to terminate.
func applicationWillTerminate(_ application: UIApplication) {
// Remove the observer.
SKPaymentQueue.default().remove(your_observer)
}
// OTHER STUFF...
}
You might actually be missing on the timing your app receives the purchases because of this.
By the way, you already have an "In App Purchases" helper object (IAPHealper). All you need to do is, make your AppDelegate store a variable to it, and instantiate it inside the "didFinishLaunchingWithOptions" method.
Something like:
class AppDelegate: UIResponder, UIApplicationDelegate {
var store : IAPHealper;
....
// Attach an observer to the payment queue.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
store = IAPHelper();
return true
}
// OTHER STUFF...
}
EDIT: (To keep all the info here on stack overflow)
According to an Apple's Employee response:
https://forums.developer.apple.com/message/204476#204476
Promo codes can only be tested in the production store.
So, in order to test them:
Submit your app for App Review.
Set the release date of the App sometime in the future so that once App Review approves the app, it's not available to the general user.
Use an app promo code to install the app
Use the in app promo code.
And finally, the developer's post also warns us that, after an App has been approved you have to wait 48 hours before the codes start working.
So if after following the steps described above, your app is not behaving as expected. Then the problem you are facing is your app not being "ready" when apple sends you the "purchase successful" notification. Hence why you should follow the guideline described on the first part of this answer. (About initializing your transaction listener as soon as your app is launched)
Promo codes work in production environment only.
If you want to ensure IAPs work without actual application release then use promo codes when application is in "pending developer release" state (after review). One app promo code to install app and another to test IAP. Sometimes it can take additional time (up to 2 days) to distribute data for all Apple servers.
from official devforum answer
#Eatton provided very helpful information on the other thread. I just want to summarize the solution to handling redemption of Promo Code for consumable products.
I. You should use SwiftyStoreKit, and put this code in AppDelegate:
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
if purchase.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
// Unlock content
self.unlockIAPContent(productID: purchase.productId)
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
II. If you want to call the logic in the any ViewController, please consider to use NotificationCenter, put the code under //Unlock content
III. How to test it?
In iOS 11 release, Apple introduced a new feature for promoting your in-app purchase directly in App Store.
First add the handler:
#if DEBUG
SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
return true
}
#endif
Then compose the following URL on your Mac and AirDrop it over to your iOS device and open it in Safari.
itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name
Then the completion block of SwiftyStoreKit.completeTransactions() in your AppDelegate will be triggered.
This can also be used for testing Promo Code redemption since the URL request creates a pending transaction and adds it to the queue. Make sure you remove this code for your prod release.
Hope this helps!

UWP/WinRT: How to perform a UI task upon completion of an asynchronous operation in a model?

I'm following the MVVM pattern, and have a model called a DocumentStore. The class has a method as follows:
void DocumentStore::Open_Document(StorageFile^ file) {
create_task(FileIO::ReadTextAsync(file))
.then([this, file](String^ fileContents)
{
// Take the fileContents and add them to internal data structure
});
}
My ViewModel is popping up a FileOpenPicker to get a file that it then feed as the argument into Open_Document:
create_task(picker->PickSingleFileAsync())
.then([this](StorageFile^ file)
{
m_DocStore->Open_Document(file);
// Target location to do something
}
);
I'd like to be able to perform an action after the task inside of Open_Document has completed, i.e. after the fileContents have been processed.
Is there a way for my Model to notify any interested listeners that a task is complete?
Or should my Model's Open_Document method actually be itself asynchronous? However, I need to process the data structure inside the task, and wouldn't that cause my method to be running inside a different thread context?
I'm working in C++/CX but will take any help I can get.
If I understand correctly, the process will be as following.
Open the file -> Read the content -> process the content -> do STH else.
You can push the async operation to the task chain and create a new async operation by using create_async method.
Here is the code for your reference:
create_task(StorageFile::GetFileFromApplicationUriAsync(ref new Windows::Foundation::Uri("ms-appx:///Assets/XMLFile.xml")))
.then([](StorageFile^ file) {
WriteLine("Read the file");
return FileIO::ReadTextAsync(file);
}).then([](task<String^> task) {
String ^ text = task.get();
WriteLine("Content: " + text);
return create_async([text]() {
WriteLine("Process the text: " + text);
});
}).then([](task<void> task) {
task.get();
WriteLine("Do STH else");
});
I'm posting what I ended up going with, but I accepted Jeffrey Chen's answer since it helped me get there.
My Model now has an Event DocOpened. This is fired upon completion of Open_Document. I subscribed my ViewModel to this Event with a handler that is capable of performing tasks whenever that Event is fired.

Akka wait for computation of actor

I would like to create a minimal example of an actor that sends off a message to an actor and then waits for the response of that actor. The reason for this example is that I want to use it in my thesis in context of disussing the usage other language features (e.g., futures) instead of pure actors. So the point here is that it has to be an actor that waits for a message before processing anything else.
The idea I had was to demonstrate an actor that requests a file to be read from disk, then does some long computation and then waits for the read to finish.
What I have so far is the following:
import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props
import scala.io.Source
case class FileContents(content: String)
class WorkerActor extends Actor
{
def receive =
{
case "compute" =>
println("Computing!")
// Create actor to read the file
val reader = context.actorOf(Props[ReadFileActor])
reader ! ReadFile("/home/christophe/code/thesis-example/src/main/resources/file.txt")
// Heavy computation
Thread.sleep(5000)
case FileContents(content) =>
println("Got file content:\n" + content)
// Continue computation.
}
}
case class ReadFile(path: String)
class ReadFileActor extends Actor
{
def receive =
{
case ReadFile(path) =>
var contents: String = ""
for (line <- Source.fromFile(path).getLines())
{
contents += line
}
sender ! FileContents(contents)
}
}
object Main extends App
{
val system = ActorSystem("HelloSystem")
val worker = system.actorOf(Props[WorkerActor], name = "worker")
worker ! "compute"
worker ! "compute"
}
But what happens here is that WorkerActor receives the compute message, and then starts a child actor to read in the file. After the heave computation it receives the second compute message instead. And finally receives the two messages from the ReadFile actor.
What I actually want to happen is that WorkerActor receives the compute message, does the heavy computation and then waits until he receives the FileContents message. Only after that it can receive any other message (i.e., the second compute message).
I have read the docs and searched around for examples but i cant seem to find anything on it.
Disclaimer: I am not using Akka except for this small example in context of my thesis.
Just create several (pool of) workers for several compute messages instead of one worker, smthng like:
object Main extends App {
val system = ActorSystem("HelloSystem")
val router = system.actorOf(RoundRobinPool(2).props(Props[Worker]), "router")
router ! "compute"
router ! "compute"
}
If you want second worker to be launched after first:
def receive = {
case "compute" => ...
case FileContents(content) =>
println("Got file content:\n" + content)
// Continue computation.
context.parent ! "compute" //send to the pool
}
...
//Main:
router ! "compute"
Another option is to remember sender of "compute" (it will be your Main) and send the response back to the top-level:
var main = _
def receive = {
case "compute" =>
...
main = sender
case FileContents(content) =>
...
main ! "ack"
}
//Main:
(router ? "compute") foreach (_ => router ! compute)
If you don't like future here - you may rewrite it with actor:
//Main
class MainActor extends Actor {
def receive = {
case "start" => router ! "compute"
case "ack" => router ! "compute"
}
}
P.S. Blocking computations inside actor should be managed properly or they may lead to thread starvation.
Sounds like what you want is to keep processing this message until you get a response. in other words block
class WorkerActor extends Actor {
def receive = {
case "compute" =>
println("Computing!")
// Create actor to read the file
val reader = context.actorOf(Props[ReadFileActor])
val future: Future[FileContents] =
(reader ? ReadFile("file.txt").mapTo[FileContents]
// Heavy computation
Thread.sleep(5000)
// this will block current thread and thus the actor,
// so it will not process any other messages until
// future is completed or time is out
Await.result(future, timeout)
}
}
BUT, this is considered as a very bad thing in actor lands.

HttpTaskAsyncHandler and HTTP context

I am just starting to learn how Task works, and get one interesting case.
I have HttpTaskAsyncHandler but I can't get acccess to HttpContext if my code inside ProcessRequestAsync calls to some Task
public class MyAsyncHandler : HttpTaskAsyncHandler, IReadOnlySessionState
{
public override async Task ProcessRequestAsync(HttpContext context)
{
//can use HttpContext here
await MyJob("data");
//can use HttpContext here
}
public async Task MyJob(string data)
{
var func = Task.Factory.StartNew(() => Process(data));
await func;
}
public string Process(string context)
{
**//can't use HttpContext here**
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
is it possible to fix ? I understand that Process method would be call in other tread but anyway.
Thanks/
You are correct that HttpContent does not exist inside of your Task, since it is in a separate thread. So you will need to access create a new instance of Elmah inside your Process method(new thread). You can follow the 2nd or 3rd answer in the previous question, Using Elmah in a Console Application to accomplish what you want.