Phasset + AfNetworking Upload Multiple Videos - ios

Currently I'm using the following code to upload videos:
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[UploadModel getAssetData:entity.asset resultHandler:^(NSData *filedata) {
NSString *mimeType =[FileHelper mimeTypeForFileAtUrl:entity.fileUrl];
// NSError *fileappenderror;
[formData appendPartWithFileData:filedata name:#"data" fileName: entity.filename mimeType:mimeType];
}];
} error:&urlRequestError];
GetAssetData method
+(void)getAssetData: (PHAsset*)mPhasset resultHandler:(void(^)(NSData *imageData))dataResponse{
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionOriginal;
[[PHImageManager defaultManager] requestAVAssetForVideo:mPhasset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *localVideoUrl = [(AVURLAsset *)asset URL];
NSData *videoData= [NSData dataWithContentsOfURL:localVideoUrl];
dataResponse(videoData);
}
}];
}
The problem with this approach that an app simply runs out of memory whenever large/multiple video files are being uploaded. I suppose it's due to requesting the NSDATA (aka filedata ) for uploading of a file(see in the method above). I've tried to request the file path using method
appendPartWithFileURL intead of appendPartWithFileData
it works on an emulator. and fails on a real device with an error that it can't read the file by the path specified. I've described this issue here
PHAsset + AFNetworking. Unable to upload files to the server on a real device
=======================================
Update: I've modified my code in order to test approach of uploading file by the local path on a new iPhone 6s+ as follows
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSString *mimeType =[FileHelper mimeTypeForFileAtUrl:entity.fileUrl];
NSError *fileappenderror;
[formData appendPartWithFileURL:entity.fileUrl name:#"data" fileName:entity.filename mimeType:mimeType error:&fileappenderror];
if (fileappenderror) {
[Sys MyLog: [NSString stringWithFormat:#"Failed to append: %#", [fileappenderror localizedDescription] ] ];
}
} error:&urlRequestError];
Testing on iPhone 6s+ gives a more clear log warning It occurs as the result of invoking method appendPartWithFileURL
<Warning>: my_log: Failed to append file: The operation couldn’t be completed. File URL not reachable.
deny(1) file-read-metadata /private/var/mobile/Media/DCIM/100APPLE/IMG_0008.MOV
15:41:25 iPhone-6s kernel[0] <Notice>: Sandbox: My_App(396) deny(1) file-read-metadata /private/var/mobile/Media/DCIM/100APPLE/IMG_0008.MOV
15:41:25 iPhone-6s My_App[396] <Warning>: my_log: Failed to append file: The file “IMG_0008.MOV” couldn’t be opened because you don’t have permission to view it.
Here is The code used to fetch the local file path from PHAsset
if (mPhasset.mediaType == PHAssetMediaTypeImage) {
PHContentEditingInputRequestOptions * options = [[PHContentEditingInputRequestOptions alloc]init];
options.canHandleAdjustmentData = ^BOOL(PHAdjustmentData *adjustmeta){
return YES;
};
[mPhasset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
dataResponse(contentEditingInput.fullSizeImageURL);
}];
}else if(mPhasset.mediaType == PHAssetMediaTypeVideo){
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionOriginal;
[[PHImageManager defaultManager] requestAVAssetForVideo:mPhasset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *localVideoUrl = [(AVURLAsset *)asset URL];
dataResponse(localVideoUrl);
}
}];
}
So the issue remains the same - files uploaded to the server are empty

