Lazy `SignalProducer` that fetches more data asynchronously when all data has been consumed - frp

Let's imagine that we can fetch a fixed number of messages asynchronously (one request, containing N elements)
func fetchMessages(max: UInt, from: Offset) -> SignalProducer<Message,NoError>
Now, I'd like to turn this into an unbounded SignalProducer that will lazily call fetchMessages when the previous stream completes.
func stream(from: Offset) -> SignalProducer<Message, NoError> {
// challenge is to implement this function
}
An initial idea that could work, but that would still require pre-computing all the ranges would be to genericize the following code
func lazyFetchFrom(from: Offset) -> SignalProducer<Message,NoError> {
return SignalProducer<Message,NoError> { (observer, disposable) in
fetchMessages(from).start(observer)
}
}
let lazyStream =
fetchMessages(1000, from)
.concat(lazyFetchFrom(from + 1000))
.concat(lazyFetchFrom(from + 2000))
.... // could probably be done generically using a flatMap
Now, I'd like to go one step further and evaluate the next call to lazyFetchFrom once the previous values have been consumed. Is that possible?
Thanks
PS: to be clear, my main concern is to provide some sort of backpressure so that the producer doesn't produce too quickly compared to the consumer
Edit: here is my latest attempt at implementing some backpressure. However, when we observeOn the signal, the backpressure disappears, and everything is queued in memory

- (RACSignal *)allContentFromId:(NSInteger)contentId afterDate:(NSDate *)date fetchSize:(NSInteger)fetchSize {
RACSignal *signalNextPagination = [self nextPaginationAllContentFromId:contentId afterDate:date fetchSize:fetchSize];
//in signalNextPagination will be send next fetch size data and send complete only after downloaded all data
//so we used aggregate
return [signalNextPagination aggregateWithStart:#[] reduce:^id(NSArray *before, NSArray *next) {
return [before arrayByAddingObjectsFromArray:next];
}];
}
- (RACSignal *)nextPaginationAllContentFromId:(NSInteger)contentId afterDate:(NSDate *)date fetchSize:(NSInteger)fetchSize {
//command will be send one fetch request
//after recv data in command need try size fetch
//if size eq fetch size, so need repeat command with new offset
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSDate *date) {
return [self requestContentFromId:contentId afterDate:date limit:fetchSize];
}];
command.allowsConcurrentExecution = YES;
RACSignal *download = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[command.executionSignals flattenMap:^RACStream *(id value) {
return value;
}] subscribeNext:^(NSArray *datas) {
[subscriber sendNext:datas];
if ([datas count] == fetchSize) {
NSDate *date = [[datas firstObject] pubDate];
[command execute:date];
} else {
[subscriber sendCompleted];
}
} error:^(NSError *error) {
[subscriber sendError:error];
[subscriber sendCompleted];
}];
[command execute:date];
return nil;
}];
return download;
}

Related

Wait for asynchronous block to finish - signal (seems) do not work

