I have two different view controllers ViewController.m & teacherList.m. I am trying to call the -(NSMutableArray *)getTeachersList method from teacherList.m to have it create an array for me and then be able to use it in ViewController.m. I want to do this just hard coding it and not with sequencing or use of storyboard
ViewController.m
#import "ViewController.h"
#import "teacherList.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize userView;
- (void)viewDidLoad
{
NSMutableArray *hey = [teacherList getTeachersList];;
NSString *hello = [hey objectAtIndex:0];
[self say:hello];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)say:(NSString *)greet{
userView.text = greet;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
teacherList.m File
#import "teacherList.h"
#interface teacherList ()
#end
#implementation teacherList
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSMutableArray *)getTeachersList{
NSURL *website = [NSURL URLWithString:#"http://www2.qcc.mass.edu/facAbsence/default.asp"];
NSURLRequest *request = [NSURLRequest requestWithURL:website];
NSURLResponse* response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//storing data in a string
NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//putting it into an array to be
//able to working wiht string or array
NSArray *newString = [myString componentsSeparatedByString:#"\n"];
NSMutableArray *teacherArray = [[NSMutableArray alloc]init];
NSMutableString *curLineNum;
NSString *curLine;
int i;
for (i = 0; i <[newString count]; i++) {
curLine = [newString objectAtIndex:i];
if ([curLine rangeOfString:#"<strong>"].location != NSNotFound) {
NSScanner *theScanner = [NSScanner scannerWithString:curLine];
[theScanner scanUpToString:#"<strong>" intoString:NULL];
[theScanner setScanLocation: [theScanner scanLocation]+8];
[theScanner scanUpToString:#"</strong>" intoString:&curLineNum];
[teacherArray addObject:curLineNum];
}
}
return teacherArray;
}
#end
Don't put the method getTeachersList in a view controller. Create a singleton object that is a subclass of NSObject. Let's call it TeacherListManager.
Then ask the singleton for the teacher's list.
The call might look like this:
TeacherListManager *theTeacherListManager = [TeacherListManager sharedTeacherListManager];
NSMutableArray *theTeacherList = theTeacherListManager.teachersList;
if (theTeacherList == nil)
//Handle the case where the teacher list hasn't been loaded yet.
Do a search on Objective C singleton design pattern for more information.
Your getTeachersList method is currently written to get the teachers list using synchronous networking calls, which is bad. If anything slows down the network connection then it can hang the UI for up to 2 minutes until the connection times out.
you should rewrite that method to download the data asynchronously.
Related
I am stuck and need some help understanding why this is not working.
I want to be able to download the HTML of a page and then format it to show correctly, the code inside the second class (spriing) will download and display the HTML in a UITextView if it is placed inside the ViewController, however this is breaking the MVC right?
So could anyone tell me why I am getting the out of scope error on the mStringData variable?
My classes are below:
I have one class which is a view controller;
//Class for the download and processing of data from website
#import "FirstViewController.h"
#implementation FirstViewController
// The designated initializer. Override to perform setup that is required before the view is loaded.
//- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
// if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// // Custom initialization
//}
// return self;
//}
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
spriing = [Spriing new];
[spriing downloadData:#"http://www.spriing.co.uk/services/"];
SpriingTxt.text = spriing.mStringData;
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[mRecData release];
[mStringData release];
}
And a separate class;
#import "Spriing.h"
#implementation Spriing
#synthesize mStringData;
#synthesize mRecData;
- (void)downloadData: (NSString*) URL{
mBaseURL = URL;
// Create the request.
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:mBaseURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
mCon=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (mCon)
{
// create var to store data
mRecData = [[NSMutableData data] retain];
}
else
{
// Inform the user that the connection failed.
}
}
//If the connection is reset
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
//reset the data length
[mRecData setLength:0];
}
//Obtaining new data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//Add any newly recieved data to the currently stored data
[mRecData appendData:data];
}
//If something went wrong
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//Release the connection
[mCon release];
//Release the data
[mRecData release];
//Alert the user
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Error!"
message:#"No internet connection!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] autorelease];
[alert show];
[alert release];
}
//When its done
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//NSLog(#"finished");
// Once this method is invoked, "responseData" contains the complete result
self.mStringData = [[[NSString alloc] initWithData:mRecData encoding:NSUTF8StringEncoding] retain];
//NSLog(#"%#", mStringData);
self.mStringData = [self processData:mStringData];
//NSLog(#"%#", mStringData);
//SpriingTxt.text = mStringData;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
//mStringData = nil;
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
[mBaseURL autorelease];
mBaseURL = [[request URL] retain];
return request;
}
-(NSString*) processData: (NSString*) string
{
NSMutableString *html = [NSMutableString stringWithCapacity:[string length]];
NSScanner *scanner = [NSScanner scannerWithString:string];
NSString *tempText = nil;
while (![scanner isAtEnd])
{
[scanner scanUpToString:#"<" intoString:&tempText];
if (tempText != nil)
[html appendString:tempText];
[scanner scanUpToString:#">" intoString:NULL];
if (![scanner isAtEnd])
[scanner setScanLocation:[scanner scanLocation] + 1];
tempText = nil;
}
return html;
}
- (void) dealloc
{
[super dealloc];
//[mStringData release];
}
#end
You are starting an asynchronous request for a URL which will take some time. Although it returns immediately, it doesn't imply that the data has been download. NSURLRequest's delegate will be notified when the data has finished downloading. It is not until then that there is data in mStringData which is probably nil prior to being assigned the downloaded data. So when you do SpriingTxt.text = spriing.mStringData; immediately after an asynchronous request without the data being downloaded, SpriingTxt.text is assigned nil.
To resolve this, you can either make a synchronous request which will block until the data has been downloaded which is generally a bad idea or you can message via delegates or notifications to your view controller when the data of your asynchronous request has been downloaded.
To implement the delegate
Delegates are implemented using protocols. You will create a delegate property in the delegating object which would be Spriing as it will let the delegate know when the string has been downloaded and the view controller will be its delegate as it wants to know when the data is available so that it can update its view. Delegates are usually not retained as most times it is the object that creates them that becomes its delegate. So retaining the delegate would create a retain cycle in such instances. There are lots of tutorials about creating the delegates. A rough implementation would be,
in Spriing.h
#protocol SpriinDelegate;
#interface Spriing:... {
id<SpriingDelegate> delegate;
...
}
#property (nonatomic, assign) id<SpriingDelegate> delegate;
...
#end
#protocol SpriingDelegate
- (void)spriing:(Spriing*)aSpriing didFinishDownloadingString:(NSString*)aString;
#end
in Spriing.m
#implementation Spriing
#synthesize delegate;
...
//When its done
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
...
self.mStringData = [self processData:mStringData];
if ( self.delegate && [self.delegate respondsToSelector:#selector(spriing:didFinishDownloadingString:)]) {
[self.delegate spriing:self didFinishDownloadingString:self.mStringData];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
...
#end
in the view controller,
- (void)viewDidLoad {
[super viewDidLoad];
spriing = [Spriing new];
spriing.delegate = self;
[spriing downloadData:#"http://www.spriing.co.uk/services/"];
}
- (void)spriing:(Spriing*)aSpriing didFinishDownloadingString:(NSString*)aString {
SpriingText.text = aString;
}
...
I am getting memory leak theFileName = [[responseString lastPathComponent]stringByDeletingPathExtension];
theFileName is a global variable. I have synthesized it and
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
// Custom initialization
theFileName = [[NSString alloc] init];
}
return self;
}
- (void)requestFinished:(ASIHTTPRequest *)request{
//internally calls this function
// Use when fetching text data
NSString *responseString = [request responseString];
//NSLog(#"the responsestring for download is:%#",responseString);
theFileName = [[responseString lastPathComponent]stringByDeletingPathExtension];
//NSLog(#"the theFileName for download is:%#",theFileName);
//adds extension .jpg to file name
NSString *jpg=#".jpg";
NSString *addjpg=[theFileName stringByAppendingString:jpg];
//NSLog(#"append %#",addjpg);
}
Released it in dealloc.
-(void)dealloc
{
[thefileName release];
}
}
theFileName = [[responseString lastPathComponent]stringByDeletingPathExtension];
creates a new object for theFileName, which already holds an NSString object. You need to release that old value before, i.e.
[theFileName release];
theFileName = [[responseString lastPathComponent]stringByDeletingPathExtension];
You might consider using a copy (recommended) or retain property for theFilename, and use the dot-syntax in requestFinished:.
Here are a few things that might help.
You're not calling super's dealloc method within self's dealloc. For example,
- (void) dealloc
{
[self.theFileName release];
[super dealloc];
}
You're not using the getters and setters that come with synthesizing a property, and we don't know what property you've used with theFileName. If you've got a retaining property, i.e. a statement like #property (copy) NSString * theFileName then you should use the setter so that you don't trip up on retain counts. For example,
(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
// Custom initialization
NSString * aFileName = [[NSString alloc] init];
[self setTheFileName:aFileName];
[aFileName release];
}
return self;
}
is better.
Hi have a problem with singleton Class in iPhone app.
I have create a simple class for visualization of NSString value.
My problem is generate when i try to stamp a NSString in a textVIew.
I call my methods and the value of string in Singleton class is (invalid) (i have tested it with debug).
Can you help me with the code solution.
my code:
#import "UntitledViewController.h"
#import "SingletonController.h"
#implementation UntitledViewController
#synthesize resetTextEvent;
#synthesize buttonSavePosition;
-(IBAction)stamp{
textEvent.text = [sharedController name];
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedController = [SingletonController sharedSingletonController];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
#import "SingletonController.h"
#implementation SingletonController
#synthesize name;
static SingletonController *sharedController = nil;
+(SingletonController*)sharedSingletonController{
if(sharedController == nil){
sharedController = [[super allocWithZone:NULL] init];
}
return sharedController;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedSingletonController] retain];
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (NSUInteger)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
-(id)init{
self = [super init];
if (self != nil) {
name = [NSString stringWithFormat:#"hello"];
}
return self;
}
-(void) dealloc {
[super dealloc];
}
#end
This line:
name = [NSString stringWithFormat:#"hello"];
is problematic. name refers to an instance variable, not your property. So what's happening is your string is being assigned to name, but it's an autoreleased object. So, at some point in the future, name is released automatically and refers to deallocated memory.
If you've specified the name property as either retain or copy, then either of the following lines will property retain the object:
self.name = [NSString stringWithFormat:#"hello"];
name = [[NSString stringWithFormat:#"hello"] retain];
Does anyone know why xCode's "build and analyze" would report this line as a "possible memory leak"?
goodSound = [[SoundClass alloc] initializeSound:#"Good.wav"];
///// Here are the 4 files in question:
// Sounds.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#interface SoundClass : NSObject
{
SystemSoundID soundHandle;
}
-(id) initializeSound:(NSString *)soundFileName;
-(void) play;
#end
/////////////
// Sounds.m
#import "Sounds.h"
#implementation SoundClass
-(id) initializeSound:(NSString *)soundFileName
{
self = [super init];
NSString *const resourceDir = [[NSBundle mainBundle] resourcePath];
NSString *const fullPath = [resourceDir stringByAppendingPathComponent:soundFileName];
NSURL *const url = [NSURL fileURLWithPath:fullPath];
OSStatus errCode = AudioServicesCreateSystemSoundID((CFURLRef) url, &soundHandle);
if(errCode == 0)
NSLog(#"Loaded sound: %#", soundFileName);
else
NSLog(#"Failed to load sound: %#", soundFileName);
return self;
}
//////////////////////////////
-(void) play
{
AudioServicesPlaySystemSound(soundHandle);
}
/////////////////////////////
-(void) dealloc
{
AudioServicesDisposeSystemSoundID(soundHandle);
[super dealloc];
}
/////////////////////////////
#end
//////////////
// MemTestViewController.h
#import <UIKit/UIKit.h>
#class SoundClass;
#interface MemTestViewController : UIViewController
{
SoundClass *goodSound;
}
-(IBAction) beepButtonClicked:(id)sender;
#end
///////////
// MemTestViewController.m
#import "MemTestViewController.h"
#import "Sounds.h"
#implementation MemTestViewController
- (void)viewDidLoad
{
NSLog(#"view did load: alloc'ing mem for sound class");
// "build and analyze" says this is possibly a memory leak:
goodSound = [[SoundClass alloc] initializeSound:#"Good.wav"];
[super viewDidLoad];
}
-(IBAction) beepButtonClicked:(id)sender
{
NSLog(#"beep button clicked");
[goodSound play];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)dealloc
{
[goodSound release];
[super dealloc];
}
#end
It is a possible memory leak. viewDidLoad may be called any number of times if the view is unloaded, in which case you will leak memory each time. To guard, you can either release the memory in viewDidUnload and set the ivar to nil, or you can simply initialize the sound in viewDidLoad only if the ivar actually is nil.
So:
- (void)viewDidLoad
{
if( !goodSound )
goodSound = [[SoundClass alloc] initializeSound:#"Good.wav"];
[super viewDidLoad];
}
And/or:
- (void)viewDidUnload
{
[goodSound release];
goodSound = nil;
[super viewDidUnload];
}
The static analyzer is right; your code is a possible memory leak. Because static analysis can't prove that MemTestViewController's dealloc method will always be called (doing so would solve the halting problem, afterall), there is no way for the static analyzer to be sure that goodSound will be correctly released.
Modern Objective-C rarely uses direct ivar access accept in init and dealloc methods. You should declare a #property (retain) for your goodSound, even if it's private, and use that elsewhere in your class, including in the viewDidLoad method. The static analyzer should then correctly recognize this pattern and not flag the line as a warning.
On a side note—just in case you're interested—your -[SoundClass initializeSound:] should be named something like -[SoundClass initWithSoundFileName:] to match convention, and should be written with a guard to test for a nil return from [super init]:
-(id)initWithSoundFileName:(NSString*)soundFileName
{
self = [super init];
if(self != nil) {
NSString *const resourceDir = [[NSBundle mainBundle] resourcePath];
NSString *const fullPath = [resourceDir stringByAppendingPathComponent:soundFileName];
NSURL *const url = [NSURL fileURLWithPath:fullPath];
OSStatus errCode = AudioServicesCreateSystemSoundID((CFURLRef) url, &soundHandle);
if(errCode == 0)
NSLog(#"Loaded sound: %#", soundFileName);
else
NSLog(#"Failed to load sound: %#", soundFileName);
}
return self;
}
It's always best to add this guard, especially because you are calling into C code that might not handle nil/NULL as nicely as Objective-C.
Ok, I'm trying to avoid global variables, so I read up on singleton classes.
This is a try to set and read a mutable array, but the result is null.
//Content.h
#interface Content : NSObject {
NSMutableArray *contentArray;
}
+ (Content *) sharedInstance;
- (NSMutableArray *) getArray;
- (void) addArray:(NSMutableArray *)mutableArray;
#end
.
//Content.m
#implementation Content
static Content *_sharedInstance;
+ (Content *) sharedInstance
{
if (!_sharedInstance)
{
_sharedInstance = [[Content alloc] init];
}
return _sharedInstance;
}
- (NSMutableArray *) getArray{
return contentArray;
}
- (void) addArray:(NSMutableArray *)mutableArray{
[contentArray addObject:mutableArray];
}
#end
And in a ViewController I added #import "Content.h", where I try to call this:
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:#"test",#"foo",#"bar",nil];
Content *content = [Content sharedInstance];
[content addArray:mArray];
NSLog(#"contentArray: %#", [content getArray]);
You need to alloc and init the array first. Personally I'd do it in the init method of the content class like so:
-(id)init{
if(self = [super init]){
…the rest of your init code…
contentArray = [[NSMutableArray alloc] init];
}
return self;
}
You never actually alloc/initialise the contentArray array.