The proposed solution above is correct only partially(and it was found by myself before). Since The system doesn't permit to read files outside of sandbox therefore the files cannot be accessed(read/write) by the file path and just copied. In the version iOS 9 and above Photos Framework provides API(it cannot be done through the NSFileManager , but only using Photos framework api) to copy the file into your App's sandbox directory. Here is the code which I used after digging in docs and head files.
First of all copy a file to the app sandbox directory.
// Assuming PHAsset has only one resource file.
PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset:myPhasset] firstObject];
+(void)writeResourceToTmp: (PHAssetResource*)resource pathCallback: (void(^)(NSURL*localUrl))pathCallback {
// Get Asset Resource. Take first resource object. since it's only the one image.
NSString *filename = resource.originalFilename;
NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:filename];
NSURL *localpath = [NSURL fileURLWithPath:pathToWrite];
PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new];
options.networkAccessAllowed = YES;
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource toFile:localpath options:options completionHandler:^(NSError * _Nullable error) {
if (error) {
[Sys MyLog: [NSString stringWithFormat:#"Failed to write a resource: %#",[error localizedDescription]]];
}
pathCallback(localpath);
}];
} // Write Resource into Tmp
Upload Task Itself
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST"
URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// Assuming PHAsset has only one resource file.
PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset:myPhasset] firstObject];
[FileHelper writeResourceToTmp:resource pathCallback:^(NSURL *localUrl)
{
[formData appendPartWithFileURL: localUrl name:#"data" fileName:entity.filename mimeType:mimeType error:&fileappenderror];
}]; // writeResourceToTmp
}// End Url Request
AFHTTPRequestOperation * operation = [[AFHTTPRequestOperation alloc ] initWithRequest:urlRequest];
//.....
// Further steps are described in the AFNetworking Docs
This upload Method has a significant drawback .. you are screwed if device goes into "sleep mode".. Therefore the recommended upload approach here is to use method .uploadTaskWithRequest:fromFile:progress:completionHandler in AFURLSessionManager.
For versions below iOS 9.. In Case of images You can fetch the NSDATA from PHAsset as shown in the code of my question.. and upload it. or write it first into your app sandbox storage before uploading. This approach is not usable in case of large files. Alternatively you may want to use Image/video picker exports files as ALAsset. ALAsset provides api which allows you to read file from the storage.but you have to write it to the sandbox storage before uploading.

Creating NSData for every video might be bad, because videos ( or any other files ) can be much bigger than RAM of the device,
I'd suggest to upload as "file" and not as "data", if you append file, it will send the data from the disk chunk-by-chunk and won`t try to read whole file at once, try using
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error
also have a look at https://github.com/AFNetworking/AFNetworking/issues/828
In your case , use it with like this
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:entity.fileUrl name:entity.filename error:&fileappenderror];
if(fileappenderror) {
NSLog(#"%#",fileappenderror);
}
} error:&urlRequestError];

