Handle writeData method on NSFileHandle on main thread - ios

Since writeData call is synchronous, what is the best way to use it when we call writeData from a different thread other than main queue?
For instance, a web service is called to fetch some data and the completionHandler is assigned to the web service call. Now this completion handler will be executed on a different thread (not on main queue).
I have seen my app getting stuck, on writeData method for 5 to 6 mins. This is the only thing I can suspect right now.
I tried wrapping around my writeData call with dispatch_async(mainQueue) but it did not work.
- (void) writeToFile: (NSString *) targetString
{
//_loggingString holds the data, which keeps on accumulating as the user performs operations. At some point of time (callbacks from API's I call this method, to actually, write this string in the file and clear this string afterwards.)
NSString *oldString = [_loggingString copy];
_loggingString = [oldString stringByAppendingString:targetString];
if (![[NSFileManager defaultManager]fileExistsAtPath:#"somePath"])
{
[[NSFileManager defaultManager]createFileAtPath:#"somePath" contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"somePath"];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
_loggingString = #"";
}

You can do the saving part in a BackGround thread
- (void) writeToFile: (NSString *) targetString
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *oldString = [_loggingString copy];
_loggingString = [oldString stringByAppendingString:targetString];
if (![[NSFileManager defaultManager]fileExistsAtPath:#"somePath"])
{
[[NSFileManager defaultManager]createFileAtPath:#"somePath" contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"somePath"];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
_loggingString = #"";
});
}

It is not preferred to do file write operation in main thread. Also, there will be issue in performance with default global queue, as the system cannot prioritize the task.
So try to create 4 types of background queues:
dispatch_queue_t GlobalUserInteractiveQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
}
dispatch_queue_t GlobalUserInitiatedQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
}
dispatch_queue_t GlobalUtilityQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
}
dispatch_queue_t GlobalBackgroundQueue(void) {
return dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
}
In your code just do this:
create custom queue.
queue = dispatch_queue_create("customQueueName", NULL);
then write code in dispatch async
dispatch_async( queue ,
^ {
// execute asynchronously
[fileHandle seekToEndOfFile];
[fileHandle writeData:[_loggingString dataUsingEncoding:NSUTF8StringEncoding]];
});
Check the working process of each queue here:
https://gist.github.com/ankitthakur/dd945a66924fbd697169

Related

Why am I able to access file when device is locked? (iOS)

My app has turned on Data Protection and I created a file with NSFileProtectionComplete
+ (void)createLogFile {
NSString *deviceModel = [Utils getDeviceModel];
NSString *appVersion = [Utils getAppVersion];
NSData *initData = [[NSString stringWithFormat:#"%#-%#\n================================\n\n\n", deviceModel, appVersion] dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:[self logFilePath]
contents:initData
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
}
and when I lock my device applicationProtectedDataWillBecomeUnavailable: will be called.
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSData *key = [MyKeychain getKey];
NSString *log = [NSString stringWithFormat:#"The key is:\n %#", key];
[MyFileLogger logInfo:log];
});
}
Then I can find the result in the file, which means I was able to write that file when my device is locked.
Shouldn't Data Protection prevents from accessing files when device is locked? What's wrong?
--updated-- (add method logInfo:)
+ (void)logInfo:(NSString *)str {
NSString *info = [self wrapWithTimestamp: str];
NSString *logFilePath = [Utils logFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) {
[Utils createLogFile];
}
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[info dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
}
According to the answer to this question, after the applicationProtectedDataWillBecomeUnavailable method is called there is a 10 second "grace period" before data protection activates.
If you increase your time delay from 5 to 11 seconds you should see that your data is not written to your log file.
I was able to observe this with sample code and an 11 second delay.

Write logs in HTML file using GCD doesn't work as expected

I created an overload method for NSLog in order to write all logs in an HTML file.
All logs are written in the HTML file using GCD.
The problem is that lines are sometimes truncated...
Here my code :
Write in log file function :
+(void)writeInLogFile:(NSString *)strLog inFolder:(NSString *)folder fileName:(NSString *)fileName extension:(NSString *)extension{
//Create Directory
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:folder];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) //Does directory already exists?
{
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error])
{
NSLog(#"%d||Create log directory error: %#",LOG_SEVERITY_HIGH, error);
}
}
NSString* filePath = [NSString stringWithFormat:#"%#/%#.%#",path,fileName,extension];
int nbOfLogFiles = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil] count];
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
[HandleString createLogFile:filePath andStrLog:strLog];
}
else{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
}
}
That is the result :
And the call (called each time an NSLog is executed):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
That is the result :
As you can see, the last line is truncated...
This appens throughout the file.
I tried to run the process on the main thread and it works well without problems.
Another interesting thing when i change the QOS the result isn't the same, for exemple whith priority high, i have more truncated lines.
Edit : The code to write in file :
+(void)createLogFile:(NSString*)filePath andStrLog:(NSString*)strLog{
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
The issue is that you're writing from multiple threads simultaneously. Consider this sequence of events:
Thread A seeks to end of file
Thread B seeks to end of file
Thread B writes its data
Thread A writes its data
Thread A's data will overwrite Thread B's data. If Thread A's data is longer, then no trace of Thread B's data will be left. If Thread A's data is shorter, then the part of Thread B's data beyond that length will remain.
Swapping the order of the last two steps has a similar problem. And there are, of course, more complicated scenarios with more threads.
One solution is to serialize all accesses to the file, as you've done in your self-answer. Another is to open the file in append-only mode. In this mode, all writes are done at the end of the file. This is enforced by the OS. There's no way for two simultaneous writes to overwrite each other. The current file position of the file handle is irrelevant, so there's no need to seek.
NSFileHandle doesn't directly support opening a file in append-only mode. You have to use open() to open a file descriptor and then hand that file descriptor off to NSFileHandle. (You could also just use write() and close(), but it's a bit messier.) For example:
int fd = open(filePath.fileSystemRepresentation, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
// handle error
}
NSFileHandle* fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
[fileHandle release]; // Only needed if not using ARC
Note that you don't have to explicitly create the log file in this case. The open() call will create it if it doesn't exist, because of the O_CREAT flag.
Given the phrase "I tried to run the process on the main thread and it works well without problems." I would say that your truncation problem comes from a buffer being closed before completely empty. That does not explain the disparity you might have observed while using high priority (remember that the extra lines might be coincidental until proven otherwise...).
Now, I would recommend you to try a small sleep before ending the thread scope...
I found a solution a lil bit tricky :
I removed the dispatch_asynch :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
And in my write in log file function i did :
static dispatch_queue_t asyncQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
asyncQueue = dispatch_queue_create("asyncQueue", NULL);
});
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
dispatch_async(asyncQueue, ^{
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
else{
dispatch_async(asyncQueue, ^{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
It could may be help someone.
But i have to know if according to you, it is a good solution?
John

Clean GCD queue for all the rest requests in queue

I have a cache of files in which I need to write/read images.
All the work with the file system I need to perform in background.
For this purposes for saving files I use:
dispatch_barrier_async([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
[data writeToFile:tileFilePathName atomically:YES];
});
And for reading:
__block UIImage *tileImage = nil;
dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
tileImage = [[UIImage imageWithContentsOfFile:tileFilePathName] retain];
dispatch_async(dispatch_get_main_queue(), ^{
completion (tileCoordValue, side, tileImage, error);
[tileImage release];
});
});
Everything works well, there is a great amount of files in cash folder, but I sometimes I need to clean the cash. I need to cancel all the queue blocks and perform a block with cleaning the folder.
The first my realization of the method looks like this:
+ (void) cleanCash
{
NSString *folderPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"cash"];
dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
NSParameterAssert(error == nil);
}
});
}
But I have a numerous problems with it because of it is not cancel the all the waiting operations in queue. I try to look solutions in SO but can't implement them unfortunately. Can anyone help me with this?
You can use NSOperationQueue with maxConcurrentOperationCount = 1 (it will make a serial queue) for run NSOperation that will load your images;
NSOperation is a cancelable object. Also you can make dependencies between operations.
P.S.
You can view SDWebImage, it contains SDImageCache class. It class is very good for manage cache of images.

