Update an NSTextField with X of Y when downloading files - macos

I am attempting to update a "status label", NSTextField, with the current (X) of total (Y) when downloading files from an NSURLConnection. Below is some code that is working, but not 100%, or the way I would like.
X = runningCurrent
Y = runningTotal
The following code updates the (Y) or ofTotal correctly, however, the (X) or current jumps all over the place and does not increment 1, 2, 3 .. etc.
ApplicationController
- (void) updateLabelWithCurrent:(int)current ofTotal:(int)total
{
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloading %i of %i",current,total]];
[txtStatus setNeedsDisplay:YES];
}
XML Data Source
for (int x = 0; x < [catArray count]; x++)
{
/* download each file to the corresponding category sub-directory */
[[WCSWallpaperDownloader alloc]
initWithWallpaperURL: [NSURL URLWithString:[[catArray objectAtIndex:x] objectForKey:#"imageUrl"]]
andFileOutput: [NSString stringWithFormat:#"%#/%#_0%i.jpg",cat,catName,x] withCurrent:x ofTotal:[catArray count]];
}
WCSWallpaperDownloader
- (id)initWithWallpaperURL:(NSURL *)imageUrl andFileOutput:(NSString*)fileOutput withCurrent:(int)current ofTotal:(int)total
{
self = [super init];
if (self)
{
appController = [[ApplicationController alloc] init];
self.fileOut = fileOutput;
NSURLRequest *imageRequest =
[NSURLRequest requestWithURL:imageUrl cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:1800.0];
[[NSURLConnection alloc] initWithRequest:imageRequest delegate:self];
runningCurrent = current;
runningTotal = total;
}
return self;
}
#pragma mark NSURLConenction
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
receivedData = [[NSMutableData data] retain];
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
/* release the connection, and the data object */
[connection release];
[receivedData release];
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
/* updates the status label with the current download of total objects being downloaded */
[appController updateLabelWithCurrent: runningCurrent ofTotal: runningTotal];
/* skip existing files */
if ( ! [MANAGER fileExistsAtPath:fileOut] )
{
[receivedData writeToFile:fileOut atomically:YES];
[receivedData release];
}
[[appController txtStatus] setStringValue:#""];
}
Solution
The following code correctly increments the download status as each object finishes.
- (void) incrementStatusLabelWithTotal:(int)total
{
runningCurrent++;
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloading %i of %i",runningCurrent,total]];
}

It looks like you are setting off your downloads one by one, but they are not finishing in the same order - so you create each object telling it that it is loading item X of Y, but if the object downloading item 6 finishes before the object downloading item 4, your X is going to go, as you say, all over the place.
Each wallpaper downloader should just tell the appController that it has finished, and let the appController hold the number of items that have been downloaded so far, and the total number.
In fact, the wallpaper downloaders don't really need to know how many downloads are happening, or which particular number they are. Your XML data source should be telling your "app controller" the total number of downloads, and then each downloader, as it finishes, should tell the controller that it is done.
So, your current init method would just be:
- (id)initWithWallpaperURL:(NSURL *)imageUrl andFileOutput:(NSString*)fileOutput
I'm not sure you should be allocating a new instance of appController each time in this method - the rest of the code looks like there should be a single one of these which is displaying one label, effectively a delegate for the downloader? Perhaps this should be assigned by the XML data source when it creates each object?
After the download is complete, your connectionDidFinishLoading method would be something like this:
[appController downloaderDidFinishDownloading:self];
Which would call a method in your appController that looks something like this:
-(void)downloaderDidFinishDownloading:(WCSWallpaperDownloader*)downloader
{
completedDownloads++;
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloaded %i of %i",completedDownloads,totalDownloads]];
}
Where completedDownloads and totalDownloads are ivars in your app controller class.

Related

MBProgressHUD with NSURLConnection XCode 7

Background:
Please don't mark this as duplicate. I have tried every other related post and they didn't work for me. I have looked at countless examples (StackOverflow/MBProgressHUD Demo/etc.) trying to get this to work. I feel like most examples are outdated as some methods are deprecated. This is the closest I have got.
What I want the code to do:
The MBProgress HUD should display the default loading screen with "Preparing" before I start connecting to JSON. When I connect, I want the mode to change to AnnularDeterminate when I receive a response. Once it's finished loading, I want the progress bar to display my progress of adding the data to a dictionary. Once that is done, change the mode to CustomView displaying a checkmark image and "Completed".
The Issue:
The MBProgress HUD will display. However, it only shows the default loading screen with "Preparing..." underneath it and then it will say "Completed" with the checkmark custom view after progress is at 1.0. There is no Determinate view with my updated progress in between.
My Code
- (void)viewDidLoad
{
buildingsDataArray = [[NSMutableArray alloc] init];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.delegate = self;
HUD.label.text = NSLocalizedString(#"Preparing...", #"HUD preparing title");
[self connect];
NSLog(#"End of setup");
}
- (void)connect
{
NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:buildingList]];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
NSLog(#"End of connect");
}
// Connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"connection received response");
[MBProgressHUD HUDForView:self.view].mode = MBProgressHUDModeAnnularDeterminate;
NSLog(#"Changed mode");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[webData appendData:data];
NSLog(#"connection received data");
[MBProgressHUD HUDForView:self.view].progress = 0.0f;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Fail with error - %# %#", [error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
if ([[error localizedDescription] isEqualToString:#"The Internet connection appears to be offline."]) {
//do something
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSArray *allDataArray = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
float count = [allDataArray count];
float c=1;
if ([buildingsDataArray count]!=0)
[buildingsDataArray removeAllObjects];
for (NSDictionary *dataDict in allDataArray){ //from 1 to 6
if ([[dataDict objectForKeyedSubscript:#"id"] intValue] != 0)
[buildingsDataArray addObject:dataDict];
usleep(200000);
float p = c/count;
[MBProgressHUD HUDForView:self.view].progress = p;
NSLog(#"Progress: %f", [MBProgressHUD HUDForView:self.view].progress);
});
c++;
}
[MBProgressHUD HUDForView:self.view].customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Checkmark.png"]];
[MBProgressHUD HUDForView:self.view].label.text = NSLocalizedString(#"Completed", #"HUD done title");
[MBProgressHUD HUDForView:self.view].mode = MBProgressHUDModeCustomView;
[[MBProgressHUD HUDForView:self.view] hideAnimated:YES afterDelay:1.0f];
}
And here is my console output
End of connect
End of setup
connection received response
Changed mode
connection received data
connection finished loading
Progress: 0.166667
Progress: 0.333333
Progress: 0.500000
Progress: 0.666667
Progress: 0.833333
Progress: 1.000000
Additional comments
When I try adding
dispatch_async(dispatch_get_main_queue()
outside all of the MBProgress method calls inside my connection delegate,
I get the default loading screen again. But after progress reaches 1.0, the determinate loading screen view shows (fully loaded) with "Completed" and then disappears after delay 1.0. There is still no loading process in between. Also, the checkmark image never shows. I don't think this is the way to do it.
The output when I do this is:
End of connect
End of doSetup
connection received response
connection received data
connection finished loading
Changed mode
Progress: 0.166667
Progress: 0.333333
Progress: 0.500000
Progress: 0.666667
Progress: 0.833333
Progress: 1.000000
Here is a useful reference that touches base on UICalls on different threads:
Is there a way to make drawRect work right NOW?
This is what worked:
NSURLConnection was being called on the main thread. So all of my UI updates weren't occurring until after the connection delegate calls were finished.
So to fix this problem, I put the connection in a background thread.
- (void)viewDidLoad{
//...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSURL *URL = [NSURL URLWithString:buildingList];
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:URL];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
});
}
Inside the connection delegate methods, I surrounded each MBProgress call with
dispatch_async(dispatch_get_main_queue(), ^{
//...
}
Hope this helps others. I spent all day on this and it was such a simple misunderstanding...

cocoa downloading file callbacks not called when downloaded from background thread, works on main thread

I am new to cocoa programming , I am trying to download binary file from a url to disk. Unfortunately the methods are not calledback for some reason. The call to downloadFile is made from a background thread started via [self performSelectorOnBackground] etc. Any ideas what am I doing wrong ?
NOTE
When I make call to downloadFile from main UI thread, it seems to work, what's up with background thread ?
-(BOOL) downloadFile
{
BOOL downloadStarted = NO;
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:versionLocation]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/tmp" allowOverwrite:YES];
downloadStarted = YES;
} else {
// inform the user that the download failed.
downloadStarted = NO;
}
return downloadStarted;
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Release the connection.
[download release];
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
I think you should start the run loop to which your NSURLDownload object is attached. By default it will use the current thread's run loop, so you probably should do something like this after initialization of NSURLDownload object:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (!self.downloaded && !self.error && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
Properties self.downloaded and self.error should be set in your callbacks.
In main thread run loop probably started by the NSApplication object.

Custom NSView Drag Destination

I'm trying to create a simple NSView that will allow a folder from Finder to be dragged onto it. A folder path is the only thing I want the view to accept as a draggable item. I've been trying to follow the Apple documentation, but so far nothing's working. So far, I've just tried to get the view to work with any file type, but I can't even seem to do that. Here's what I have so far:
-(id) initWithFrame:(NSRect)frameRect
{
if (self = [super initWithFrame:frameRect])
{
NSLog(#"getting called");
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeString,
NSPasteboardTypePDF,
NSPasteboardTypeTIFF,
NSPasteboardTypePNG,
NSPasteboardTypeRTF,
NSPasteboardTypeRTFD,
NSPasteboardTypeHTML,
NSPasteboardTypeTabularText,
NSPasteboardTypeFont,
NSPasteboardTypeRuler,
NSPasteboardTypeColor,
NSPasteboardTypeSound,
NSPasteboardTypeMultipleTextSelection,
NSPasteboardTypeFindPanelSearchOptions, nil]];
}
return self;
}
-(BOOL) prepareForDragOperation: (id<NSDraggingInfo>) sender
{
NSLog(#"preparing for drag");
return YES;
}
The initWithFrame: method is getting called, but when I try to drag into the view the prepareForDragOperation: method doesn't ever seem to get called. My questions:
What am I doing wrong? Why isn't prepareForDragOperation: ever getting called?
What do I need to do to get the drag operation to only support dragging folders?
Update
I updated my registerForDraggedTypes: method with every type I could find. It now looks like this:
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeString,
NSPasteboardTypePDF,
NSPasteboardTypeTIFF,
NSPasteboardTypePNG,
NSPasteboardTypeRTF,
NSPasteboardTypeRTFD,
NSPasteboardTypeHTML,
NSPasteboardTypeTabularText,
NSPasteboardTypeFont,
NSPasteboardTypeRuler,
NSPasteboardTypeColor,
NSPasteboardTypeSound,
NSPasteboardTypeMultipleTextSelection,
NSPasteboardTypeFindPanelSearchOptions,
NSStringPboardType,
NSFilenamesPboardType,
NSPostScriptPboardType,
NSTIFFPboardType,
NSRTFPboardType,
NSTabularTextPboardType,
NSFontPboardType,
NSRulerPboardType,
NSFileContentsPboardType,
NSColorPboardType,
NSRTFDPboardType,
NSHTMLPboardType,
NSURLPboardType,
NSPDFPboardType,
NSVCardPboardType,
NSFilesPromisePboardType,
NSMultipleTextSelectionPboardType, nil]];
I've noticed that the prepareForDragOperation: method isn't getting called when I drag a folder into the view. Did I miss a step?
Here's a simple little drag & drop view meeting those criteria:
MDDragDropView.h:
#interface MDDragDropView : NSView {
BOOL isHighlighted;
}
#property (assign, setter=setHighlighted:) BOOL isHighlighted;
#end
MDDragDropView.m:
#implementation MDDragDropView
#dynamic isHighlighted;
- (void)awakeFromNib {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *path in paths) {
NSError *error = nil;
NSString *utiType = [[NSWorkspace sharedWorkspace]
typeOfFile:path error:&error];
if (![[NSWorkspace sharedWorkspace]
type:utiType conformsToType:(id)kUTTypeFolder]) {
[self setHighlighted:NO];
return NSDragOperationNone;
}
}
}
[self setHighlighted:YES];
return NSDragOperationEvery;
}
And the rest of the methods:
- (void)draggingExited:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
return YES;
}
- (BOOL)isHighlighted {
return isHighlighted;
}
- (void)setHighlighted:(BOOL)value {
isHighlighted = value;
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
if (isHighlighted) {
[NSBezierPath setDefaultLineWidth:6.0];
[[NSColor keyboardFocusIndicatorColor] set];
[NSBezierPath strokeRect:self.frame];
}
}
#end
The reason prepareForDragOperation: isn't being called is that the dragging destination sequence follows a precise set of steps, and if the earlier steps aren't implemented, or are implemented but return a "stop the drag operation" type of answer, the later methods are never reached. (In your case, it doesn't appear that you've implemented the draggingEntered: method, which would need to return something other than NSDragOperationNone to continue on in the sequence).
Before prepareForDragOperation: is sent, the view is first sent a series of dragging destination messages:
A single - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender.
Depending on the NSDragOperation mask returned from that method, the following will be called if it's implemented in your class:
Multiple - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender.
Depending on the NSDragOperation mask returned from that method, then prepareForDragOperation: will be called.
I'm using NSURLPboardType to register for stuff being dropped from the Finder (when I drag a file or a folder to my application, it receives them as urls)
Try this. And if it works, it'll solve your second problem : just check if the URL is a folder to accept or reject the drop :
// if item is an NSURL * :
CFURLHasDirectoryPath((CFURLRef)item)
// returns true if item is the URL of a folder.

Problems creating a new Document-based Core Data project

I am trying to create a Core Data-based Mac OS X application. I've used Core Data on iOS, but things are a little different with AppKit. I am also using Xcode 4.1 on 10.7, and I know some Core Data things have changed on Lion. The only up-to-date reference from Apple is a recent WWDC session, which promises/suggests updated references on Apple.com, but most of the Core Data stuff is very out of date.
To start, I used Xcode to generate a Document-based project using Core Data. I hollowed out some of the Main Menu bits (deleted some menu items) and got rid of the NSWindow object inside Main Menu.xib, creating my own MyDocument.xib with a NSWindow (and sub-views) instead.
Questions:
I assume that a Persistent Store Controller always needs a persistent store, so if a new document is created, I'm just adding an in-memory store. When opening up from disk, I create it using the SQLite store. Is that correct?
I (ostensibly) do a store migration in writeToURL:ofType:error: if the url is different. But if I set a breakpoint in writeToURL:ofType:error:, and then hit Save, my breakpoint is not hit. Why not?
If I choose "Revert to Saved", the application crashes -- hard. I have to force-quit it (and quit Xcode). Why?
(Very) old video tutorials suggest a way to easily bind data to a UIView like a table. Following along with Xcode is impossible since so much has changed. Any newer references out there for that?
Code:
MyDocument.h:
#import <Cocoa/Cocoa.h>
#interface MyDocument : NSPersistentDocument
{
#private
NSManagedObjectModel* mom;
NSPersistentStoreCoordinator* psc;
NSManagedObjectContext* moc;
}
#end
MyDocument.m:
#import "MyDocument.h"
#interface MyDocument ()
- (void)setUpManagedObjectModel;
- (void)setUpPersistentStoreCoordinator;
- (void)setUpManagedObjectContext;
- (BOOL)addPersistentStore:(NSString*)type url:(NSURL*)url error:(NSError**)outError;
#end
#implementation MyDocument
- (void)dealloc
{
[moc release];
[psc release];
[mom release];
[super dealloc];
}
- (id)init
{
self = [super init];
if (self)
{
[self setUpManagedObjectModel];
[self setUpPersistentStoreCoordinator];
[self setUpManagedObjectContext];
}
return self;
}
- (id)initWithType:(NSString *)typeName error:(NSError **)outError
{
self = [super initWithType:typeName error:outError];
if (self)
{
[self addPersistentStore:NSInMemoryStoreType url:nil error:outError];
}
return self;
}
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError **)outError
{
return [self addPersistentStore:NSSQLiteStoreType url:url error:outError];
}
- (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError **)outError
{
NSPersistentStore* currentStore = [[psc persistentStores] lastObject];
if (![[currentStore URL] isEqual:url])
{
NSPersistentStore* newStore = [psc migratePersistentStore:currentStore
toURL:url
options:nil
withType:typeName
error:outError];
if (!newStore)
{
return NO;
}
}
return [moc save:outError];
}
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return #"MyDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has loaded the document's window.
}
+ (BOOL)autosavesInPlace
{
return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
NSLog(#"menu item: %#", [item title]);
return YES;
}
#pragma mark Private Methods
- (void)setUpManagedObjectModel
{
NSURL* modelUrl = [[NSBundle mainBundle] URLForResource:#"MyDatabase" withExtension:#"momd"];
mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelUrl];
}
- (void)setUpPersistentStoreCoordinator
{
psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
}
- (void)setUpManagedObjectContext
{
moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:psc];
}
- (BOOL)addPersistentStore:(NSString*)type url:(NSURL*)url error:(NSError**)outError
{
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSPersistentStore* ps = [psc addPersistentStoreWithType:type
configuration:nil
URL:url
options:options
error:outError];
return (ps != nil);
}
#end
If you're using NSPersistentDocument, by default all of this is handled for you, you don't need to do any Core Data setup. NSPersistentDocument will automatically handle creating, saving and migrating the persistent store.
If you want to control how the persistent store is configured, just override
-configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:
Then you can just ask the document for its managedObjectContext when you need it and code away.
I highly recommend you read Apple's NSPersistentDocument tutorial for more detail.

Issue in writing `NSOutputStream`.

I have one basic question,
While working with NSOutputStream, should we wait for NSStreamEventHasSpaceAvailable to send the packet, so we can call , [NSOutputStream write] as and when its needed,
I believe NSStream should take care of write function...
if this is not correct, then please provide your views on following logic,
===== To Write on NSOutputStream =================
Have Queue to add packet, that to be sent
// StreamQueue.h
#interface StreamQueue : NSObject <NSCoding>
{
NSMutableArray * data;
NSRecursiveLock * theLock;
}
#pragma mark �Initialization & Deallocation�
- (id)init;
- (id)initWithQueue:(CommQueue *)queue;
- (id)initWithCoder:(NSCoder *)coder;
- (void)dealloc;
- (void)encodeWithCoder:(NSCoder *)coder;
#pragma mark
#pragma mark �Accessor Methods�
- (int)size;
- (BOOL)isEmpty;
- (id)top;
- (NSArray *)data;
#pragma mark
#pragma mark �Modifier Methods�
- (void)enqueue:(id)object;
- (id)dequeue;
- (void)removeAll;
#end
and its implementation
#import "StreamQueue.h"
#implementation StreamQueue
#pragma mark �Initialization & Deallocation�
- (id)init
{
if (self = [super init]) {
data = [[NSMutableArray alloc] init];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (id)initWithQueue:(StreamQueue *)queue
{
if (self = [super init]) {
data = [[NSMutableArray alloc] initWithArray:[queue data]];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
data = [[NSMutableArray alloc] initWithArray:[coder decodeObject]];
theLock = [[NSRecursiveLock alloc] init];
}
return self;
}
- (void)dealloc
{
[data release];
[theLock release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:data];
}
#pragma mark
#pragma mark �Accessor Methods�
- (int)size
{
int size;
[theLock lock];
size = [data count];
[theLock unlock];
return size;
}
- (BOOL)isEmpty
{
BOOL empty;
[theLock lock];
empty = ([data count] == 0);
[theLock unlock];
return empty;
}
- (id)top
{
id object = nil;
[theLock lock];
if (![self isEmpty])
object = [data objectAtIndex:0];
[theLock unlock];
return object;
}
- (NSArray *)data
{
NSArray * array;
[theLock lock];
array = [NSArray arrayWithArray:data];
[theLock unlock];
return array;
}
#pragma mark
#pragma mark �Modifier Methods�
- (void)enqueue:(id)object
{
[theLock lock];
[data addObject:object];
[theLock unlock];
}
- (id)dequeue
{
id object = [self top];
if (object != nil) {
[theLock lock];
[object retain];
[data removeObjectAtIndex:0];
[theLock unlock];
}
return [object autorelease];
}
- (void)removeAll
{
[theLock lock];
while (![self isEmpty])
[data removeObjectAtIndex:0];
[theLock unlock];
}
#end
Now when Application have something to send over socket(NSStream), it should add it into the queue,
-(bool)sendRawData:(const uint8_t *)data length:(int)len{
// if still negotiating then don't send data
assert(!networkConnected);
NSData *pData = [NSData dataWithBytes:(const void *)data length:len];
// pToSendPacket is of type StreamQueue
[pToSendPacket enqueue:pData];
return;
}
and this piece of code when we get NSHasSpaceAvailableEvent
-(void)gotSpaceAvailable{
// is there any pending packets that to be send.
NSData *pData = (NSData *)[pToSendPacket dequeue];
if(pData == nil){
// no pending packets..
return;
}
const uint8_t *data = (const uint8_t *)[pData bytes];
int len = [pData length];
int sendlength = [pOutputStream write:data maxLength:len];
if(sendlength == -1 ){
NSError *theError = [pOutputStream streamError];
NSString *pString = [theError localizedDescription];
int errorCode = [theError code];
return ;
}
}
I was expecting Application will keep on receiving the event, whenever OutputStream sends data, but i recieved only once... :(
Please help ...
If you don't wait for the event, the write call will block until space is available. Generally you want to aim to design your code to work asychronously, so waiting for NSStreamEventHasSpaceAvailable is the best solution.
As for when you receive the space available notification, see the documentation here:
If the delegate receives an NSStreamEventHasSpaceAvailable event and
does not write anything to the stream, it does not receive further
space-available events from the run loop until the NSOutputStream
object receives more bytes. When this happens, the run loop is
restarted for space-available events. If this scenario is likely in
your implementation, you can have the delegate set a flag when it
doesn’t write to the stream upon receiving an
NSStreamEventHasSpaceAvailable event. Later, when your program has
more bytes to write, it can check this flag and, if set, write to the
output-stream instance directly.
There is no firm guideline on how many bytes to write at one time.
Although it may be possible to write all the data to the stream in one
event, this depends on external factors, such as the behavior of the
kernel and device and socket characteristics. The best approach is to
use some reasonable buffer size, such as 512 bytes, one kilobyte (as
in the example above), or a page size (four kilobytes).
So you should be getting regular NSStreamEventHasSpaceAvailable events as long as you do write data for each event.

Resources