See my answer here to your other question. Seems relevant here as the subject matter is linked.
In that answer I describe that in my experience Apple does not give us direct access to video source files associated with a PHAsset. Or an ALAsset for that matter.
In order to access the video files and upload them, you must first create a copy using an AVAssetExportSession. Or using the iOS9+ PHAssetResourceManager API.
You should not use any methods that load data into memory as you'll quickly run up against OOM exceptions. And it's probably not a good idea to use the requestAVAssetForVideo(_:options:resultHandler:) method as stated above because you will at times get an AVComposition as opposed to an AVAsset (and you can't get an NSURL from an AVComposition directly).
You also probably don't want to use any upload method that leverages AFHTTPRequestOperation or related APIs because they are based on the deprecated NSURLConnection API as opposed to the more modern NSURLSession APIs. NSURLSession will allow you to conduct long running video uploads in a background process, allowing your users to leave your app and be confident that the upload will complete regardless.
In my original answer I mention VimeoUpload. It's a library that handles video file uploads to Vimeo, but its core can be repurposed to handle concurrent background video file uploads to any destination. Full disclosure: I'm one of the library's authors.

Related

NSData WriteToFile Fails while saving a photo from gallery through Share Extension

I am Writing an app which has share extension to save selected photo to my app' local storage from iphone photo gallery.
NSData WriteToFile returns YES but I couldn't find the stored file into the directory in of which I gave path while writing.
So, in short NSData WriteToFile fails to save a photo at given path.
Below is my code.
- (IBAction)acceptButtonTapped:(id)sender
{
__block UIImage *photo;
for (NSExtensionItem *item in self.extensionContext.inputItems)
{
for (NSItemProvider *itemProvider in item.attachments)
{
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
{
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image)
{
dispatch_async(dispatch_get_main_queue(), ^{
photo = image;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy_MM_dd_hh_mm_ss"];
NSString *fileName;
fileName = [NSString stringWithFormat:#"%#.jpeg",[formatter stringFromDate:[NSDate date]]];
dataPath = [dataPath stringByAppendingPathComponent:fileName];
NSData * imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
BOOL isdone = [imageData writeToFile:dataPath atomically:NO];
NSLog(#"%u", isdone);
});
}
}];
break;
}
}
}
[self.extensionContext completeRequestReturningItems:#[] completionHandler:nil];
}
Any Help would be much appreciable.
Thank you.
If you're trying to access the Document directory from the share extension, NO you can't do that. Share extension or other widgets are separate application from their containing app and therefore have their own sandbox. So you will need to use App Groups to share files.
Application groups are primarily targeted for extensions, more specifically, for widgets.
NSFileManager has a method on it containerURLForSecurityApplicationGroupIdentifier: where you can pass in the identifier you created when turning on App Groups for your apps
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:#"group.com.company.app"];
You can save the files to this location, because you can access the shared application groups from both extension and host app.
You're modifying dataPath on each pass through the loop, appending another filename to it. That will create an ever-growing series of badly formed paths that contain all the filenames.
Don't do that. Create a new local variable filePath, and construct a filename into filePath using
filePath = [docsPath stringByAppendingPathComponent: filename];
Log your path and LOOK AT IT. When your program doesn't behave as expected, don't trust any of your assumptions, because one or more of them may be wrong.

Updating files in iOS app

I have .json file in my Xcode project that contains much information.
I can't fetch it each time because it's really big. But I'd like to update that file once a week in my app. Is it possible?
Maybe you can make a local notification that is fired once a week, and then download the file. Or you can even send push notifications to tell the app to download.
To download a file (in this case and unzip it using SSZipArchiver and AFNetworking for the download):
NSString *url = [NSString stringWithFormat:#"someurl"];
NSString *directoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSString *filePath = [directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"nameofthefile.zip"]];
zipPath = filePath;
NSLog(#"DIRECTORY WHERE THE FILES ARE SAVED-->%#",directoryPath);
AFURLConnectionOperation *operation1 = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation1.outputStream = [NSOutputStream outputStreamToFileAtPath:filePath append:NO];
[operation1 setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
NSLog(#"-----> %f", (float)totalBytesRead / totalBytesExpectedToRead);
self.hud.progress = (float)totalBytesRead / totalBytesExpectedToRead;
}];
[operation1 setCompletionBlock:^()
{
[SSZipArchive unzipFileAtPath:zipPath toDestination:directoryPath overwrite:YES password:nil error:nil delegate:self];
}];
[operation1 start];
Here is some information: How to create local notifications Stackoverflow
You cannot replace the file in your Xcode project. You can only read resources from the application bundle. If you want to write you need to first copy the file to the application documents directory and modify it there.
Now you can download even large files in the background and replace the file in the writable directory when you are done.
Further remarks:
Think about how you could devise the web service to just send incremental changes. That seems so much more reasonable. Also, rather than parsing the content of a huge JSON file into memory, you might be better off using Core Data.

How to know if a PHAsset has been modified?

More specifically, how can you know whether a PHAsset has current version of the underlying asset different than the original?
My user should only need to choose between the current or original asset when necessary. And then I need their answer for PHImageRequestOptions.version.
As of iOS 16, PHAsset has a hasAdjustments property which indicates if the asset has been edited.
For previous releases, you can get an array of data resources for a given asset via PHAssetResource API - it will have an adjustment data resource if that asset has been edited.
let isEdited = PHAssetResource.assetResources(for: asset).contains(where: { $0.type == .adjustmentData })
Note that if you want to actually work with a resource file, you have to fetch its data using a PHAssetResourceManager API. Also note that this method returns right away - there's no waiting for an async network request, unlike other answers here.
I have found two ways of checking PHAsset for modifications.
- (void)tb_checkForModificationsWithEditingInputMethodCompletion:(void (^)(BOOL))completion {
PHContentEditingInputRequestOptions *options = [PHContentEditingInputRequestOptions new];
options.canHandleAdjustmentData = ^BOOL(PHAdjustmentData *adjustmentData) { return YES; };
[self requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
if (completion) completion(contentEditingInput.adjustmentData != nil);
}];
}
- (void)tb_checkForModificationsWithAssetPathMethodCompletion:(void (^)(BOOL))completion {
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.deliveryMode = PHVideoRequestOptionsDeliveryModeFastFormat;
[[PHImageManager defaultManager] requestAVAssetForVideo:self options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if (completion) completion([[asset description] containsString:#"/Mutations/"]);
}];
}
EDIT: I was at the point where I needed the same functionality for PHAsset with an image. I used this:
- (void)tb_checkForModificationsWithAssetPathMethodCompletion:(void (^)(BOOL))completion {
[self requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSString *path = (contentEditingInput.avAsset) ? [contentEditingInput.avAsset description] : contentEditingInput.fullSizeImageURL.path;
completion([path containsString:#"/Mutations/"]);
}];
}
Take a look at PHImageRequestOptionsVersion
PHImageRequestOptionsVersionCurrent
Request the most recent version of the image asset (the one that reflects all edits).
The resulting image is the rendered output from all previously made adjustments.
PHImageRequestOptionsVersionUnadjusted
Request a version of the image asset without adjustments.
If the asset has been edited, the resulting image reflects the state of the asset before any edits were performed.
PHImageRequestOptionsVersionOriginal
Request the original, highest-fidelity version of the image asset. The
resulting image is originally captured or imported version of the
asset, regardless of any edits made.
If you ask user before retrieving assets, you know which version user specified. If you get a phasset from elsewhere, you can do a revertAssetContentToOriginal to get the original asset. And PHAsset has modificationDate and creationDate properties, you can use this to tell if a PHAsset is modified.
I found this code the only one working for now, and it handles most of the edge cases. It may not be the fastest one but works well for most images types. It takes the smallest possible original and modified image and compare their data content.
#implementation PHAsset (Utilities)
- (void)checkEditingHistoryCompletion:(void (^)(BOOL edited))completion
{
PHImageManager *manager = [PHImageManager defaultManager];
CGSize compareSize = CGSizeMake(64, 48);
PHImageRequestOptions *requestOptions = [PHImageRequestOptions new];
requestOptions.synchronous = YES;
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
requestOptions.version = PHImageRequestOptionsVersionOriginal;
[manager requestImageForAsset:self
targetSize:compareSize
contentMode:PHImageContentModeAspectFit
options:requestOptions
resultHandler:^(UIImage *originalResult, NSDictionary *info) {
UIImage *currentImage = originalResult;
requestOptions.version = PHImageRequestOptionsVersionCurrent;
[manager requestImageForAsset:self
targetSize:currentImage.size
contentMode:PHImageContentModeAspectFit
options:requestOptions
resultHandler:^(UIImage *currentResult, NSDictionary *info) {
NSData *currData = UIImageJPEGRepresentation(currentResult, 0.1);
NSData *orgData = UIImageJPEGRepresentation(currentImage, 0.1);
if (completion) {
//handle case when both images cannot be retrived it also mean no edition
if ((currData == nil) && (orgData == nil)) {
completion(NO);
return;
}
completion(([currData isEqualToData:orgData] == NO));
}
}];
}];
}
#end

Using AFNetworking to upload an Audio File

I'm trying to use the AFNetworking library to upload a file to Clypit. I've looked at their documentation here: https://github.com/AFNetworking/AFNetworking
And have configured my code to upload an audio file like so:
NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:cell];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:cellIndexPath];
ICIRecordingCell *c = (ICIRecordingCell *)cell;
NSString *fileName = c.title.text;
NSURL *filePath = [NSURL fileURLWithPath:fileName];
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:#"http://upload.clyp.it/upload"
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:#"audioFile" fileName:#"audio.m4a" mimeType:#"audio/m4a" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
} else {
NSLog(#"%# %#", response, responseObject);
}
} ];
[uploadTask resume];
But neither responseObject or error are ever firing. I'm not even certain if this is the type of request I should be sending. Clypit Api says they accept request as follows:
Upload an audio file. Accepted file types are mp3, ogg, m4a, wav,
aiff, aif and 3gpp. Regardless of file type when uploading, a
resulting mp3 and ogg will be created and made available. The audio
file can be added to a playlist by providing a playlistId and the
playlistUploadToken. Otherwise, a new playlist will automatically be
created. A playlist can contain a maximum of 20 audio files. The title
of the audio file will be set to that of the name of the uploaded
file.
Parameters: audioFile - The binaries of the audio file. playlistId -
Optional. The playlist that the audio file will be added to. If this
value is specified, the correct playlistUploadToken must also be
included in the request. If this value is not specified, a playlist
will be automatically created. playlistUploadToken - Optional. Given
to you after you create a playlist. When adding an audio file to an
already existing playlist, this value must be provided. order -
Optional. The position in which you would like this audio file to
appear in the playlist. description - Optional. The description of the
audio file. Maximum allowed length is 420 characters. longitude -
Optional. The longitude of where the audio file was recorded. If
passed in, latitude becomes required. Value must be between -15069 and
15069 degrees. latitude - Optional. The latitude of where the audio
file was recorded. If passed in, longitude becomes required. Value
must be between -90 and 90 degrees.
Uploads are done via a multipart/form-data POST. Consider the
following form:
It will create a request that looks like this: POST
http://upload.clyp.it/upload HTTP/1.1 Host: upload.clyp.it
Connection: keep-alive Content-Type: multipart/form-data;
boundary=---------------------------21632794128452 Content-Length:
5005
-----------------------------21632794128452 Content-Disposition: form-data; name="audioFile"; filename="MyAudioFile.mp3" Content-Type:
audio/mpeg (Audio file data goes here)
Am I using the right approach? Thanks in advance

