Im learning about NSTimer and after reading Docs I made this simple test. My understanding is that when I call viewDidLoad in main, viewDidLoad should then call runTest after 3.0 secs - but it's not working. It compiles fine with no errors but will not load runTest (no NSLog) please help ... thank you.
#import "MyClass.h"
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyClass *trial = [[MyClass alloc]init];
[trial viewDidLoad]; ///// this should call viewDidLoad /////
[pool drain];
return 0;
}
#import <Foundation/Foundation.h>
#interface MyClass : NSObject {
NSTimer *aTimer;}
#property (nonatomic, retain) NSTimer *aTimer;
- (void)viewDidLoad;
- (void)runTest;
#end
#import "MyClass.h"
#implementation MyClass
#synthesize aTimer;
- (void)viewDidLoad {
aTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(runTest) userInfo:nil repeats:NO];}
- (void) runTest {
NSLog(#"Working - runTest has loaded !");
aTimer = nil;
}
#end
It will not work this way. NSTimer requires a runloop, which will be created automatically when you make a normal Cocoa Application not a Foundation command line tool.
Create the timer in the applicationDidFinishLaunching method of the application delegate or in the init of your object if you instantiate it in the main nib.
Related
I have the following defined in a header file:
static NSArray *knownPrinters = nil;
In a test case:
knownPrinters = [NSArray new];
I have a breakpoint before and after that single line and the value of knownPrinters remains nil despite being assigned. There is only the main thread and I running only one unit test.
Am I going crazy or is there a legitimate reason why this would occur?
I defined your global variable by using extern keyword as rmaddy suggested and it is allocated in a unit test under the same conditions you described. Here is the code
// Header file
#import <Foundation/Foundation.h>
extern NSArray *knownPrinters;
#interface DummyObject : NSObject
#end
Here is the implementation file
#import "DummyObject.h"
NSArray *knownPrinters = nil;
#implementation SingletonObject
#end
And test file,
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "DummyObject.h"
#interface SOStaticAssignTests : XCTestCase
#end
#implementation SOStaticAssignTests
- (void)setUp {
[super setUp];
}
- (void)tearDown {
[super tearDown];
}
- (void)testExample {
knownPrinters = [NSArray new];
NSLog(#"For debugging");
}
#end
I hope it solves your problem.
I've developed a Category that gives the NSOperation the ability to be executed in the background at timed intervals. I would really appreciate getting some feedback on this, especially any potential problems with this approach that I'm not thinking of.
Thank you!
Here's the code:
NSOperation+Repeat.h
#import <Foundation/Foundation.h>
#interface NSOperation (repeat)
#property (readonly, nonatomic) NSTimeInterval repeatInterval;
#property (readonly, nonatomic) NSOperationQueue *repeatOperationQueue;
- (void)performUsingOperationQueue:(NSOperationQueue *)operationQueue;
- (void)performAtRepeatingInterval:(NSTimeInterval)interval usingOperationQueue:(NSOperationQueue *)operationQueue;
#end
NSOperation+Repeat.m
#import "NSOperation+repeat.h"
#import <objc/runtime.h>
static char const * const RepeatPropertiesKey = "RepeatProperties";
#implementation NSOperation (repeat)
#dynamic repeatInterval;
#dynamic repeatOperationQueue;
static NSString * RepeatIntervalKey = #"interval";
static NSString * RepeatOperationQueueKey = #"operationQueue";
static NSString * RepeatTimerKey = #"timer";
- (NSMutableDictionary *)repeatProperties {
NSMutableDictionary * properties = objc_getAssociatedObject(self, RepeatPropertiesKey);
if (properties == nil) {
properties = [NSMutableDictionary new];
objc_setAssociatedObject(self, RepeatPropertiesKey, properties, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return properties;
}
- (NSTimeInterval)interval {
NSNumber * interval = [[self repeatProperties] objectForKey:RepeatIntervalKey];
return [interval doubleValue];
}
- (NSOperationQueue *)repeatOperationQueue {
NSOperationQueue * operationQueue = [[self repeatProperties] objectForKey:RepeatOperationQueueKey];
return operationQueue;
}
- (void)performUsingOperationQueue:(NSOperationQueue *)operationQueue {
[operationQueue addOperation:[self copy]];
}
- (void)performAtInterval:(NSTimer *)timer {
[self performUsingOperationQueue:self.repeatOperationQueue];
}
- (void)performAtRepeatingInterval:(NSTimeInterval)interval usingOperationQueue:(NSOperationQueue *)operationQueue {
// Save interval and operationQueue in repeatProperties
[self.repeatProperties setValue:[NSNumber numberWithDouble:interval] forKey:RepeatIntervalKey];
[self.repeatProperties setValue:operationQueue forKey:RepeatOperationQueueKey];
// Create timer to call performAtInterval on self
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:(interval*60)
target:self
selector:#selector(performAtInterval:)
userInfo:nil
repeats:YES];
// Save the timer in repeatProperties
[self.repeatProperties setValue:timer forKey:RepeatTimerKey];
[self performUsingOperationQueue:operationQueue];
}
#end
Here's an example of a NSOperation subclass that can repeat:
MSScheduleImportOperation.h
#import <Foundation/Foundation.h>
#import "NSOperation+Repeat.h"
#interface MSScheduleImportOperation : NSOperation <NSCopying>
#property (readonly, strong, nonatomic) NSString* employeeId;
- (id)initWithEmployeeId:(NSString *)employeeId;
#end
MSScheduleImportOperation.m
#import "MSScheduleImportOperation.h"
#implementation MSScheduleImportOperation
#synthesize employeeId = __employeeId;
- (id)initWithEmployeeId:(NSString *)employeeId {
self = [super init];
__employeeId = [employeeId copy];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
MSScheduleImportOperation* copy = [[MSScheduleImportOperation alloc] initWithEmployeeId:self.employeeId];
return copy;
}
- (void)main
{
...
}
#end
The Apple documentation says:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
So the first problem is that there might be internals that stop it from working. Although, I see you try to get around the problem by making a copy.
This leads us to the other problem is that NSOperation is not advertised to conform to NSCopying.
[operationQueue addOperation:[self copy]];
This line should throw an exception.
Instead of a category on NSOperation where the object copies itself and adds the copy to an NSOperationQueue - it would be simpler to manage this at a higher level. For example:
+[RepeatingOperation operationBlock:(InitOperationBlock)operationBlock
queue:(NSOperationQueue*)queue
interval:(NSTimeInterval)interval];
where InitOperationBlock would be a block where the operation was created and configured.
The main benefit is the API would be harder to mess up. For example in category in the original post, performUsingOperationQueue: will fail silently if you forget to set repeatOperationQueue.
I expect that code to display "New Version" at every 3 seconds but it doesn't.
Car.h
#import <Foundation/Foundation.h>
#interface Car : NSObject
-(void)displayVersion;
#end
Car.m
#import "Car.h"
#implementation Car
-(void)displayVersion
{
NSLog(#"New version");
}
#end
main.c
#import <Foundation/Foundation.h>
#import "Car.h"
int main (int argc, const char * argv[])
{
#autoreleasepool
{
Car *ford = [[Car alloc]init];
[NSTimer scheduledTimerWithTimeInterval:3
target:ford
selector:#selector(displayVersion)
userInfo:nil
repeats:YES];
}
return 0;
}
What is wrong here ?
PS: I hate that "Your post does not have much context to explain the code sections; please explain your scenario more clearly"
I think the code is the best explanation of the problem !
NSTimer relies on a running NSRunLoop to function. Your program terminates immediately after you create and schedule the timer, and an NSRunLoop is never set up anyway. Typically, in a Cocoa app, the call to NSApplicationMain() in main() sets up up the main run loop and begins "spinning" it. You should create a new project using the default Cocoa app template in Xcode, and create your timer in the NSApplicationDelegate's -(void)applicationDidFinishLaunching: method.
I've started cleaning up my app before publication - using "Instruments" Leak analyzer.
I found a leak I can't plug. So I built a simple project to illustrate the problem. Please see code below. I put a button on the view to test fire the procedure "test". It always generates a leak.
First the header and code for an object named "theObj"
#import <Foundation/Foundation.h>
#interface theObj : NSObject {
NSString* theWord;
}
#property (nonatomic,retain) NSString* theWord;
#end
#import "theObj.h"
#implementation theObj
#synthesize theWord;
-(id) initWithObjects: (NSString *) aWord;
{
if (self = [super init]){
self.theWord = aWord;
}
return self;
}
-(void) dealloc{
[theWord release];
[super dealloc];
}
#end
Now the view controller
#import <UIKit/UIKit.h>
#import "theObj.h"
#interface LeakAnObjectViewController : UIViewController {
NSMutableArray* arrObjects;
}
- (IBAction)test;
#end
#import "LeakAnObjectViewController.h"
#implementation LeakAnObjectViewController
- (IBAction)test {
if (arrObjects == nil)
arrObjects = [[NSMutableArray alloc] init];
NSString* aStr = #"first";
[arrObjects addObject:[[theObj alloc] initWithObjects:aStr]];
[arrObjects removeAllObjects];
}
You alloc the object, which means you own it. Then you give it to the array, which means the array owns it as well. Then the array removes it, so you are the only owner. But you don't have a reference to the object anymore, so you can't release it, so it's just leaked.
Someone really needs to learn the rules around memory management. Specifically as it pertains to ownership, etc.
Having some issues with code not executing within the classes I created and thought I initialized and implemented correctly here are all the files. There is a class with an array of another class. Then implemented in the code finally but for some reason none of the NSLog calls seem to execute except the one immediately before [mobdefs createTable] in the main code. All help appreciated...
// Mobdefs.h
#interface Mobdefs : NSObject {
#public NSMutableArray *mobInfo;
}
#property(retain) NSMutableArray *mobInfo;
-(void) createTable;
#end
// Mobdefs.m
#import "Mobdefs.h"
#import "Mobrec.h"
#implementation Mobdefs
#synthesize mobInfo;
- (id) init
{
mobInfo = [[NSMutableArray alloc] init];
return self;
}
-(void) addmobrec
{
MobRec *aNewMobRec = [[MobRec alloc] init];
aNewMobRec.mName=#"newbie";
[mobInfo addObject:aNewMobRec];
[aNewMobRec release];
NSLog(#"MobRec Added\n");
}
-(void) createTable
{
NSLog(#"Populating mob table.\n"); // *** THIS CODE NEVER SEEMS TO GET EXECUTED
}
#end
//main.h
Mobdefs *mobdef;
//main.m
NSLog(#"just before createTable call\n");
[mobdef createTable];
although the createTable code is called in the main the only NSLog output I get is the 'just before createtable...'
It doesn't seem that you have initialized mobdef. Add the following:
mobdef = [[Mobdefs alloc] init];
to your main.m before you invoke the method on it.
Objective-C silently ignore calls on nil, as mobdef would be initialized to initially.
are you allocating and initializing mobdef in main.m?