Updating UI (labels) while downloading Images in iOS

This is the function to download images , in this function i have to update the UI, the UILabels that shows total images and counter value (downloaded images).
thats why i am calling a thread.
-(void) DownloadImages
{
NSLog(#"Images count to download %lu",(unsigned long)[productID count]);
if ([productID count] > 0)
{
// Document Directory
NSString *myDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for (i = 0; i < [productID count]; i++)
{
[NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
// [self UpdateLabels]; this does not work….
NSLog(#"image no %d", i);
//[self Status];
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#-1-lrg.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
}
NSLog(#"Downloading images completed...");
}
}
Problem:
- here for every new image i have to call every time a new NSThread, there may be thousands of images and it looks bad that there will be thousands threads.
in simulator i have tested it for 3000 images, but in my iPad when testing it gives the error,,,
App is Exited and terminated due to memory pressure.
i guess the error is due to this calling approach of new thread every time.
what i am doing wrong here.?????????????
i have searched there are two different options but i don't know which one is right option,
Options are:
- [self performSelectorInBackground:#selector(UpdateLabels) withObject:nil];
- [NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
and i also want to know that if i use
[NSThread cancelPreviousPerformRequestsWithTarget:self];
just after the call of selector, is it right approach or not..???
My suggestion is to use NSOperationQueue . you could then set the maxConcurrentOperationCount property to whatever you like.
In addition in your case is the best approach.
Make some research on what are your maxConcurrentOperationCount with how many concurrent thread your iphone could run without any issue.
NSOperationQueue will manage this for you run new thread with this limitation.
It should look something like this:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue setMaxConcurrentOperationCount:500];
for (i = 0; i < [productID count]; i++) {
[myQueue addOperationWithBlock:^{
// you image download here
}];
}
but check apple reference first.

Dispatch queues and asynchronous RNCryptor

This is a follow-up to Asynchronously decrypt a large file with RNCryptor on iOS
I've managed to asynchronously decrypt a large, downloaded file (60Mb) with the method described in this post, corrected by Calman in his answer.
It basically goes like this:
int blockSize = 32 * 1024;
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:...];
NSOutputStream *decryptedStream = [NSOutputStream output...];
[cryptedStream open];
[decryptedStream open];
RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:#"blah" handler:^(RNCryptor *cryptor, NSData *data) {
NSLog("Decryptor recevied %d bytes", data.length);
[decryptedStream write:data.bytes maxLength:data.length];
if (cryptor.isFinished) {
[decryptedStream close];
// call my delegate that I'm finished with decrypting
}
}];
while (cryptedStream.hasBytesAvailable) {
uint8_t buf[blockSize];
NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize];
NSData *data = [NSData dataWithBytes:buf length:bytesRead];
[decryptor addData:data];
NSLog("Sent %d bytes to decryptor", bytesRead);
}
[cryptedStream close];
[decryptor finish];
However, I'm still facing a problem: the whole data is loaded in memory before being decrypted. I can see a bunch of "Sent X bytes to decryptor", and after that, the same bunch of "Decryptor recevied X bytes" in the console, when I'd like to see "Sent, received, sent, receives, ...".
That's fine for small (2Mb) files, or with large (60Mb) files on simulator; but on a real iPad1 it crashes due to memory constraints, so obviously I can't keep this procedure for my production app.
I feel like I need to send the data to the decryptor by using dispatch_async instead of blindly sending it in the while loop, however I'm completely lost. I've tried:
creating my own queue before the while, and using dispatch_async(myQueue, ^{ [decryptor addData:data]; });
the same, but dispatching the whole code inside of the while loop
the same, but dispatching the whole while loop
using RNCryptor-provided responseQueue instead of my own queue
Nothing works amongst these 4 variants.
I don't have a complete understanding of dispatch queues yet; I feel the problem lies here. I'd be glad if somebody could shed some light on this.
If you only want to process one block at a time, then only process a block when the first block calls you back. You don't need a semaphore to do that, you just need to perform the next read inside the callback. You might want an #autoreleasepool block inside of readStreamBlock, but I don't think you need it.
When I have some time, I'll probably wrap this directly into RNCryptor. I opened Issue#47 for it. I am open to pull requests.
// Make sure that this number is larger than the header + 1 block.
// 33+16 bytes = 49 bytes. So it shouldn't be a problem.
int blockSize = 32 * 1024;
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:#"C++ Spec.pdf"];
NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:#"/tmp/C++.crypt" append:NO];
[cryptedStream open];
[decryptedStream open];
// We don't need to keep making new NSData objects. We can just use one repeatedly.
__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNEncryptor *decryptor = nil;
dispatch_block_t readStreamBlock = ^{
[data setLength:blockSize];
NSInteger bytesRead = [cryptedStream read:[data mutableBytes] maxLength:blockSize];
if (bytesRead < 0) {
// Throw an error
}
else if (bytesRead == 0) {
[decryptor finish];
}
else {
[data setLength:bytesRead];
[decryptor addData:data];
NSLog(#"Sent %ld bytes to decryptor", (unsigned long)bytesRead);
}
};
decryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
password:#"blah"
handler:^(RNCryptor *cryptor, NSData *data) {
NSLog(#"Decryptor recevied %ld bytes", (unsigned long)data.length);
[decryptedStream write:data.bytes maxLength:data.length];
if (cryptor.isFinished) {
[decryptedStream close];
// call my delegate that I'm finished with decrypting
}
else {
// Might want to put this in a dispatch_async(), but I don't think you need it.
readStreamBlock();
}
}];
// Read the first block to kick things off
readStreamBlock();
Cyrille,
The reason your app is crashing due to memory constraints is that the RNCryptor buffer grows beyond the capabilities of the device.
Basically, you're reading the content of the file much faster than RNCryptor can handle it. Since it can't decrypt fast enough it buffers the incoming stream until it can process it.
I haven't yet have time to dive into the RNCryptor code and figure out exactly how it's using GCD to manage everything, but you can use a semaphore to force the reads to wait until the previous block was decrypted.
The code below can successfully decrypt a 225MB file on an iPad 1 without crashing.
It has a few issues that I'm not quite happy with, but it should give you a decent starting point.
Some things to note:
I wrapped the internals of the while loop in an #autoreleasepool block to force the release of the data. Without it, the release won't happen until the while loop finishes. (Matt Galloway has a great post explaining it here: A Look under ARC's hood
The call to dispatch_semaphore_wait blocks execution until a dispatch_semaphore_signal is received. This means no UI updates and the potential of the app freezing if you send one too many (thus the check for bytesRead > 0).
Personally I feel that there must be a better solution for this, but I haven't yet had the time to research it a bit more.
I hope this helps.
- (IBAction)decryptWithSemaphore:(id)sender {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int total = 0;
int blockSize = 32 * 1024;
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *input = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:#"zhuge.rncryptor"];
NSString *output = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:#"zhuge.decrypted.pdf"];
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:input];
__block NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:output append:NO];
__block NSError *decryptionError = nil;
[cryptedStream open];
[decryptedStream open];
RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:#"12345678901234567890123456789012" handler:^(RNCryptor *cryptor, NSData *data) {
#autoreleasepool {
NSLog(#"Decryptor recevied %d bytes", data.length);
[decryptedStream write:data.bytes maxLength:data.length];
dispatch_semaphore_signal(semaphore);
data = nil;
if (cryptor.isFinished) {
[decryptedStream close];
decryptionError = cryptor.error;
// call my delegate that I'm finished with decrypting
}
}
}];
while (cryptedStream.hasBytesAvailable) {
#autoreleasepool {
uint8_t buf[blockSize];
NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize];
if (bytesRead > 0) {
NSData *data = [NSData dataWithBytes:buf length:bytesRead];
total = total + bytesRead;
[decryptor addData:data];
NSLog(#"New bytes to decryptor: %d Total: %d", bytesRead, total);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
}
[cryptedStream close];
[decryptor finish];
dispatch_release(semaphore);
}
After spending the last 2 days trying to get my MBProgress hud to update its progress with Calman's code i came up with the following.
The memory used still stays low and the UI updates
- (IBAction)decryptWithSemaphore:(id)sender {
__block int total = 0;
int blockSize = 32 * 1024;
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *input = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:#"zhuge.rncryptor"];
NSString *output = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:#"zhuge.decrypted.pdf"];
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:input];
__block NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:output append:NO];
__block NSError *decryptionError = nil;
__block RNDecryptor *encryptor=nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:input error:NULL];
__block long long fileSize = [attributes fileSize];
[cryptedStream open];
[decryptedStream open];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
encryptor = [[RNDecryptor alloc] initWithPassword:#"12345678901234567890123456789012" handler:^(RNCryptor *cryptor, NSData *data) {
#autoreleasepool {
NSLog(#"Decryptor recevied %d bytes", data.length);
[decryptedStream write:data.bytes maxLength:data.length];
dispatch_semaphore_signal(semaphore);
data = nil;
if (cryptor.isFinished) {
[decryptedStream close];
decryptionError = cryptor.error;
[cryptedStream close];
[encryptor finish];
// call my delegate that I'm finished with decrypting
}
}
}];
while (cryptedStream.hasBytesAvailable) {
#autoreleasepool {
uint8_t buf[blockSize];
NSUInteger bytesRead = [cryptedStream read:buf maxLength:blockSize];
if (bytesRead > 0) {
NSData *data = [NSData dataWithBytes:buf length:bytesRead];
total = total + bytesRead;
[encryptor addData:data];
NSLog(#"New bytes to decryptor: %d Total: %d", bytesRead, total);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
HUD.progress = (float)total/fileSize;
});
}
}
}
});
}

Resources