Download file using AFNetworking on iOS 6 - ios6

I've recently updated to AFNetworking 2.0. The documentation said it is compatible with iOS6.0+. I am building a iOS 6.0 app, when I am trying to implement a download method (both images and videos). The example use
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
However, I got an "Use of undeclared identifier 'AFURLSessionManager'" error. And I found out that AFURLSessionManager use a class that is only available from iOS7. I'm just wondering, who could I download in iOS6 using AFNetworking?
Also, is there anyway to see download progress?
Thank you

You can use the AFHTTPRequestOperation class to perform a file download on iOS 6. You basically just need to set the operation's outputStream property to store the file and the downloadProgressBlock property to monitor the progress.
The bare bones method below is declared in a class that is a subclass of AFHTTPRequestOperationManager. When I initialized an instance of this class I set up the baseURL property.
- (AFHTTPRequestOperation *)downloadFileWithContentId:(NSString *)contentId destination:(NSString*)destinationPath {
NSString *relativeURLString = [NSString stringWithFormat:#"api/library/zipped/%#.zip", contentId];
NSString *absoluteURLString = [[NSURL URLWithString:relativeURLString relativeToURL:self.baseURL] absoluteString];
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:#"GET" URLString:absoluteURLString parameters:nil];
void (^successBlock)(AFHTTPRequestOperation *operation, id responseObject) = ^void(AFHTTPRequestOperation *operation, id responseObject) {
};
void (^failureBlock)(AFHTTPRequestOperation *operation, NSError *error) = ^void(AFHTTPRequestOperation *operation, NSError *error) {
};
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:successBlock failure:failureBlock];
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
operation.outputStream = outputStream;
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
}];
[self.operationQueue addOperation:operation];
return operation;
}

try this...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
AFHTTPRequestOperation *operation = [manager GET:urlString
parameters:nil
success:^(AFHTTPRequestOperation *operation, NSData *responseData)
{
[responseData writeToURL:someLocalURL atomically:YES];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Downloading error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
float downloadPercentage = (float)totalBytesRead/(float)(totalBytesExpectedToRead);
[someProgressView setProgress:downloadPercentage animated:YES];
}];

As you say AFURLSessionManager is only available in iOS 7(is backed by NSURLSession), so you should use the NSURLConnection based classes in AFNetworking 2.0 (AFHTTPRequestOperationManager, AFHTTPRequestOperation, etc).

Related

Throwing a MacOS notification from a minimal standalone Objective-C program?

I'm trying to implement push notifications on MacOS within a C codebase. Ideally, there'd just be one Objective-C file containing (1) a public C function I can call and (2) some Objective-C code I can use to throw a notification. This way, the source files can be compiled and linked seamlessly in the build process.
Toward this end, I've been trying to create a minimal example that can throw notifications with just a single .m file (not an entire XCode project), much like the one discussed in NSUserNotificationCenter not showing notifications. However, two problems:
I still can't get the code to work despite trying the solutions in the aforementioned link. It compiles and runs but does not throw a notification.
If possible, we'd like to switch to the new user notifications API. Not a big deal if this isn't possible for now, though.
Here's what I've tried so far:
#import <Foundation/Foundation.h>
#import <Foundation/NSUserNotification.h>
#interface AppDelegate : NSObject <NSUserNotificationCenterDelegate>
#end
#implementation AppDelegate
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}
- (void)throwNotification {
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = #"Some title";
userNotification.informativeText = #"Some text";
printf("trying to throw {%s %s}\n", [[userNotification title] UTF8String], [[userNotification informativeText] UTF8String]);
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
#end
int main (int argc, const char * argv[]) {
AppDelegate *app = [[AppDelegate alloc] init];
[app throwNotification];
return 0;
}
This is compiled with cc -framework Foundation -o app main.m.
Any insight would be appreciated!
The problem is that in order to display a notification, you need to have a proper bundle identifier.
We're going to take a bit of the code from here, where we wait for the notification to get displayed. We can embed an Info.plist file into the compiled binary, which will accomplish the same thing as the swizzling code in a file called notify.m:
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject<NSUserNotificationCenterDelegate>
#property (nonatomic, assign) BOOL keepRunning;
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *appdel = [[AppDelegate alloc] init];
app.delegate = appdel;
NSUserNotificationCenter *nc = [NSUserNotificationCenter defaultUserNotificationCenter];
nc.delegate = appdel;
appdel.keepRunning = TRUE;
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = #"Some title";
userNotification.informativeText = #"Some text";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
while (appdel.keepRunning) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}
return 0;
}
#implementation AppDelegate
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
{
self.keepRunning = NO;
}
#end
I construct an Info.plist file consisting of the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.finder</string>
</dict>
</plist>
Please use an appropriate bundle identifier, as com.apple.finder will cause all these notifications to appear to come from macOS Finder, which might be confusing to users.
I then compile it using:
clang -o notify -framework Cocoa notify.m -Wl,-sectcreate,_\_TEXT,__info_plist,Info.plist
there's a \ in that build line to avoid the markdown parsing of the underscores, but it's not needed for the actual build command line.

