I am trying to create a Boolean variable, debugMode, and access it across several different classes. This way I can set it's value once in my ViewController, and will be able to access it in my different classes (subclasses of SKScene) to show framerate, log physics values, etc.
I have read that I need to create an instance of my class? I don't see how that applies in this program.
I am new to objective-c and would greatly appreciate any help! Thank you!
The default solution is a preprocessor define, this is set by default in xcode projects.
So, in the source you can put
#ifdef DEBUG
// code that should only run in Debug Configuration
#endif
So if I get you right you want an instance of a given class that you can use across the whole of your application without losing the state of the class but this should only exist in the DEBUG version of your code?
Ok we can do this using a Singleton Pattern mixed with the #ifdef DEBUG to determine whether in debug mode or not.
DebugManager.h
// Our Debug Class that we have just made up.
// Singleton class
#interface DebugManager : NSObject
// Some properties that we want on the class
#property (nonatomic, strong) NSString *debugName;
#property (nonatomic, readonly) NSDate *instanceCreatedOn;
// a method for us to get the shared instance of our class
+ (id)sharedDebugManager;
#end
DebugManager.m
#import "DebugManager.h"
#implementation DebugManager
// Create a shared instance of our class unless one exists already
+ (id)sharedDebugManager
{
static DebugManager *sharedDebugManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedDebugManager = [[self alloc] init];
});
return sharedDebugManager;
}
- (id)init
{
if (self = [super init]) {
debugName = #"Debug Instance";
instanceCreatedOn = [NSDate new];
}
return self;
}
#end
Now that we have a Singleton class setup we can add the below line to our *-Prefix.pch which will give us an instance of our DebugManager class that we can use throughout our app.
#ifdef DEBUG
DebugManager *manager = [DebugManager sharedDebugManager];
#endif
Just remember that when you want to use your manager instance you will need to wrap it in #ifdef DEBUG because when running in production this will not see the instance of manager anymore. So make sure you do:
#ifdef DEBUG
NSLog(#"The DebugManagers instance name is %#", [manager debugName]);
#endif
Don't forget to add your preprocessor macro in xcode under your Build Settings follow this answer to find out how to do that
If you have any questions just ask below.
Related
Now I am developing an iOS application which works like this:
User scans QR code,
App searches for a specific key - > value,
it gives out a value to the user.
Currently I have two ViewControllers - the main and "value" ViewController, which is inherited from main. The problem is that if I create NSDictionary in main VC it is not visible in "value" VC. Main VC gives only the string (QR code, the key) through the segue. So, the value VC has to search for key and display the value.
What I ask is some kind of global variable or one DataSource visible across the whole app. Of course, I can implement NSDictionary initialisation inside value ViewDidLoad method and it will work, but this is not the point. New modules are to be added there and the variable has to be global. I googled a lot and got the idea that singleton pattern can be helpful here. I tried to implement it, but no idea how to do. Do I need it, or it is too complex for this kind of DataSource?
Thank you!
The basic idea is, you will still need to #include the header file of the place where this dictionary will be. The solution that Naveen proposes means that you will be including the header for the app delegate wherever you want to access it. Whether to use the app delegate for this purpose or not is kinda grayish. Some people often do this, some say its a bad use of it.
The singleton approach means that you will create a class, that will always contain the same information since the init method will return object that was previously created.
For the singleton aproach, imagine I have a database manager class. So in the header of this class (the DatabaseManagerSingleton.h) ill have this:
#interface DatabaseManager : NSObject
+ (DatabaseManager*)sharedInstance;
// Your dictionary
#property (nonatomic,strong) NSMutableDictionary* someDictionary;
The implementation will look like this: (check how "sharedInstance" initializes the object)
#implementation DatabaseManager
#pragma mark - Singleton Methods
+ (DatabaseManager*)sharedInstance {
static DatabaseManager *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init
{
self = [super init];
if (self != nil)
{
// Custom initialization
_someDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
Now, a VERY important thing is that, any place you want to use this object should first include the header:
EDIT: To use it in your code:
1) add the header
#import "DatabaseManager.h"
2) initialize the object
DatabaseManager *databaseManager = [DatabaseManager sharedInstance];
3) do whatever you need
// Initialize the dictionary
databaseManager.someDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"OBJECT",#"someKey", nil]; // In this case the object is just a NSString.
// Access
[databaseManager.someDictionary objectForKey:#"someKey"];
Put as a property on Appdelegate
#property (nonatomic,strong) NSDictionary * sharedData;
Access anywhere like
NSDictionary *sharedData= ((APPDelegate *) [UIApplication sharedApplication].delegate).sharedData;
Being inspired by the solution to this question I tried using the same approach with XCTest.
I've set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES'.
XCode still doesn't produce any gcda files. Anyone have any ideas of how to solve this?
Code:
#import <XCTest/XCTestLog.h>
#interface VATestObserver : XCTestLog
#end
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (mainSuite == suite) {
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
}
#end
In AppDelegate.m I have:
extern void __gcov_flush(void);
- (void)applicationWillTerminate:(UIApplication *)application {
__gcov_flush();
}
EDIT: I edited the question to reflect the current status (without the red herrings).
EDIT To make it work I had to add the all the files under test to the test target including VATestObserver.
AppDelegate.m
#ifdef DEBUG
+ (void)initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
VATestObserver.m
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface VATestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation VATestObserver
+ (void)initialize {
[[NSUserDefaults standardUserDefaults] setValue:#"VATestObserver"
forKey:XCTestObserverClassKey];
[super initialize];
}
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
Update 1:
After reading a bit more about this, 2 things have now become clear to me (emphasis added):
Tests and the tested application are compiled separately. Tests are actually injected into the running application, so the __gcov_flush() must be called inside the application not inside the tests.
— Xcode5 Code Coverage (from cmd-line for CI builds) - Stack Overflow
and,
Again: Injection is complex. Your take away should be: Don’t add .m files from your app to your test target. You’ll get unexpected behavior.
— Testing View Controllers – #1 – Lighter View Controllers
The code below was changed to reflect these two insights…
Update 2:
Added information on how to make this work for static libraries, as requested by #MdaG in the comments. The main changes for libraries is that:
We can flush directly from the -stopObserving method because there isn't a separate app where to inject the tests.
We must register the observer in the +load method because by the time the +initialize is called (when the class is first accessed from the test suite) it's already too late for XCTest to pick it up.
Solution
The other answers here have helped me immensely in setting up code coverage in my project. While exploring them, I believe I've managed to simplify the code for the fix quite a bit.
Considering either one of:
ExampleApp.xcodeproj created from scratch as an "Empty Application"
ExampleLibrary.xcodeproj created as an independent "Cocoa Touch Static Library"
These were the steps I took to enable Code Coverage generation in Xcode 5:
Create the GcovTestObserver.m file with the following code, inside the ExampleAppTests group:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
When doing a library, since there is no app to call, the flush can be invoked directly from the observer. In that case, add the file to the ExampleLibraryTests group with this code instead:
#import <XCTest/XCTestObserver.h>
#interface GcovTestObserver : XCTestObserver
#end
#implementation GcovTestObserver
- (void)stopObserving
{
[super stopObserving];
extern void __gcov_flush(void);
__gcov_flush();
}
#end
To register the test observer class, add the following code to the #implementation section of either one of:
ExampleAppDelegate.m file, inside the ExampleApp group
ExampleLibrary.m file, inside the ExampleLibrary group
#ifdef DEBUG
+ (void)load {
[[NSUserDefaults standardUserDefaults] setValue:#"XCTestLog,GcovTestObserver"
forKey:#"XCTestObserverClass"];
}
#endif
Previously, this answer suggested to use the +initialize method (and you can still do that in case of Apps) but it doesn't work for libraries…
In the case of a library, the +initialize will probably be executed only when the tests invoke the library code for the first time, and by then it's already too late to register the observer. Using the +load method, the observer registration in always done in time, regardless of which scenario.
In the case of Apps, add the following code to the #implementation section of the ExampleAppDelegate.m file, inside the ExampleApp group, to flush the coverage files on exiting the app:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
Enable Generate Test Coverage Files and Instrument Program Flow by setting them to YES in the project build settings (for both the "Example" and "Example Tests" targets).
To do this in an easy and consistent way, I've added a Debug.xcconfig file associated with the project's "Debug" configuration, with the following declarations:
GCC_GENERATE_TEST_COVERAGE_FILES = YES
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES
Make sure all the project's .m files are also included in the "Compile Sources" build phase of the "Example Tests" target. Don't do this: app code belongs to the app target, test code belongs to the test target!
After running the tests for your project, you'l be able to find the generated coverage files for the Example.xcodeproj in here:
cd ~/Library/Developer/Xcode/DerivedData/
find ./Example-* -name *.gcda
Notes
Step 1
The method declaration inside XCTestObserver.h indicates:
/*! Sent immediately after running tests to inform the observer that it's time
to stop observing test progress. Subclasses can override this method, but
they must invoke super's implementation. */
- (void) stopObserving;
Step 2
2.a)
By creating and registering a separate XCTestObserver subclass, we avoid having to interfere directly with the default XCTestLog class.
The constant key declaration inside XCTestObserver.h suggests just that:
/*! Setting the XCTestObserverClass user default to the name of a subclass of
XCTestObserver indicates that XCTest should use that subclass for reporting
test results rather than the default, XCTestLog. You can specify multiple
subclasses of XCTestObserver by specifying a comma between each one, for
example #"XCTestLog,FooObserver". */
XCT_EXPORT NSString * const XCTestObserverClassKey;
2.b)
Even though it's common practice to use if(self == [ExampleAppDelegate class]) around the code inside +initialize [Note: it's now using +load], I find it easier to omit it in this particular case: no need to adjust to the correct class name when doing copy & paste.
Also, the protection against running the code twice isn't really necessary here: this is not included in the release builds, and even if we subclass ExampleAppDelegate there is no problem in running this code more than one.
2.c)
In the case of libraries, the first hint of the problem came from this code comment in the Google Toolbox for Mac project: GTMCodeCovereageApp.m
+ (void)load {
// Using defines and strings so that we don't have to link in XCTest here.
// Must set defaults here. If we set them in XCTest we are too late
// for the observer registration.
// (...)
And as the NSObject Class Reference indicates:
initialize — Initializes the class before it receives its first message
load — Invoked whenever a class or category is added to the Objective-C runtime
The “EmptyLibrary” project
In case someone tries to replicate this process by creating their own "EmptyLibrary" project, please bear in mind that you need to invoke the library code from the default emtpy tests somehow.
If the main library class is not invoked from the tests, the compiler will try to be smart and it won't add it to the runtime (since it's not being called anywhere), so the +load method doesn't get called.
You can simply invoke some harmless method (as Apple suggests in their Coding Guidelines for Cocoa # Class Initialization). For example:
- (void)testExample
{
[ExampleLibrary self];
}
Because you have to create a new XCTestSuiteRun instance in the testSuiteDidStop method, you are not going to get the proper results on an == check. Instead of depending on instance equality, we used a simple counter and call flush when it hits zero, which it will when the top-level XCTestSuite finishes executing. There are probably more clever ways to do this.
First, we had to set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' in both the Test and main app targets.
#import <XCTest/XCTestLog.h>
#import <XCTest/XCTestSuiteRun.h>
#import <XCTest/XCTest.h>
// Workaround for XCode 5 bug where __gcov_flush is not called properly when Test Coverage flags are set
#interface GCovrTestObserver : XCTestLog
#end
#ifdef DEBUG
extern void __gcov_flush(void);
#endif
static NSUInteger sTestCounter = 0;
static id mainSuite = nil;
#implementation GCovrTestObserver
- (void)testSuiteDidStart:(XCTestRun *)testRun {
[super testSuiteDidStart:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
sTestCounter++;
if (mainSuite == nil) {
mainSuite = suite;
}
}
- (void)testSuiteDidStop:(XCTestRun *)testRun {
sTestCounter--;
[super testSuiteDidStop:testRun];
XCTestSuiteRun *suite = [[XCTestSuiteRun alloc] init];
[suite addTestRun:testRun];
if (sTestCounter == 0) {
__gcov_flush();
}
}
#end
There was an additional step required, because the +initialize call was not being made on the observer when included in the Test target.
In the AppDelegate, add the following:
#ifdef DEBUG
+(void) initialize {
if([self class] == [AppDelegate class]) {
[[NSUserDefaults standardUserDefaults] setValue:#"GCovrTestObserver"
forKey:#"XCTestObserverClass"];
}
}
#endif
Here's another solution that avoids having to edit your AppDelegate
UIApplication+Instrumented.m (put this in your main target):
#implementation UIApplication (Instrumented)
#ifdef DEBUG
+ (void)load
{
NSString* key = #"XCTestObserverClass";
NSString* observers = [[NSUserDefaults standardUserDefaults] stringForKey:key];
observers = [NSString stringWithFormat:#"%#,%#", observers, #"XCTCoverageFlusher"];
[[NSUserDefaults standardUserDefaults] setValue:observers forKey:key];
}
- (void)xtc_gcov_flush
{
extern void __gcov_flush(void);
__gcov_flush();
}
#endif
#end
XCTCoverageFlusher.m (put this in your test target):
#interface XCTCoverageFlusher : XCTestObserver
#end
#implementation XCTCoverageFlusher
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
SEL coverageFlusher = #selector(xtc_gcov_flush);
if ([application respondsToSelector:coverageFlusher])
{
objc_msgSend(application, coverageFlusher);
}
[application.delegate applicationWillTerminate:application];
}
#end
- (void)applicationWillTerminate:(UIApplication*)application must be defined in your application delegate, not in the observer class.
I didn't have any library problems. "-lgov" is not needed and you don't have to add any libraries. Coverage is supported directly by LLVM compiler.
The process for this is a little different if you're using Specta, since it does its own swizzling. The following is working for me:
Test Bundle:
#interface MyReporter : SPTNestedReporter // keeps the default reporter style
#end
#implementation MyReporter
- (void) stopObserving
{
[super stopObserving];
UIApplication* application = [UIApplication sharedApplication];
[application.delegate applicationWillTerminate:application];
}
#end
AppDelegate:
- (void)applicationWillTerminate:(UIApplication *)application
{
#ifdef DEBUG
extern void __gcov_flush(void);
__gcov_flush();
#endif
}
You then need to enable your custom reporter subclass by setting the environmental variable SPECTA_REPORTER_CLASS to MyReporter in the Run section of your main scheme.
GCOV Flush in -(void)applicationWillTerminate didn't work for me, I think because my App is running in Background.
I also set 'Generate Test Coverage Files=YES' and 'Instrument Program Flow=YES' but no gcda-Files.
Then I executed "__gcov_flush()" in -(void)tearDown from the TestClass, which gave me gcda-Files for my TestClass ;)
Then I created the following Function in my AppDelegate:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
+(void)gcovFlush;
#end
#implementation AppDelegate
+(void)gcovFlush{
extern void __gcov_flush(void);
__gcov_flush();
NSLog(#"%s - GCOV FLUSH!", __PRETTY_FUNCTION__);
}
#end
I called [AppDelegate gcovFlush] in my -(void)tearDown and voilá, there are my gcda Files ;)
I hope this helps, bye Chris
In my application there are lot of view controller in some view controller some variables are there which i want to use in other classes .my variable is not present in application delegate file so i can i make it global to use every where in my application?
In my opinion, how about using singleton pattern? So when you want to use the variables of that class, just get instance and then use the variables.
#interface MySingletonViewController : UIViewController
{
//here your variables
int globalVariables;
}
#property (nonatomic, assign) int globalVariables;
+ (MySingletonViewController *)sharedSingleton;
#end
#implementation MySingletonViewController
#synthesize globalVariables;
static MySingletonViewController *sharedSingleton = nil;
+ (MySingletonViewController *)sharedSingleton
{
#synchronized(self)
{
if (sharedSingleton == nil)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
#end
UIViewController is class actually, so we can do this way : ) Hope this helpful.
Sure you can, but using global variables through entire app is definitely broken architecture design.
As Objective-C based on C, you can define variable (in you case - pointer to class) in any *.m file outside implementation part as:
MyVeryOwnClass *g_MyVeryOwnClassPointer = nil;
And access it as:
extern MyVeryOwnClass *g_MyVeryOwnClassPointer;
/* do some operations with your pointer here*/
Or move extern declaration to header file.
PS: You can use singletons. They are not the best solution, but better then using raw variable.
I've recently updated to Xcode 4.3.2 and found that I can now declare private instance variables inside #implementation block like so:
#interface TestClass : NSObject
#property (nonatomic, copy) NSString *testProp;
#end
#implementation TestClass {
NSString *_testPropStore;
}
- (NSString *)testProp { return _testPropStore; }
- (void)setTestProp:(NSString *)testProp { _testPropStore = [testProp copy]; }
- (id)init {
if (self = [super init]) {
_testPropStore = nil;
}
return self;
}
#end
Notice the NSString *_testPropStore line inside #implementation brace block.
I've also tested with the following code:
TestClass *c1 = [[TestClass alloc] init];
TestClass *c2 = [[TestClass alloc] init];
c1.testProp = #"Hello";
c2.testProp = #"World";
NSAssert(c1.testProp == c2.testProp, #"It's working!");
Which seems to work fine. (That is, the app crashes with the "It's working" message at the NSAssert line.)
So is this a new feature of Objective-C for declaring private instance variables? Since I discovered this by accident, I would love to know if it is just for declaring private instance variables or will there be any side effects that I'm not aware of?
I couldn't find any relevant document since most questions of such type with the word private just ended up with answers on how to declare them on a private extension category which is different.
It's for real, it's the new way,* it's great, and, yes, it's in the docs. The Objective-C Programming Language, which is as close as we get to having an actual spec for the language, has the following to say:
The definition of a class is structured very much like its declaration. It begins with an #implementation directive and ends with the #end directive. In addition, the class may declare instance variables in braces after the #implementation directive:
#implementation ClassName
{
// Instance variable declarations.
}
// Method definitions.
#end
There's also a historical note a little ways back from that link, addressing the fact that we used to have to declare ivars in the interface block:
Historically, the interface required declarations of a class’s instance variables, the data structures that are part of each instance of the class. ... Instance variables represent an implementation detail, and should typically not be accessed outside of the class itself. Moreover, you can declare them in the implementation block or synthesize them using declared properties. Typically you should not, therefore, declare instance variables in the public interface and so you should omit the braces.
For the question of privacy, yes, these variables are truly private -- they act like ivars declared in the interface with the #private directive. This means that subclasses can't access them, by default. Their visibility can be changed, however, using either #protected or (if necessary for some bizarre reason) #public:
#interface Stuper : NSObject
#end
#implementation Stuper
{
#protected
NSString * sangfroid;
}
#end
#interface Stub : Stuper
- (void)setSangfroid: (NSString *)newSangfroid;
#end
#implementation Stub
- (void)setSangfroid: (NSString *)newSangfroid {
sangfroid = [newSangfroid copy];
}
*You have to use clang > 3.0, I believe, so that's just a few months ago as of this posting. GCC won't do it.
It's pretty new and it's valid as long as any compiler you need supports it.
It's great for minimizing your dependencies -- the includes and forwards may largely be in the implementation file. For this reason, and if all the compilers you use support it, then it's a better place for your ivars than in the #interface block.
A final caveat is that our current (may.2.2012) debuggers do not support this.
I am new to Objective-C and i was trying out a sample program in Threads from the book "Learn Objective-C for java developers".
I am getting 6 errors on the function definition.
Its with errors.
Is there any link that gives good threading example for beginners like me.
Thread2.m
#import <Foundation/Foundation.h>
#import "Process.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Process* process = [Process new];
NSProgressIndicator* indicator = [NSProgressIndicator new];
Heartbeat* heartbeat = [Heartbeat startHeartbeatProcess:process withIndicator:indicator];
[heartbeat stop];
[pool drain];
return 0;
}
Process.h
#import <Foundation/Foundation.h>
#interface Process : NSObject {
}
#property double progress;
#end
#interface NSProgressIndicator : NSObject {
}
#end
#interface Heartbeat : NSObject {
#public
NSThread* thread;
NSProgressIndicator* indicator;
Process* monitor;
}
+(Heartbeat*)startHeartbeatProcess:(id)process withIndicator:(NSProgressIndicator*)progress;
-(void)stop;
-(void)heartbeatThread:(id)ignored;
-(void)updateIndicator;
#end
Process.m
#import "Process.h"
#implementation Process
+(Heartbeat*)startHeartbeatProcess:(id)process withIndicator:(NSProgressIndicator*)progress {
Heartbeat* heartbeat = [Heartbeat new];
heartbeat->monitor = process;
heartbeat->indicator = progress;
heartbeat->thread = [[NSThread alloc]initWithTarget:heartbeat selector:(heartbeatThread:) object:nil]; //'heartbeatThread' undeclared
[heartbeat->thread start];
return heartbeat;
}
-(void)stop {
[thread cancel]; //thread undeclared
}
-(void)heartbeatThread:(id)ignored {
while (![thread isCancelled]) {
//thread undeclared
[self performSelectorOnMainThread:#selector(updateIndicator) withObject:nil waitUntilDone:YES];
[NSThread sleepForTimeInterval:0.5];
}
}
-(void)updateIndicator {
[indicator setDoubleValue:monitor.progress];
}
#end
Could not find the setDoubleValue method in the class NSProgressIndicator.
Could not find the setDoubleValue method in the class NSProgressIndicator
For this one, that's because NSProgressIndicator is part of AppKit (the Cocoa GUI library), and you're only linking against Foundation (which is the non-GUI stuff). It seems in your code you've attempted to define an interface for NSProgressIndicator yourself, but you haven't declared any methods on it — that's why it's complaining about not being able to find the setDoubleValue method.
What should you do about it? Well, if you're wanting to use Cocoa's GUI stuff, you need to structure your program in the way Cocoa's GUI system expects. In Xcode, if you create a new Cocoa application it should give you a sample project to build on. In particular, your main() function should contain return NSApplicationMain(argc, (const char **) argv);, which handles starting a run loop to receive events.
If you just want to learn about threading, it may be better to abandon trying to get GUI stuff in the same program, and adapt your code to just print stuff to the console instead.
I find it hard to believe that this is an example from a book, since it seems fairly fundamentally broken!
Other errors I found when I tried running it:
Expected ')' before ':' token
This is on the line heartbeat->thread = [[NSThread alloc]initWithTarget:heartbeat selector:(heartbeatThread:) object:nil];.
The problem there is the syntax for declaring a selector: instead of just saying selector:(heartbeatThread:), you need to say selector:#selector(heartbeatThread:).
'thread' undeclared (first use in this function'
In your header file, you claimed that the class Heartbeat has a method called stop. (That is, you defined -(void)stop; in the #interface section for the Heartbeat class).
However, you implemented that method in the #implementation section for the Process class.
You'd make it easier for yourself if you had one pair of .h and .m files per class, rather than trying to cram multiple class definitions into a single pair of files. That way you could make sure you were putting the implementation of the stop method in the correct class's .m file.
property 'progress' requires method '-progress' to be defined - use #synthesize, #dynamic or provide a method implementation
In the implementation for process you defined an #property called progress. If you define a property, you either have to write getters and setters for it yourself, or write #synthesize progress within your implementation. Doing the latter is equivalent to Objective-C generating your getters and setters automatically at runtime.
thread is not a member of the Process class; it belongs to the Heartbeat class. You have to define a member in the Process class to keep a reference on the Heartbeat instance so you can call methods on its thread member.