iOS NSURL queuing mechansim for multiple requests from file

I am very new to iOS development, but I would like to make an app that has two table view controllers (columns): both are a row of images that act as links. The first would be a column of YouTube videos and the second a column of websites. I would like to have all these listed in a file file.txt listed like so: V, http://youtube.com/example W, http://example.com
There would be a long list of those, the V meaning its a video (for the video column) and W for the websites. Now, I understand how to being the single file in, but what happens afterwards is my concern. Can I read each line into some sort of queue and then fire the NSURL request for each one consecutively? How can that be done with NSURL? Is there perhaps a better approach?
There are two questions for me:
Is a text file really the best format?
I might suggest a plist or archive (if the file is only going to exist only in your app's bundle and/or documents folder) or JSON (if it's going to live on a server before delivering it to the user) instead of a text file. It will make it easier to parse this file than a text file. For example, consider the following dictionary:
NSDictionary *dictionary = #{#"videos" : #[#"http://youtube.com/abc", #"http://vimeo.com/xyz"],
#"websites": #[#"http://apple.com", #"http://microsoft.com"]};
You can save that to a plist with:
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"files.plist"];
[dictionary writeToFile:plistPath atomically:YES];
You can add that file to your bundle or whatever, and then read it at a future date with:
dictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath];
You can, alternatively, write that to a JSON file with:
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonPath = [documentsPath stringByAppendingPathComponent:#"files.json"];
[data writeToFile:jsonPath atomically:YES];
You can read that JSON file with:
data = [NSData dataWithContentsOfFile:jsonPath];
dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
Either way, you can get the list of videos or web sites like so:
NSArray *videos = dictionary[#"videos"];
NSArray *websites = dictionary[#"websites"];
Now that you have your arrays of videos and websites, the question then is how you then use those URLs.
You could do something like:
for (NSString *urlString in videos) {
NSURL *url = [NSURL URLWithString: urlString];
// now do something with the URL
}
The big question is what is the "do something" logic. Because you're dealing with a lot of URLs, you would want to use a NSOperation based solution, not a GCD solution, because NSOperationQueue lets you control the degree of concurrency. I'd suggest a NSOperation-based networking library like AFNetworking. For example, to download the HTML for your websites:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSString *urlString in websites)
{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// convert the `NSData` responseObject to a string, if you want
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
// now do something with it, like saving it in a cache or persistent storage
// I'll just log it
NSLog(#"responseObject string = %#", string);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error = %#", error);
}];
[queue addOperation:operation];
}
Having said that, I'm not sure it makes sense to kick off a ton of network requests. Wouldn't you really prefer to wait until the user taps on one of those cells before retrieving it (and for example, then just open that URL in a UIWebView)? You don't want an app that unnecessarily chews up the user's data plan and battery retrieving stuff that they might not want to retrieve. (Apple has rejected apps that request too much data from a cellular connection.) Or, at the very least, if you want to retrieve stuff up front, only retrieve stuff as you need it (e.g. in cellForRowAtIndexPath), which will retrieve the visible rows, rather than the hundreds of rows that might be in your text/plist/json file.
Frankly, we need a clearer articulation of what you're trying to do, and we might be able to help you with more concise counsel.

Resources