Get "[SceneKit] Error:" when set AVPlayer to SCNMaterial.diffuse.contents

According Apple's docs, SCNMaterial.diffuse.contents can be a AVPlayer.
In this case material appear content of video.
Everything seems ok but I get several sceneKit error logs:
[SceneKit] Error: Could not get pixel buffer (CVPixelBufferRef)
Below is my code. Move [_player play] to viewWillAppear does't help.
- (void)viewDidLoad {
[super viewDidLoad];
self.boxNode = [self.sceneView.scene.rootNode childNodeWithName:#"box" recursively:YES];
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/VRF_SuLie.MP4"];
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
_player = [AVPlayer playerWithPlayerItem:item];
self.boxNode.geometry.firstMaterial.diffuse.contents = _player;
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[_player play];
}
How to avoid this error?

Use core data entity in a countdown timer

I have 2 entities in core data to create countdown timers. Timer has an attribute called timerName and entity Blinds(changed from 'Times') has an attribute called duration.
Entities called
Timer <---->> Blind
and attributes called
timerName <---->> duration
with relationships called
blinds <---->>timer
I need to place the various durations into a countdown timer one at a time. When the first duration reaches 0 the next duration is fetched from core data and that is counted down to zero etc.
I am very new to Objective-C and core data but I know I need a loop and fetch request but don't know where to start. Any code examples would be appreciated. Thanks
EDIT
I have setup a fetchrequest in my model.m
- (NSFetchedResultsController *)frc_newTimer
{
if (_frc_newTimer) return _frc_newTimer;
// Otherwise, create a new frc, and set it as the property (and return it below)
_frc_newTimer = [_cdStack frcWithEntityNamed:#"Timer"
withPredicateFormat:nil
predicateObject:nil
sortDescriptors:#"timerName,YES"
andSectionNameKeyPath:nil];
return _frc_newTimer;
}
Then in my view controller.h
#import <UIKit/UIKit.h>
#import "Timer.h"
#import "Blind.h"
#interface BlindTimerViewController : UIViewController <NSFetchedResultsControllerDelegate>
{
IBOutlet UILabel *lblCountDown;
NSTimer *countdownTimer;
int secondsCount;
}
- (IBAction)StartTimer:(id)sender;
- (IBAction)ResetTimer:(id)sender;
#property (assign, nonatomic) NSInteger currentTimeIndex;
#property (nonatomic, strong) Model *model;
#property (nonatomic, strong) Timer *myTimer;
#end
then in view controller.m
#interface BlindTimerViewController ()
#end
#implementation BlindTimerViewController
#synthesize model = _model;
and
-(void) timerRun
{
secondsCount = secondsCount -1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
lblCountDown.text = timerOutput;
//need to add a label for the next blind in the coredata list and update it while in a loop......
if (secondsCount == 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void) setTimer{
// Configure and load the fetched results controller
self.model.frc_newTimer.delegate = self;
self.model.frc_newTimer.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"timerName LIKE %#", #"Sample Timer"];
//add code to get the first coredata item in the blinds list
secondsCount = 240; // i need to insert the CoreData Blinds HERE
countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
and buttons (yet to be fully sorted) to start actions
- (IBAction)StartTimer:(id)sender
{
[self setTimer];
}
- (IBAction)ResetTimer:(id)sender {
[countdownTimer invalidate];
countdownTimer = nil;
secondsCount = 0;
lblCountDown.text = #"00:00";
}
I'm assuming that you're running the countdown for a known Timer. In this case you don't need a fetch request as you have a relationship from the Timer to its set of Times, we can access it directly:
NSSet *times = self.myTimer.times;
We want to sort it so you can run the durations in some order:
(you might also want to check that the count of times > 0)
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"duration" ascending:YES];
NSArray *orderedTimes = [times sortedArrayUsingDescriptors:#[ sortDescriptor ]];
Next, we're going to need an instance variable to track where we are:
#property (assign, nonatomic) NSInteger currentTimeIndex;
With these parts, you can manage the process, and use an NSTimer to actually do the work. When the timer fires you go back to the time, get and sort the times, increment the index we're using, check the index is in range, get the duration and start the timer.
I'm going to be cheeky and say that if the expiring timer is nil, that means we're starting the process from scratch (it would be better to take the first case out into a specific method):
- (void)timerFired:(NSTimer *)expiringTimer
{
[expiringTimer invalidate];
NSInteger index = (expiringTimer != nil ? (self.currentTimeIndex + 1) : 0);
NSSet *times = self.myTimer.times;
if (times.count < index) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"duration" ascending:YES];
NSArray *orderedTimes = [times sortedArrayUsingDescriptors:#[ sortDescriptor ]];
double duration = [[[orderedTimes objectAtIndex:index] duration] doubleValue];
[NSTimer scheduledTimerWithTimeInterval:duration target:self selector:#selector(timerFired:) userInfo:nil repeats:NO];
} else {
// deal with the error
}
}
Now you can start the countdown with [self timerFired:nil];
You haven't said what you're doing while the timers are running, that could change things quite a bit (like you want to display an update of the time on screen each second)...
If you need to fetch the timer from your Core Data DB, that's where the fetch request comes in:
NSManagedObjectContext *context = <#Managed object context#>;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Timer"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"timerName LIKE %#", #"Sample Timer"]];
NSArray *timers = [context executeFetchRequest:fetchRequest error:nil]; // should really add the error...
Timer *myTimer = nil;
if (timers.count == 1) {
myTimer = [timers lastObject];
} else {
// we didn't find the timer, agh!
}

Accessing a variable in multiple methods

Still a bit new and I am having some issues I was hoping someone could help with. I am trying to load a JSON string coming from my server into a collectionview in iOS6
I can pull in the data using a fetchedData method called from the viewDidLoad method and that part works fine. In the fetchedData method, I break out the JSON data and place it in NSDictionaries and NSArrays and can dump the correct data to the log to see it.
The problem is when I try and use any of the information elsewhere in my code such as get the amount of elements in any of hte arrays to use as a counter to fill the collectionview.
It may be that I am tired but I can't seem to get my head around this part. The declaration of many of the main variables was in the fetchedData method and I thought since the were declared there it could be the reason I could not see them elsewhere so I moved the declaration of the variables to the interface section and was hoping this would make the variables GLOBAL and the fetchedData method continues to work just fine, but nowhere else.
When I put in breaks in the cell definition area I can see in the debugger window the variables come up as empty.
I am not sure what sections of the code you may want to see so let me know and I can post them but maybe someone could give an example of how arrays and dictionary items can be accessed in multiple methods.
To avoid confusion and to expose my hodgepodge of code at this point anyway here is the .m file or at least most of it Please don't rip to hard on the coding style I have been trying anything I could think of and tore it up pretty hard myself and it was late.
#import "ICBCollectionViewController.h"
#import "ICBCollectionViewCell.h"
#import "ICBDetailViewController.h"
#interface ICBCollectionViewController () {
NSDictionary* json;
NSDictionary* title;
NSDictionary* shortDescrip;
NSDictionary* longDescrip;
NSDictionary* price;
NSDictionary* path;
NSDictionary* sKU;
NSDictionary* audiotrack;
NSDictionary* audiotracksize;
NSArray* titles;
NSArray* shortDescription;
NSArray* longDescription;
NSArray* prices;
// NSArray* paths;
NSArray* SKUs;
NSArray* audiotracks;
NSArray* audiotracksizes;
}
#end
/*
#interface NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONURLString:(NSString*)urlAddress;
-(NSData*)toJSON;
#end
#implementation NSDictionary(JSONCategories)
+(NSDictionary*)dictionaryWithContentsOfJSONURLString:(NSString*)urlAddress
{
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString: urlAddress] ];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error != nil) return nil;
return result;
}
-(NSData*)toJSON
{
NSError* error = nil;
id result = [NSJSONSerialization dataWithJSONObject:self options:kNilOptions error:&error];
if (error != nil) return nil;
return result;
}
#end
*/
#implementation ICBCollectionViewController
#synthesize paths;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: imobURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
// Do any additional setup after loading the view.
}
- (void)fetchedData:(NSData *)responseData {
NSError* error;
//parse out the json data
json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
titles = [json objectForKey:#"title"]; //2
shortDescription = [json objectForKey:#"shortD"];
longDescription = [json objectForKey:#"longD"];
prices = [json objectForKey:#"price"];
self.paths = [json objectForKey:#"path"];
SKUs = [json objectForKey:#"SKU"];
audiotracks = [json objectForKey:#"audiotrack"];
audiotracksizes = [json objectForKey:#"audiotracksize"];
NSLog(#"paths: %#", paths); //3
// NSLog(#"shortDescrip: %#", shortDescription);
NSInteger t=7;
// 1) Get the latest loan
title = [titles objectAtIndex:t];
shortDescrip = [shortDescription objectAtIndex:t];
longDescrip = [longDescription objectAtIndex:t];
price = [prices objectAtIndex:t];
path = [paths objectAtIndex:t];
sKU = [SKUs objectAtIndex:t];
audiotrack = [audiotracks objectAtIndex:t];
audiotracksize = [audiotracksizes objectAtIndex:t];
//NSLog(title.count text);
//NSLog(title.allValues);
// 2) Get the data
NSString* Title = [title objectForKey:#"title"];
NSString* ShortDescrip = [shortDescrip objectForKey:#"shortD"];
NSString* LongDescrip = [longDescrip objectForKey:#"longD"];
NSNumber* Price = [price objectForKey:#"price"];
NSString* Path = [path objectForKey:#"path"];
NSString* SKU = [sKU objectForKey:#"SKU"];
NSString* AudioTrack = [audiotrack objectForKey:#"audiotrack"];
NSNumber* AudioTrackSize = [audiotracksize objectForKey:#"audiotracksize"];
/*************************HERE THE DATA EXISTS*******************************/
/******** Path = "XYXYXYXYXYXY" for example ********************************/
// 3) Set the label appropriately
NSLog([NSString stringWithFormat:#"Here is some data: Title: %# Path %# SKU: %# Price: %# Track %# Size %#",Title, Path, SKU, Price, LongDescrip, AudioTrackSize]);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//DetailSegue
if ([segue.identifier isEqualToString:#"DetailSegue"]) {
ICBCollectionViewCell *cell = (ICBCollectionViewCell *)sender;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
ICBDetailViewController *dvc = (ICBDetailViewController *)[segue destinationViewController];
dvc.img = [UIImage imageNamed:#"MusicPlayerGraphic.png"];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
NSLog(#"paths qty = %d",[paths count]);
return 20;
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier=#"Cell";
ICBCollectionViewCell *cell = (ICBCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
// paths = [json objectForKey:#"path"];
NSDictionary* path = [paths objectAtIndex:indexPath.row];
NSString* Path = [path objectForKey:#"path"];
// NSString* Path = [paths objectAtIndex:indexPath.row];
NSLog(#"%d",indexPath.row);
/***********************HERE IT DOES NOT**************************/
/******** Path = "" **********************************************/
NSLog(#"xxx");
NSLog(path);
NSLog(paths);
NSLog(Path);
NSLog(#"ZZZ");
Path=#"deepsleep";
NSLog(#"xxx");
NSLog(Path);
NSLog(#"ZZZ");
// paths = [json objectForKey:#"path"];
// NSString* Path = [path objectForKey:#"path"];
NSString *imagefile = [NSString stringWithFormat:#"https://imobilize.s3.amazonaws.com/glennharrold/data/%#/mid.png", Path];
NSLog(imagefile);
NSURL *url1=[NSURL URLWithString:imagefile];
dispatch_async(kBgQueue, ^{
NSData *data1 = [NSData dataWithContentsOfURL:url1];
cell.imageView.image =[[UIImage alloc]initWithData:data1];
});
return cell;
}
#end
Try breaking out the JSON data and sorting it in the appDelegate. If you declare public variables there #property (nonatomic, strong) NSDictionary *myDict etc., then you can access those variables by importing your appDelegate and using the following code:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSDictionary *newDict = appDelegate.myDict;
Otherwise, you can store the information in a singleton, or in the root view controller. The key is to store your variables in a class that won't be deallocated. Most often, it is a bad idea to use a viewController for that purpose-- they have a tendency to be navigated away from, which deallocates the memory and gets rid of your variables. Google "model-view-controller" for more info.
I found out what the main issue was it the ViewDidLoad method I was using a background activity to get the JSON data from my server and as that process was running the foreground was also being processed and since the rest of the code was based on a value returned when the background process finished the data was actually null so all data based on that single piece were also null and it looked as if it was not available. Once I made the process run in the foreground all the variable started having values.
Thanks for your assistance with this

NSFetchedResultsController delegate methods called infinite number of times upon insertNewObjectForEntityForName:inManagedObjectContext:

[EDIT] I am changing this to more concisely explain what my problem was, after having more precisely pinpointed the issue.
I am working on core data for my app, but am stumped. It hangs in this method every time. No crashes, no logs, no nothing. It just hangs.
- (void)insertNewObject:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
Section *newSection = (Section *)newObject;
newSection.title = #"inserted";
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
I discovered that if I put NSLogs in these two delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
they just keep getting called infinite number of times.
Ok, I figured it out. I was creating an infinite loop.
This delegate method gets called:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
Then this eventually gets called because I called [self.tableView beginUpdates]; in the delegate method.
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Section *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
cell.textLabel.text = object.title;
}
Then this delegate method:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
The the problem is that I was actually changing the NSManagedObject's attributes while it was updating the content
object.title = [NSString stringWithFormat:#"Chapter %i", indexPath.row];
this caused controllerWillChangeContent: to be called once again creating a loop that just goes round and round.

Resources