I am using cryptotokenkit to send/receive data from smart card. The use case is, I must have the response from the card API before I do something else.
In my case I found that this line is always called after kMaxBlockingTimeSmartCardResponse (= 10) seconds.
From
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
it directly goes to
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
Then wait for 10 seconds to come to the block call. Its not the block callback delaying. If I set the dispatch time 20 or 30 seconds, it wait for 20 or 30 seconds to execute. The wait call really wait for the specified time then callback block is executed.
What I am doing wrong? I wonder if this is related to adObserver.
- (void) updateSlots {
self.slotNames = [self.manager slotNames];
self.slotModels = [NSMutableArray new];
self.slots = [NSMutableArray new];
if([self.slotNames count] > 0)
{
for (NSString *slotName in self.slotNames)
{
NSLog(#"SmartCard reader found: %#", slotName);
// semaphore BLOCK starts
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.manager getSlotWithName:slotName reply:^(TKSmartCardSlot *slot)
{
if (slot) {
SCSlotModel *slotModel = [SCSlotModel new];
[self.slotModels addObject:slotModel];
[self.slots addObject:slot];
[slot addObserver:self forKeyPath:#"state"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial
context:nil];
slotModel.suffixedName = slotName; // NOT slot.name; original name is suffixed with 01, 02 etc.
slotModel.slot = slot;
slotModel.cardStatus = [CardUtil mapCardStatus:slot.state];
DLog(#"slot: %#, slotmodel: %#",slot.name, slotModel);
} else {
NSLog(#"Did not find slot with name: %#", slotName);
}
dispatch_semaphore_signal(sema);
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
// semaphore BLOCK ends
self.errorCode = SCPNoError;
}
} else {
NSLog(#"No slot available.");
self.errorCode = SCPErrorReaderNotFound;
}
}
getSlotWithName is the method from TKSmartCardSlotManager
/// Instantiates smartcard reader slot of specified name. If specified name is not registered, returns nil.
- (void)getSlotWithName:(NSString *)name reply:(void(^)(TKSmartCardSlot *__nullable slot))reply;
But in other places it works as expected, for the same type of asynchronous calls.
- (BOOL) beginSession:(TKSmartCard *)card
{
__block BOOL response = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[card beginSessionWithReply:^(BOOL success, NSError *error) {
response = success;
if (!success) {
NSLog(#"Could not begin session.");
}
if (error) {
NSLog(#"Could not begin session with error %#", error);
}
dispatch_semaphore_signal(sema);
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
return response;
}
What thread does getSlotWithName:reply: call its reply handler back on?
If for example updateSlots is executed on the main thread and you are doing something like this:
... // some async operation in `getSlotWithName:reply:` has completed
dispatch_async(dispatch_get_main_queue(), ^{
reply(slot)
});
then you would have ended up queueing your reply on the main thread however the main thread is currently locked with your semaphore.
Ensuring you call dispatch_semaphore_signal() from a thread other than the one you have locked should fix your issue.
Side Note: GCD and semaphores do the trick here but there are better ways you could perform actions after an async operation completes rather than letting a thread get locked up.. For example, delegate callbacks.
Not got enough information here to suggest anything exact but there are other options out there :)

completionHandler of AVAudioPlayerNode.scheduleFile() is called too early

I am trying to use the new AVAudioEngine in iOS 8.
It looks like the completionHandler of player.scheduleFile() is called before the sound file has finished playing.
I am using a sound file with a length of 5s -- and the println()-Message appears round about 1 second before the end of the sound.
Am I doing something wrong or do I misunderstand the idea of a completionHandler?
Thanks!
Here is some code:
class SoundHandler {
let engine:AVAudioEngine
let player:AVAudioPlayerNode
let mainMixer:AVAudioMixerNode
init() {
engine = AVAudioEngine()
player = AVAudioPlayerNode()
engine.attachNode(player)
mainMixer = engine.mainMixerNode
var error:NSError?
if !engine.startAndReturnError(&error) {
if let e = error {
println("error \(e.localizedDescription)")
}
}
engine.connect(player, to: mainMixer, format: mainMixer.outputFormatForBus(0))
}
func playSound() {
var soundUrl = NSBundle.mainBundle().URLForResource("Test", withExtension: "m4a")
var soundFile = AVAudioFile(forReading: soundUrl, error: nil)
player.scheduleFile(soundFile, atTime: nil, completionHandler: { println("Finished!") })
player.play()
}
}
I see the same behavior.
From my experimentation, I believe the callback is called once the buffer/segment/file has been "scheduled", not when it is finished playing.
Although the docs explicitly states:
"Called after the buffer has completely played or the player is stopped. May be nil."
So I think it's either a bug or incorrect documentation. No idea which
You can always compute the future time when audio playback will complete, using AVAudioTime. The current behavior is useful because it supports scheduling additional buffers/segments/files to play from the callback before the end of the current buffer/segment/file finishes, avoiding a gap in audio playback. This lets you create a simple loop player without a lot of work. Here's an example:
class Latch {
var value : Bool = true
}
func loopWholeFile(file : AVAudioFile, player : AVAudioPlayerNode) -> Latch {
let looping = Latch()
let frames = file.length
let sampleRate = file.processingFormat.sampleRate
var segmentTime : AVAudioFramePosition = 0
var segmentCompletion : AVAudioNodeCompletionHandler!
segmentCompletion = {
if looping.value {
segmentTime += frames
player.scheduleFile(file, atTime: AVAudioTime(sampleTime: segmentTime, atRate: sampleRate), completionHandler: segmentCompletion)
}
}
player.scheduleFile(file, atTime: AVAudioTime(sampleTime: segmentTime, atRate: sampleRate), completionHandler: segmentCompletion)
segmentCompletion()
player.play()
return looping
}
The code above schedules the entire file twice before calling player.play(). As each segment gets close to finishing, it schedules another whole file in the future, to avoid gaps in playback. To stop looping, you use the return value, a Latch, like this:
let looping = loopWholeFile(file, player)
sleep(1000)
looping.value = false
player.stop()
The AVAudioEngine docs from back in the iOS 8 days must have just been wrong. In the meantime, as a workaround, I noticed if you instead use scheduleBuffer:atTime:options:completionHandler: the callback is fired as expected (after playback finishes).
Example code:
AVAudioFile *file = [[AVAudioFile alloc] initForReading:_fileURL commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:nil];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
[file readIntoBuffer:buffer error:&error];
[_player scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
// reminder: we're not on the main thread in here
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"done playing, as expected!");
});
}];
My bug report for this was closed as "works as intended," but Apple pointed me to new variations of the scheduleFile, scheduleSegment and scheduleBuffer methods in iOS 11. These add a completionCallbackType argument that you can use to specify that you want the completion callback when the playback is completed:
[self.audioUnitPlayer
scheduleSegment:self.audioUnitFile
startingFrame:sampleTime
frameCount:(int)sampleLength
atTime:0
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
// do something here
}];
The documentation doesn't say anything about how this works, but I tested it and it works for me.
I've been using this workaround for iOS 8-10:
- (void)playRecording {
[self.audioUnitPlayer scheduleSegment:self.audioUnitFile startingFrame:sampleTime frameCount:(int)sampleLength atTime:0 completionHandler:^() {
float totalTime = [self recordingDuration];
float elapsedTime = [self recordingCurrentTime];
float remainingTime = totalTime - elapsedTime;
[self performSelector:#selector(doSomethingHere) withObject:nil afterDelay:remainingTime];
}];
}
- (float)recordingDuration {
float duration = duration = self.audioUnitFile.length / self.audioUnitFile.processingFormat.sampleRate;
if (isnan(duration)) {
duration = 0;
}
return duration;
}
- (float)recordingCurrentTime {
AVAudioTime *nodeTime = self.audioUnitPlayer.lastRenderTime;
AVAudioTime *playerTime = [self.audioUnitPlayer playerTimeForNodeTime:nodeTime];
AVAudioFramePosition sampleTime = playerTime.sampleTime;
if (sampleTime == 0) { return self.audioUnitLastKnownTime; } // this happens when the player isn't playing
sampleTime += self.audioUnitStartingFrame; // if we trimmed from the start, or changed the location with the location slider, the time before that point won't be included in the player time, so we have to track it ourselves and add it here
float time = sampleTime / self.audioUnitFile.processingFormat.sampleRate;
self.audioUnitLastKnownTime = time;
return time;
}
Yes, it does get called slightly before the file (or buffer) has completed. If you call [myNode stop] from within the completion handler the file (or buffer) will not fully complete. However, if you call [myEngine stop], the file (or buffer) will complete to the end
// audioFile here is our original audio
audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: {
print("scheduleFile Complete")
var delayInSeconds: Double = 0
if let lastRenderTime = self.audioPlayerNode.lastRenderTime, let playerTime = self.audioPlayerNode.playerTime(forNodeTime: lastRenderTime) {
if let rate = rate {
delayInSeconds = Double(audioFile.length - playerTime.sampleTime) / Double(audioFile.processingFormat.sampleRate) / Double(rate!)
} else {
delayInSeconds = Double(audioFile.length - playerTime.sampleTime) / Double(audioFile.processingFormat.sampleRate)
}
}
// schedule a stop timer for when audio finishes playing
DispatchTime.executeAfter(seconds: delayInSeconds) {
audioEngine.mainMixerNode.removeTap(onBus: 0)
// Playback has completed
}
})
As of today, in a project with deployment target 12.4, on a device running 12.4.1, here's the way we found to successfully stop the nodes upon playback completion:
// audioFile and playerNode created here ...
playerNode.scheduleFile(audioFile, at: nil, completionCallbackType: .dataPlayedBack) { _ in
os_log(.debug, log: self.log, "%#", "Completing playing sound effect: \(filePath) ...")
DispatchQueue.main.async {
os_log(.debug, log: self.log, "%#", "... now actually completed: \(filePath)")
self.engine.disconnectNodeOutput(playerNode)
self.engine.detach(playerNode)
}
}
The main difference w.r.t. previous answers is to postpone node detaching on main thread (which I guess is also the audio render thread?), instead of performing that on callback thread.

How can I get XCTest to wait for async calls in setUp before tests are run?

I'm writing integration tests in Xcode 6 to go alongside my unit and functional tests. XCTest has a setUp() method that gets called before every test. Great!
It also has XCTestException's which let me write async tests. Also great!
However, I would like to populate my test database with test data before every test and setUp just starts executing tests before the async database call is done.
Is there a way to have setUp wait until my database is ready before it runs tests?
Here's an example of what I have do now. Since setUp returns before the database is done populating I have to duplicate a lot of test code every test:
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Fill out a database with data.
var data = getData()
overwriteDatabase(data, {
// Database populated.
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
Here's what I would like:
class MyTestCase: XCTestCase {
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
var data = getData()
db.overwriteDatabase(data, onDone: () -> () {
// When database done, do something that causes setUp to end
// and start running tests
})
}
func test_checkSomethingExists() {
let expectation = expectationWithDescription("")
var expected:DatabaseItem
// Do test... in this pseudocode I just check something...
db.retrieveDatabaseItem({ expected in
XCTAssertNotNil(expected)
expectation.fulfill()
})
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
}
}
Rather than using semaphores or blocking loops, you can use the same waitForExpectationsWithTimeout:handler: function you use in your async test cases.
// Swift
override func setUp() {
super.setUp()
let exp = expectation(description: "\(#function)\(#line)")
// Issue an async request
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
exp.fulfill()
}
// Wait for the async request to complete
waitForExpectations(timeout: 40, handler: nil)
}
// Objective-C
- (void)setUp {
[super setUp];
NSString *description = [NSString stringWithFormat:#"%s%d", __FUNCTION__, __LINE__];
XCTestExpectation *exp = [self expectationWithDescription:description];
// Issue an async request
NSData *data = [self getData];
[db overwriteDatabaseData: data block: ^(){
[exp fulfill];
}];
// Wait for the async request to complete
[self waitForExpectationsWithTimeout:40 handler: nil];
}
There are two techniques for running asynchronous tests. XCTestExpectation and semaphores. In the case of doing something asynchronous in setUp, you should use the semaphore technique:
override func setUp() {
super.setUp()
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
let semaphore = DispatchSemaphore(value: 0)
db.overwriteDatabase(data) {
// do some stuff
semaphore.signal()
}
semaphore.wait()
}
Note, for that to work, this onDone block cannot run on the main thread (or else you'll deadlock).
If this onDone block runs on the main queue, you can use run loops:
override func setUp() {
super.setUp()
var finished = false
// Fill out a database with data. I can make this call do anything, here
// it returns a block.
let data = getData()
db.overwriteDatabase(data) {
// do some stuff
finished = true
}
while !finished {
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
This is a very inefficient pattern, but depending upon how overwriteDatabase was implemented, it might be necessary
Note, only use this pattern if you know that onDone block runs on the main thread (otherwise you'll have to do some synchronization of finished variable).
Swift 4.2
Use this extension:
import XCTest
extension XCTestCase {
func wait(interval: TimeInterval = 0.1 , completion: #escaping (() -> Void)) {
let exp = expectation(description: "")
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
completion()
exp.fulfill()
}
waitForExpectations(timeout: interval + 0.1) // add 0.1 for sure `asyncAfter` called
}
}
and usage like this:
func testShouldDeleteSection() {
let tableView = TableViewSpy()
sut.tableView = tableView
sut.sectionDidDelete(at: 0)
wait {
XCTAssert(tableView.isReloadDataCalled, "Check reload table view after section delete")
}
}
The example above isn't complete, but you can get the idea. Hope this helps.
Swift 5.5 & iOS 13+
You could overridefunc setUp() async throws for instance:
final class MyTestsAsync: XCTestCase {
var mockService: ServiceProtocolMock!
override func setUp() async throws {
mockService = await {
//... some async setup
}()
}
override func tearDown() async throws {
//...
See Apple docs on concurrency note here

How to Limit the Number of Tokens in a NSTokenField?

I have a NSTokenField Where the tokens are created upon hitting enter. I would like to limit the number of tokens in this field. Say for example, User should be allowed to enter only 2 tokens one after the other. Later, neither user should be allowed to set the Token nor user should be allowed to search further. In short, User should be blocked after 2 tokens.
Could any one please help me in achieving this???
Thanks in advance :)
The solution is divided in 2 parts:
-(NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index
{
//limit the tokens
if(self.tokensLimit)
{
NSArray * tokensArray = [_tokenField objectValue];
if([tokensArray count] > 0)
{
if([tokens isEqualToArray:tokensArray])
{
return tokens;
}
else if([tokensArray count]>=self.tokensLimit)
{
return #[];
}
else if([tokens count]>0)
{
tokens = [tokens subarrayWithRange:NSMakeRange(0, MIN([tokens
count], self.tokensLimit))];
}
else
return #[];
}
else
{
tokens = [tokens subarrayWithRange:NSMakeRange(0, MIN([tokens count], self.tokensLimit))];
}
}
return tokens;
}
where tokensLimit is an int > 0
the delegate covers all the cases like tokens added by copy/paste, completion list, drag&drop, manually written etc..
this other delegate cover the case where the user write a string and hit "TAB"
- (BOOL)control:(NSControl *)control isValidObject:(id)object
{
if(self.tokensLimit)
{
NSArray * tokensArray = [_tokenField objectValue];
tokensArray = [tokensArray subarrayWithRange:NSMakeRange(0, MIN([tokensArray count], self.tokensLimit))];
[_tokenField setObjectValue:tokensArray];
}
return YES;
}
If you save the tokens in a db, you can count the number of rows of the particular users id, and add an if statement to limit it to 2.
Behold:
var maximumTokens: Int = 2
func tokenField(_ tokenField: NSTokenField, shouldAdd tokens: [Any], at index: Int) -> [Any] {
var count = 0
if let textView = tokenField.currentEditor() as? NSTextView {
for scalar in textView.string.unicodeScalars {
if scalar.value == unichar(NSAttachmentCharacter) {
count += 1
}
}
}
return tokens.filter({ _ in
count += 1
return count <= maximimTokens
})
}
I've tested it and it works when you are typing tags or even copying & pasting them in.

using NSOperationQueue and blocks

I am having a little trouble using addoperationwithblock in Cocoa. Let's say I have a master function
-(IBAction) callthisone {
// Call another function "slave" here and store returned value in result
result = return value from slave
NSLog(#" result is %#",result);
}];
}
-(NSArray *) slave {
[operationQueue addOperationWithBlock: ^{
NSString * result = #"5" ;
}];
return result;
}
I can never get the result value returned in the master. How do I do this ? Is my approach correct ? Thanks
You may try something like this:
-(IBAction) callthisone {
[self slave: ^(NSString* result) {
NSLog(#" result is %#",result);
}
];
}
-(void)slave: (void(^)(NSString*)) callback {
[operationQueue addOperationWithBlock: ^{
NSString* str = [NSString stringWithFormat: #"5]";
callback(str);
}
];
}
Apple's documentation for addOperationWithBlock says:
Parameters
The block to execute from the operation object. The block should take
no parameters and have no return value.
These are meant for self contained block operations.
Could you try something different that does have more flexibility in terms of getting stuff in and out of the queue / thread? Maybe Grand Central Dispatch (I was just looking at this thread).

Resources