ASINetworkQueue requests always fails - ios - objective-c

I'm facing a little bit of trouble finding whats wrong with my code, because I'm trying to download several images from different urls and the requests are always failing.
Could you guys give me a little help?
Here is my code:
//
// Chapters.h
//
//
// Created by Nuno Martins on 11/07/18.
// Copyright 2011 WeTouch. All rights reserved.
//
#import <Foundation/Foundation.h>
//#import <GHUnit/GHUnit.h>
#class ASINetworkQueue;
#interface Chapters : NSObject {
NSString * chaptersBaseUrl;
NSMutableArray * chaptersList;
ASINetworkQueue *networkQueue;
}
#property (retain) ASINetworkQueue *networkQueue;
#property (nonatomic, retain) NSString *chaptersBaseUrl;
#property (nonatomic, retain) NSMutableArray *chaptersList;
-(void)downloadChaptersIconsFromUrlArrayToFile:(NSMutableArray *)iconUrls;
#end
//
// Chapters.m
//
//
// Created by Nuno Martins on 11/07/18.
// Copyright 2011 WeTouch. All rights reserved.
//
#import "Chapters.h"
#import "Chapter.h"
#import "PDFDataAgregator.h"
#import "ASIHTTPRequest.h"
#import "ASINetworkQueue.h"
#implementation Chapters
#synthesize chaptersBaseUrl;
#synthesize chaptersList;
#synthesize networkQueue;
- (void)dealloc
{
[networkQueue release];
[super dealloc];
}
-(void)downloadChaptersIconsFromUrlArrayToFile:(NSMutableArray *)iconUrls
{
networkQueue = [[ASINetworkQueue alloc] init];
// Stop anything already in the queue before removing it
[networkQueue cancelAllOperations];
// Creating a new queue each time we use it means we don't have to worry about clearing delegates or resetting progress tracking
[networkQueue setDelegate:self];
[networkQueue setRequestDidFinishSelector:#selector(requestFinished:)];
[networkQueue setRequestDidFailSelector:#selector(requestFailed:)];
[networkQueue setQueueDidFinishSelector:#selector(queueFinished:)];
NSLog(#"Array-> %d", [iconUrls count]);
NSMutableArray *myIcons = [[NSMutableArray alloc] initWithArray:iconUrls];
//Create Chapters Folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [paths objectAtIndex:0];
NSString *newDir = [docDirectory stringByAppendingPathComponent:#"Chapters"];
[[NSFileManager defaultManager] createDirectoryAtPath:newDir withIntermediateDirectories:YES attributes:nil error: NULL];
for(unsigned i = 0; i < [myIcons count]; i++)
{
NSURL *url = [NSURL URLWithString:[myIcons objectAtIndex:i]];
NSString *fileName = [url lastPathComponent];
NSString *filePath = [newDir stringByAppendingPathComponent:fileName];
NSLog(#"Icon File Path: %#",filePath);
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[iconUrls objectAtIndex:i]]];
[request setDownloadDestinationPath:filePath];
//[request setUserInfo:[NSDictionary dictionaryWithObject:#"request1" forKey:#"name"]];
[request setTemporaryFileDownloadPath:[filePath stringByAppendingPathExtension:#"download"]];
[request setAllowResumeForFileDownloads:YES];
[networkQueue addOperation:request];
}
[networkQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([networkQueue requestsCount] == 0) {
// Since this is a retained property, setting it to nil will release it
// This is the safest way to handle releasing things - most of the time you only ever need to release in your accessors
// And if you an Objective-C 2.0 property for the queue (as in this example) the accessor is generated automatically for you
[self setNetworkQueue:nil];
}
//... Handle success
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
NSLog(#"Number of requests in Queue %d", [networkQueue requestsCount]);
if ([networkQueue requestsCount] == 0) {
[self setNetworkQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([networkQueue requestsCount] == 0) {
[self setNetworkQueue:nil];
}
NSLog(#"Queue finished");
}

Well this was a problem related with Bad url format.
I was passing http:/somesite.com/someimage.png instead of passing http://somesite.com/someimage.png
I was missing the / because when I append a BaseUrl string to the filename using stringByAppending path Component it removes one slash of the HTTP://.
Solved now!

Related

Empty UITableViewController with async calls

I am working on using NSURLSession and JSON serialization to fetch content from my site. The async calls and getting the JSON data work perfectly. My issue is, when it comes to displaying the data in the TableviewController, I put an NSLog statement to see if there is data and there is, but that cell.textlable.text never updates. I'm guessing the issue is the threads but I can't figure it out. Can you help?
#interface MainTableViewController :
UITableViewController<LokalModelProtocol>
#property (strong,nonatomic) NSMutableArray* arr;
#end
#implementation MainTableViewController
#synthesize arr;
- (void)viewDidLoad {
[super viewDidLoad];
arr = [[NSMutableArray alloc]init];
LokalModel *lokal = [[LokalModel alloc]init];
lokal.delegate=self;
[lokal downloadItems];
}
-(void)itemsDownloaded:(NSMutableArray *)items
{
arr=items;
//NSLog(#"%#", items);
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Incomplete implementation, return the number of sections
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
#warning Incomplete implementation, return the number of rows
// return 1;
return [arr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mainCell" forIndexPath:indexPath];
PostModel *post = [[PostModel alloc]init];
post =[arr objectAtIndex:indexPath.row];
NSLog(#"%#", post.postTitle); ////this outputs the correct strings///////
cell.textLabel.text =[NSString stringWithFormat:#"%#", post.postTitle];
cell.detailTextLabel.text = post.postTitle;///neither of these do//////
return cell;
}
#end
#protocol LokalModelProtocol <NSObject,NSURLSessionDelegate>
+(void)itemsDownloaded:(NSMutableArray*)items;
#end
#interface LokalModel : NSObject
-(void)downloadItems;
#property (strong, nonatomic) NSMutableData* thedata;
#property (strong, nonatomic) NSString* urlString;
#property (strong, nonatomic) NSURL* theUrl;
#property (strong,nonatomic) id<LokalModelProtocol>delegate;
+(void)parseJson:(NSData*)data;
#end
id<LokalModelProtocol>delegate;
#implementation LokalModel;
#synthesize thedata,urlString,theUrl,delegate;
-(void)downloadItems{
NSURL *theUrl = nil;
static NSString* urlString = #"https://balalatet.com/wp-json/wp/v2/posts";
theUrl=[NSURL URLWithString:urlString];
NSURLSession *currentSession= [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [currentSession dataTaskWithURL:theUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error){
[NSException raise:#"error" format:#"%#",error.localizedDescription];
NSLog(#"error1");
}
else{
NSLog(#"success");
[LokalModel parseJson:data];
}
}];
[task resume];
}
+(void)parseJson:(NSData*)data{
NSArray *jsonResults = [[NSArray alloc]init];
NSError *jsonerror;
jsonResults =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonerror];
if (jsonerror)
[NSException raise:#"json error" format:#"%#",jsonerror.localizedDescription];
NSMutableArray *posts = [[NSMutableArray alloc] init];
NSMutableDictionary *jsonElenent =[NSMutableDictionary dictionary];
for (NSMutableDictionary *d in jsonResults)
{
jsonElenent=d;
PostModel *thePost=[[PostModel alloc]init];
thePost.postId= jsonElenent[#"id"];
thePost.postDate= jsonElenent[#"date"];
thePost.postDategmt= jsonElenent[#"date_gmt"];
thePost.postGuid= jsonElenent[#"guid"];
thePost.postSlug= jsonElenent[#"slug"];
thePost.postStatus= jsonElenent[#"status"];
thePost.postSticky= jsonElenent[#"sticky"];
thePost.postPingStatus= jsonElenent[#"ping_status"];
thePost.postType= jsonElenent[#"type"];
thePost.postCommentStatus= jsonElenent[#"comment_status"];
thePost.postTags= jsonElenent[#"tags"];
thePost.postTitle= jsonElenent[#"title"];
thePost.postTemplate= jsonElenent[#"template"];
thePost.postLink= jsonElenent[#"link"];
thePost.postMeta= jsonElenent[#"meta"];
thePost.postModified= jsonElenent[#"modified"];
thePost.postModifiedgmt= jsonElenent[#"modified_gmt"];
thePost.postFeaturedMedia= jsonElenent[#"featured_media"];
thePost.postFormat= jsonElenent[#"format"];
thePost.postLinks= jsonElenent[#"links"];
thePost.postAuthor= jsonElenent[#"author"];
thePost.postContent= jsonElenent[#"content"];
thePost.postCategory= jsonElenent[#"category"];
thePost.postExcerpt= jsonElenent[#"excerpt"];
NSLog(#"%#", thePost.postTitle);
[posts addObject:thePost];
}
dispatch_async(dispatch_get_main_queue(), ^{
[delegate itemsDownloaded:posts];
});
}
#end
Update
my apologies as part of my debugging info is incorrect. the nslog output did not come from the cellForRowAtIndexPath method. in fact the arr array remains empty because the
(void)itemsDownloaded:(NSMutableArray *)items
is never called. im sure i setup the protocol correctly. any thoughts on why the MainTableViewControllerClass cant get the data?
update
so i realized that i forgot to remove the line
id<LokalModelProtocol>delegate;
that i put right before the #implementation in LokalModel. but now doing so causes an error "unrecognized selector sent to instance" at the line
[delegate itemsDownloaded:posts];
I aslo tried
[self.delegate itemsDownloaded:posts];
but it throws the same exception.
Solved
My protocol method had to be an instance method, and i had it set as a class method.
Before return your cell add try to add this code in cellForRowIndexPath
[cell layoutIfneeded];
I believe you have to add a registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: prior to using dequeueReusableCellWithIdentifier:forIndexPath: (in viewDidLoad for example)
From the documentation: https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecellwithidentifie?language=objc
Important
You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.

Try to change variable in singleton but it stays nullable

Just started programming on objective-c and now i have issue with which can't deal by myself. I'm receiving data from asynchronous request and try to delver it to singleton, but it's not changed.
This is where i'm trying to store my data
Data.h
#import <Foundation/Foundation.h>
#interface Data : NSObject
#property (nonatomic, strong) NSDictionary *products;
-(void)setProducts:(NSDictionary *)value;
#end
Data.m
#import "Data.h"
#implementation Data
+(Data *)sharedInstance
{
static Data *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[Data alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if ( self )
{
_products = [[NSDictionary alloc] init];
}
return self;
}
#end
This is the class, where i'm receiving data from server:
ConnectionService.m
- (void)getProductsWithCompletion:(void (^)(NSDictionary *products))completion
{
NSString *urlString = [NSString stringWithFormat:#"serverurl", [[AppDelegate instance]getUrl]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *getData = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSString *rawJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *value = [rawJson JSONValue];
completion(value);
}];
[getData resume];
}
This is the class where i'm calling request and try to deliver it to singleton:
viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance] products]);//all is working, products contains data
}];
// checking received data
NSDictionary *tmp = [[Data sharedInstance] products];
NSLog(#"tmp: %#", tmp); //now it's null
}
The issue is the fact that the request is asynchronous and things aren't happening in the order you expect:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
// (2)
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);//all is working, products contains data
}];
// (1)
NSDictionary *tmp = [[Data sharedInstance]products];
NSLog(#"tmp: %#", tmp); //now it's null
}
In the code you posted, (1) will happen before (2). That's because (2) is part of the completion block and is set to run once the network request has completed and all the data has been parsed and is ready to use. While that asynchronous request is prepared and run in a background thread, the main thread ((1)) continues and executes before the request has taken place.
To resolve the issue, move your logging into the completion routine, or simply remove (1).
Another way is to use protocol, to notify your completion block is finished.So that you can simply do:
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
if(self.delegate){
[self.delegate myNotifyMethod:products];
}
}];
and your protocol method:
-(void)myNotifyMethod:(NSDictionary *)items{
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);
}
You can declare the protocol as:
#protocol MyProtocol <NSObject>
- (void)myNotifyMethod: (NSDictionary *)items;
#end
and set the delegate property as:
#property (nonatomic, weak) id<MyProtocol> delegate;

Could not delete core data PersistentStore

I want to remove the app PersistentStore completely. I read the following question completely and none of them worked:
Delete/Reset all entries in Core Data?
I have a Model Helper and do the delete task inside it:
#import <CoreData/CoreData.h>
#interface GeneralModel : NSFetchedResultsController
#property (nonatomic, strong) NSManagedObjectContext *context;
#property (nonatomic, strong) NSManagedObjectModel *model;
- (NSString *)storagePath;
- (void)removeStorage;
- (void)removeStorage2;
#end
#implementation GeneralModel
- (instancetype)init
{
self = [super init];
if (self) {
// Read in Model.xcdatamodeld
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc =
[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
// Where does the SQLite file go?
NSString *path = self.storagePath;
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error]) {
#throw [NSException exceptionWithName:#"OpenFailure"
reason:[error localizedDescription]
userInfo:nil];
}
// Create the managed object context
_context = [[NSManagedObjectContext alloc] init];
_context.persistentStoreCoordinator = psc;
}
return self;
}
- (NSString *)storagePath
{
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
// Get one and only document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"model.sqlite"];
}
- (void)removeStorage {
NSPersistentStore *store = [self.context.persistentStoreCoordinator.persistentStores lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
BOOL isRemovePersistentStore = [self.context.persistentStoreCoordinator removePersistentStore:store error:&error];
if (isRemovePersistentStore == NO) {
NSLog(#"NO RemovePersistentStore. Reason: %#", error.localizedFailureReason);
}
BOOL isRemoveItemAtURL = [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
if (isRemoveItemAtURL == NO) {
NSLog(#"NO RemoveItemAtURL. Reason: %#", error.localizedFailureReason);
}
}
- (void)removeStorage2 {
NSError *error;
NSString *storagePath = [self storagePath];
NSDictionary *options = #{NSPersistentStoreUbiquitousContentNameKey: #"model"};
bool removeResult = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:[NSURL URLWithString:storagePath] options:options error:&error];
if (removeResult == NO) {
NSLog(#"Could not remove Storage. Reason: %#", error.localizedFailureReason);
}
}
#end
When I call the "removeStorage" method by the following code:
GeneralModel *model = [[GeneralModel alloc] init];
[model removeStorage];
Result is that No error apear in the console, but it just removed the main storage file: "model.sqlite" and 2 other related files: "model.sqlite-shm" and "model.sqlite-wal" remained.
When I call the "removeStorage2" method by the same code, I see the following text on the console and none of the files deleted:
Could not remove Storage. Reason: (null)
How could I solve this problem?
Check out the video "Straight outta Moscone Center West" for the official introduction of how you should be doing this, and/or consult NSPersistentStoreCoordinator.h from which the following snippet was extracted.
/* delete or truncate the target persistent store in accordance with the store
class's requirements. It is important to pass similar options as
addPersistentStoreWithType: ... SQLite stores will honor file locks, journal
files, journaling modes, and other intricacies. It is not possible to unlink
a database file safely out from underneath another thread or process, so this
API performs a truncation. Other stores will default to using NSFileManager.
*/
- (BOOL)destroyPersistentStoreAtURL:(NSURL *)url
withType:(NSString *)storeType
options:(nullable NSDictionary *)options
error:(NSError**)error NS_AVAILABLE(10_11, 9_0);
/* copy or overwrite the target persistent store in accordance with the store
class's requirements. It is important to pass similar options as
addPersistentStoreWithType: ... SQLite stores will honor file locks, journal
files, journaling modes, and other intricacies. Other stores will default
to using NSFileManager.
*/
- (BOOL)replacePersistentStoreAtURL:(NSURL *)destinationURL
destinationOptions:(nullable NSDictionary *)destinationOptions
withPersistentStoreFromURL:(NSURL *)sourceURL
sourceOptions:(nullable NSDictionary *)sourceOptions
storeType:(NSString *)storeType
error:(NSError**)error NS_AVAILABLE(10_11, 9_0);

Objective C assigning a dictionary to a variable and accessing it

I'm sorry to ask this question again, but I'm still stuck.
I have a city object trying to fetch weather from a weather fetcher object
#interface WeatherFetcher : NSObject {
}
#property (nonatomic, strong) NSMutableDictionary *weatherData;
- (void)fetchWeather:(NSString *)cityName;
- (void)handleNetworkErorr:(NSError *)error;
- (void)handleNetworkResponse:(NSData *)myData;
#end
This is were I assign the value to weatherData
#import "WeatherFetcher.h"
#implementation WeatherFetcher
- (void)fetchWeather:(NSString *)cityName
{
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=";
urlString = [urlString stringByAppendingString:cityName];
urlString = [urlString stringByAppendingString:#",Aus"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
[self handleNetworkErorr:connectionError];
}
else
{
[self handleNetworkResponse:data];
}
}];
}
#pragma mark - Private Failure Methods
- (void)handleNetworkErorr:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Please try again later" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
#pragma mark - Private Success Methods
- (void)handleNetworkResponse:(NSData *)myData
{
//NSMutableDictionary *data = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
//NSLog([jsonArray description]);
// take a look at all elements in the array
for (id element in jsonArray) {
id key = [element description];
id innerArr = [jsonArray objectForKey:key];
NSDictionary *inner = (NSDictionary *)innerArr;
if ([inner conformsToProtocol:#protocol(NSFastEnumeration)]) {
for(id ele in inner) {
if ([ele conformsToProtocol:#protocol(NSFastEnumeration)]) {
NSDictionary *innerInner = (NSDictionary *)ele;
for(id eleEle in innerInner) {
id innerInnerKey = [eleEle description];
[data setObject:[[inner valueForKey:innerInnerKey] description] forKey:[eleEle description]];
}
}
else {
id innerKey = [ele description];
[data setObject:[[inner valueForKey:innerKey] description] forKey:[ele description]];
}
}
}
else {
[data setObject:[inner description] forKey:[element description]];
}
}
self.weatherData = data;
NSLog([self.weatherData description]) **//there is data**
}
#end
However every time I call this from by city object I get nothing back at all.
#import <Foundation/Foundation.h>
#import "WeatherFetcher.h"
#interface City : NSObject {
}
#property (nonatomic, strong) NSString *cityName;
#property (nonatomic, strong) NSString *stateName;
#property (nonatomic, strong) UIImage *cityPicture;
#property (nonatomic, strong) NSString *weather;
#property (nonatomic, strong) NSMutableDictionary *weatherData;
-(NSString *)getWeather;
#end
UI calls getWeather by a button press to get the string value to be displayed on screen
#implementation City {
}
-(NSString *)getWeather {
//return self.weather;
NSString *info = #"";
WeatherFetcher *weatherFetcher = [[WeatherFetcher alloc] init];
[weatherFetcher fetchWeather:self.cityName];
self.weatherData = [weatherFetcher weatherData];
for (id element in self.weatherData) {
info = [info stringByAppendingString:[element description]];
info = [info stringByAppendingString:#"-->"];
info = [info stringByAppendingString:[self.weatherData valueForKey:[element description]]];
info = [info stringByAppendingString:#"\n"];
}
return info;
}
#end
What am I doing wrong here?
getWeather method in the city class gets called when a button is pressed and I'm trying to display this string in a text area. I don't have much experience with Objective C and this is my first app other than Hello World app.
Thank you!
Your WeatherFetcher is asynchronous (sendAsynchronousRequest:) - it sets a task to obtain the data and then returns (usually) before that data has been obtained. So when you try to access the weatherData immediately after the call to fetchWeather: it is not there yet.
You need to redesign your model to handle asynchronicity - getWeather cannot be synchronous. For example you could make fetchWeather: take a completion block to invoke when the data is available and have getWeather pass in a suitable block.

What is the best practices for sharing methods across classes in an app?

I'm still trying to wrap my head around how things should be done in the object-oriented world and I think my problem is that I don't understand how to best utilize encapsulation. Specifically, I have lots of small bits of code that I use in several classes in my project. For example:
+ (NSString *)getFormattedDate;
+ (NSString *)getResultsFilePath;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
These are all 3-5 line methods that I use in more than one class. My standard practice has been to put these snippets into a Utility.inc file and call them when I need them. Is that appropriate in the object-oriented world or should each class be self-contained? And if it's appropriate, would you put the code into a singleton or just a regular class file and [[Utilities alloc] init] in each class where you want to use the methods?
Look into using Categories. For the examples you gave, these are methods related to objects of a particular class that happen to be used in several of your own classes. Categories will allow you to park these often used methods where they can be associated with the common factors.
Create a utitity singelton which will be created only ones and then used by the other classes.
Thanks for the answers. I'm not sure that this the right way to do things, but this is what I've done on the projects I just submitted.
I made two classes, one for Utility methods and one for globals. The methods in the Utilities class are all class methods since they operate on files and constants or globals. Then I made a singleton for global variables. I have all of my global constants in the .pch file. Also in the .pch file I put the following two lines of code so that the utilities and globals are available everywhere.
// File for utilities like file delete, find Documents file
#import "Utilities.h"
#import "Globals.h"
Accessing the methods is straightforward. Here's an example of a call to both methods to generate an HTML header for an email.
NSString *gameNameHeader = [NSString stringWithFormat:#"<p>&nbsp</p><h1>%# Results</h1><h2>%#%#</h2>",GAME_NAME_TITLE,[Utilities formattedClientName], [Utilities formattedDate]];
In case anyone can use it, here is my current version of the code. (Sorry for the formatting-I can't seem to get the wiki to cooperate.)
#interface Utilities : NSObject {
}
+ (NSString *)formattedDate;
+ (NSString *)formattedClientName;
+ (NSString *)cachedResultsFilePath;
+ (NSString *)cachedResultsFileContents;
+ (NSString *)resultsFileName;
+ (NSError *)removeFileFromCache:(NSString *)fileName;
+ (NSString *)applicationCachesDirectory;
+ (NSString *)applicationDocumentsDirectory;
+ (NSString *)applicationLibraryDirectory;
+ (NSError *)copyCachedResultsToFile;
#end
#import "Utilities.h"
#implementation Utilities {
}
+ (NSString *)formattedDate {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSString *todaysDate = [dateFormatter stringFromDate:[NSDate date]];
return todaysDate;
}
+ (NSString *)formattedClientName {
NSString *client = [NSString stringWithFormat:#" "];
if( [Globals sharedInstance].currentClient ) client = [NSString stringWithFormat:#" %# ",[Globals sharedInstance].currentClient];
return client;
}
+ (NSString *)cachedResultsFilePath {
NSString *resultsFilePath = [[self applicationCachesDirectory] stringByAppendingPathComponent:#"Results.txt"];
return resultsFilePath;
}
+ (NSString *)cachedResultsFileContents {
NSStringEncoding encoding; NSError* error = nil;
NSString *resultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
return resultsText;
}
+ (NSString *)resultsFileName {
return [NSString stringWithFormat:#"%# Results%#%#.html",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
}
+ (NSError *)removeFileFromCache:(NSString *)fileName {
NSError *error = nil;
NSFileManager *localFileManager=[[NSFileManager alloc] init];
NSString *fullPath = [NSString stringWithFormat:#"%#/%#", [self applicationCachesDirectory],fileName];
[localFileManager removeItemAtPath: fullPath error:&error ];
return error;
}
+ (NSString *)applicationCachesDirectory {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSString *)applicationLibraryDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
+ (NSError *)copyCachedResultsToFile {
// Grab the header and footer and put it around the cached data
NSStringEncoding encoding; NSError *error = nil;
NSString *htmlHeaderTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_header" ofType:#"html" ];
NSString *htmlHeaderText = [NSString stringWithContentsOfFile:htmlHeaderTextPath usedEncoding:&encoding error:&error];
NSString *cachedResultsText = [NSString stringWithContentsOfFile:[self cachedResultsFilePath] usedEncoding:&encoding error:&error];
// Write the results to a file if there are any
if (cachedResultsText) {
NSString *htmlFooterTextPath = [[NSBundle mainBundle] pathForResource:#"HTML_footer" ofType:#"html" ];
NSString *htmlFooterText = [NSString stringWithContentsOfFile:htmlFooterTextPath usedEncoding:&encoding error:&error];
NSString *gameNameHeader = [NSString stringWithFormat:#"<h1>%# Results for%#%#</h1>",GAME_NAME_TITLE,[self formattedClientName],[self formattedDate] ];
NSString *tempStringP1 = [htmlHeaderText stringByAppendingString:gameNameHeader];
NSString *tempStringP2 = [tempStringP1 stringByAppendingString:cachedResultsText];
NSString *formattedTextForPrinting = [tempStringP2 stringByAppendingString:htmlFooterText];
NSString *resultsFilePath = [ [Utilities applicationDocumentsDirectory] stringByAppendingPathComponent:[self resultsFileName] ];
if ( !([[NSFileManager defaultManager] fileExistsAtPath:resultsFilePath]) ) {
if (! ([[NSFileManager defaultManager] createFileAtPath:resultsFilePath contents:nil attributes:nil]) ) {
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
}
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:resultsFilePath];
[fileHandler writeData:[formattedTextForPrinting dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandler closeFile];
}
return error;
}
#end
Globals in a singleton. Probably not thread-safe, but I don't care right now.
#interface Globals : NSObject {
}
#property (nonatomic, strong) NSString *currentClient;
#property (nonatomic, strong) NSString *showmePict;
#property BOOL checkBoxes;
+ (Globals *)sharedInstance;
- (void)resetClient;
#end
#implementation Globals {
}
static Globals *singleton = nil;
#synthesize currentClient = _currentClient;
#synthesize showmePict = _showmePict;
#synthesize checkBoxes = _checkBoxes;
+(Globals *) sharedInstance {
NSLog (#"sharedInstance of Globals called.");
if (nil != singleton) return singleton;
static dispatch_once_t pred; // lock
dispatch_once(&pred, ^{ // this code is at most once
singleton = [[Globals alloc] init];
});
return singleton;
}
- (void)resetClient {
self.currentClient = nil;
}
#end