This is my first app. It doesn't crash the simulator just brings you back to the home screen. This isn't my first cocoa program (I'm not a pro at cocoa either)just my first iPhone.
It crashes when you press the letsPlayButton, which runs the -(IBAction)letsPlay:(id)sender; method.
Help me! Im so lost with what's wrong!
.H
#import <UIKit/UIKit.h>
#interface BrainiacViewController : UIViewController {
IBOutlet UILabel *theQuestion;
IBOutlet UILabel *theScore;
IBOutlet UILabel *theLives;
IBOutlet UIButton *answerOne;
IBOutlet UIButton *answerTwo;
IBOutlet UIButton *answerThree;
IBOutlet UIButton *answerFour;
IBOutlet UIButton *letsPlayButton;
NSString *questionNumber;
NSInteger myScore;
NSInteger myLives;
NSInteger usersAnswer;
NSArray *theQuiz;
//quiz dictionarys
NSMutableDictionary *question, *rightAnswer, *wrongOne, *wrongTwo, *wrongThree;
}
-(IBAction)buttonOne:(id)sender;
-(IBAction)buttonTwo:(id)sender;
-(IBAction)buttonThree:(id)sender;
-(IBAction)buttonFour:(id)sender;
-(IBAction)letsPlay:(id)sender;
-(void)checkAnswer;
-(void)askQuestion;
-(void)updateScore;
-(void)loadQuiz;
#end
.M
#import "BrainiacViewController.h"
#implementation BrainiacViewController
-(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];
}
-(IBAction)buttonOne:(id)sender{
usersAnswer = 1;
}
-(IBAction)buttonTwo:(id)sender{
usersAnswer = 2;
}
-(IBAction)buttonThree:(id)sender{
usersAnswer = 3;
}
-(IBAction)buttonFour:(id)sender{
usersAnswer = 4;
}
-(IBAction)letsPlay:(id)sender{
//un hide answer buttons
[answerOne setHidden:NO];
[answerTwo setHidden:NO];
[answerThree setHidden:NO];
[answerFour setHidden:NO];
//hide the lets play button
[letsPlayButton setHidden:YES];
//assign values to variables
myScore = 0;
myLives = 3;
questionNumber = 0;
//ask question and start game
[self askQuestion];
}
-(void)checkAnswer{
[question objectForKey:questionNumber];
[rightAnswer objectForKey:questionNumber];
[wrongOne objectForKey:questionNumber];
[wrongTwo objectForKey:questionNumber];
[wrongThree objectForKey:questionNumber];
}
-(void)askQuestion{
//create array to mix up the possible answers
NSMutableArray *possibleAnswers = [[NSMutableArray alloc] init];
[possibleAnswers addObject:[rightAnswer objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongOne objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongTwo objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongThree objectForKey:questionNumber] ];
//set labels/buttons for question
theQuestion.text = [question objectForKey:questionNumber];
//user picks a answer button and sets a value to usersAnswer
questionNumber = questionNumber + 1;
[self checkAnswer];
}
-(void)updateScore{
}
-(void)loadQuiz{ //assigns dictionary values
//*KEY* defines question number
[question setObject:#"Which is NOT a starwars movie?" forKey:#"1"];
[rightAnswer setObject:#"Attack of the Jedi" forKey:#"1"];
[wrongOne setObject:#"A New Hope" forKey:#"1"];
[wrongTwo setObject:#"The Phantom Menace" forKey:#"1"];
[wrongThree setObject:#"Attack of the Clones" forKey:#"1"];
[question setObject:#"In what state is the majority of Yellow Stone National Park in?" forKey:#"2"];
[rightAnswer setObject:#"Wyoming" forKey:#"2"];
[wrongOne setObject:#"California" forKey:#"2"];
[wrongTwo setObject:#"Ohio" forKey:#"2"];
[wrongThree setObject:#"Nebraska" forKey:#"2"];
[question setObject:#"In the US, what household pet is the most common?" forKey:#"2"];
[rightAnswer setObject:#"Dogs" forKey:#"2"];
[wrongOne setObject:#"Cats" forKey:#"2"];
[wrongTwo setObject:#"Hamsters" forKey:#"2"];
[wrongThree setObject:#"Komodo Dragons" forKey:#"2"];
[question setObject:#"A plane is traveling 2675 miles every 5 hours. How fast is the plane going in Miles per Hour?" forKey:#"3"];
[rightAnswer setObject:#"535 Mph" forKey:#"3"];
[wrongOne setObject:#"325 Mph" forKey:#"3"];
[wrongTwo setObject:#"535 Mph" forKey:#"3"];
[wrongThree setObject:#"420 Mph" forKey:#"3"];
}
#end
appdelagate.h
#import <UIKit/UIKit.h>
#class BrainiacViewController;
#interface BrainiacAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
BrainiacViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet BrainiacViewController *viewController;
#end
appdelegate.m
#import "BrainiacAppDelegate.h"
#import "BrainiacViewController.h"
#implementation BrainiacAppDelegate
#synthesize window;
#synthesize viewController;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
/*
Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background.
*/
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application {
/*
Called when the application is about to terminate.
See also applicationDidEnterBackground:.
*/
}
#pragma mark -
#pragma mark Memory management
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
/*
Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
*/
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
#end
I tried it again and checked the console and the error was this..I dont know what that means.
[Session started at 2010-07-11 12:24:40 -0400.]
2010-07-11 12:24:45.729 Brainiac[46764:207] -[NSCFString letsPlay:]: unrecognized selector sent to instance 0x5944f00
2010-07-11 12:24:45.733 Brainiac[46764:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString letsPlay:]: unrecognized selector sent to instance 0x5944f00'
*** Call stack at first throw:
(
0 CoreFoundation 0x02395919 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x024e35de objc_exception_throw + 47
2 CoreFoundation 0x0239742b -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x02307116 ___forwarding___ + 966
4 CoreFoundation 0x02306cd2 _CF_forwarding_prep_0 + 50
5 UIKit 0x002b9e14 -[UIApplication sendAction:to:from:forEvent:] + 119
6 UIKit 0x003436c8 -[UIControl sendAction:to:forEvent:] + 67
7 UIKit 0x00345b4a -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 527
8 UIKit 0x003446f7 -[UIControl touchesEnded:withEvent:] + 458
9 UIKit 0x002dd2ff -[UIWindow _sendTouchesForEvent:] + 567
10 UIKit 0x002bf1ec -[UIApplication sendEvent:] + 447
11 UIKit 0x002c3ac4 _UIApplicationHandleEvent + 7495
12 GraphicsServices 0x02bfbafa PurpleEventCallback + 1578
13 CoreFoundation 0x02376dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
14 CoreFoundation 0x022d7737 __CFRunLoopDoSource1 + 215
15 CoreFoundation 0x022d49c3 __CFRunLoopRun + 979
16 CoreFoundation 0x022d4280 CFRunLoopRunSpecific + 208
17 CoreFoundation 0x022d41a1 CFRunLoopRunInMode + 97
18 GraphicsServices 0x02bfa2c8 GSEventRunModal + 217
19 GraphicsServices 0x02bfa38d GSEventRun + 115
20 UIKit 0x002c7b58 UIApplicationMain + 1160
21 Brainiac 0x00002790 main + 102
22 Brainiac 0x00002721 start + 53
)
terminate called after throwing an instance of 'NSException'
I tried it again and checked the console and the error was this..I dont know what that means.
2010-07-11 12:24:45.729 Brainiac[46764:207] -[NSCFString letsPlay:]: unrecognized selector sent to instance 0x5944f00
You sent a message (letsPlay:) to an object that does not respond to that message (an NSString, shown here as NSCFString).
This sort of thing usually happens because you didn't properly hold on to the object that you meant to send letsPlay: to, so it got deallocated. An NSString later got allocated at the same address, so everything that had been holding the pointer to the old object (and thinks it still is) is now holding the pointer to the NSString. Same pointer, different object. Then, when one of those would-be owners tries to send the message to the object it thinks it still has… boom.
The fix is to check your ownerships and make sure you're retaining (or creating using alloc and not autoreleasing) what you mean to own.
On a related note:
NSMutableArray *possibleAnswers = [[NSMutableArray alloc] init];
[possibleAnswers addObject:[rightAnswer objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongOne objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongTwo objectForKey:questionNumber] ];
[possibleAnswers addObject:[wrongThree objectForKey:questionNumber] ];
If you mean for this to be a local variable, you must release it by the end of the method, or you will leak this array.
If you mean to own this object, you should move it to an instance variable, and drop the local variable, and continue creating the object as you are doing now.
Required reading: The Memory Management Programming Guide for Cocoa.
You need to 'box' the integer questionNumber in a NSNumber object, like this:
theQuestion.text = [question objectForKey:[NSNumber numberWithInt:questionNumber]];
Or, you can use the more appropriate NSArray indexer:
theQuestion.text = [question objectAtIndex:questionNumber];
Related
Related to this other question: I have the need to gather information about what is the current active application, on macOS.
The linked QA answer provide a mechanism to get alerted (event) when the active application changes, but it crashes when run on a separated thread:
FocusDetector::AppFocus focus;
focus.run();
//std::thread threadListener(&FocusDetector::AppFocus::run, &focus); //Does not works
//if (threadListener.joinable())
//{
// threadListener.join();
//}
.
*** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /xxxxxxx/NSUndoManager.m:363
2020-11-24 08:54:41.758 focus_detection[81935:18248374] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3006cb57 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff68eb35bf objc_exception_throw + 48
2 CoreFoundation 0x00007fff30095d08 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff32787e9d -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 Foundation 0x00007fff326c45ee +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 440
5 AppKit 0x00007fff2d25165c -[NSApplication run] + 864
6 focus_detection 0x0000000104b1a010 _ZN13FocusDetector8AppFocus3runEv + 128
7 focus_detection 0x0000000104b19547 _ZNSt3__1L8__invokeIMN13FocusDetector8AppFocusEFvvEPS2_JEvEEDTcldsdeclsr3std3__1E7forwardIT0_Efp0_Efp_spclsr3std3__1E7forwardIT1_Efp1_EEEOT_OS6_DpOS7_ + 119
8 focus_detection 0x0000000104b1944e _ZNSt3__1L16__thread_executeINS_10unique_ptrINS_15__thread_structENS_14default_deleteIS2_EEEEMN13FocusDetector8AppFocusEFvvEJPS7_EJLm2EEEEvRNS_5tupleIJT_T0_DpT1_EEENS_15__tuple_indicesIJXspT2_EEEE + 62
9 focus_detection 0x0000000104b18c66 _ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEMN13FocusDetector8AppFocusEFvvEPS8_EEEEEPvSD_ + 118
10 libsystem_pthread.dylib 0x00007fff6a260109 _pthread_start + 148
11 libsystem_pthread.dylib 0x00007fff6a25bb8b thread_start + 15
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Abort trap: 6
This is obviously related with NSApplication, for which the documentation state:
Every app uses a single instance of NSApplication to control the main event loop
In consequence, I am looking for another way to listen on events, which is not restricted to the main event-loop ( or main thread.
Intuitively, it should be possible to get information about the current application with focus, in a separated thread.
I have no idea how to approach this problem, sorry for not providing much research. I did researched on internet for "NSNotification not in main thread" and other similar sentences, but without success.
Question:
How to listen on activeAppDidChange NSNotification outside the main thread?
Place Observers to the following Notifications below and let them invoke your methods. NotificationCenter are singleton for good reason, while you can of course, create your own. [NSWorkspace sharedWorkspace] is a singleton i guess, and [[NSWorkspace sharedWorkspace] notificationCenter] as named - the notification center to observe. The sending object to expect is nil in your case, because you don't know which object to observe more specific.
#import <AppKit/AppKit.h>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:#selector(activeAppDidChange:)
name:NSWorkspaceDidActivateApplicationNotification
object:nil
];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:#selector(activeAppDidTerminate:)
name:NSWorkspaceDidTerminateApplicationNotification
object:nil
];
// id<NSObject> myObserver;
_myObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
addObserverForName:NSWorkspaceDidHideApplicationNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
// do stuff in block
NSRunningApplication *app = note.userInfo[NSWorkspaceApplicationKey];
NSLog(#"%u %# %ld",
app.processIdentifier,
app.bundleIdentifier,
(long)app.activationPolicy
);
}
];
}
-(void)activeAppDidChange:(NSNotification *)note {
NSLog(#"%#",note.userInfo.debugDescription);
}
-(void)activeAppDidTerminate:(NSNotification *)note {
NSLog(#"%#",note.userInfo.debugDescription);
}
Just proof you can receive Notifications on Threads that are not mainThread.
// ThreadManagerExample.h
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface ThreadManagerExample : NSObject
#property (nonatomic, readonly) BOOL started;
#property (nonatomic, readonly) uint64_t looptime;
-(void)start;
-(void)stop;
#end
NS_ASSUME_NONNULL_END
// ThreadManagerExample.m
#import "ThreadManagerExample.h"
static const double kThreadPriority = 1.0;
#interface ReceivingThread : NSThread
#property (nonatomic, weak) ThreadManagerExample * threadManager;
#end
#interface ThreadManagerExample ()
#property (nonatomic, strong) ReceivingThread *thread;
#property (nonatomic, readwrite) BOOL started;
#property (nonatomic, readwrite) uint64_t looptime;
#end
#implementation ThreadManagerExample
-(void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(startThread) object:nil];
if ( _thread ) {
[_thread cancel];
while ( !_thread.isFinished ) {
[NSThread sleepForTimeInterval:0.01];
}
}
}
-(instancetype)init {
if ( !(self = [super init]) ) return nil;
_looptime = 1000000000; // 1 sec
return self;
}
-(void)startThread {
if ( !_thread) {
self.thread = [ReceivingThread new];
_thread.threadManager = self;
_thread.name=#"ReceivingThread";
[_thread setThreadPriority:kThreadPriority];
[_thread start];
}
}
-(void)start {
if ( !_thread ) {
#synchronized ( self ) {
self.started = YES;
}
[self performSelector:#selector(startThread) withObject:nil afterDelay:0.0];
}
}
-(void)stop {
#synchronized ( self ) {
self.started = NO;
}
if ( _thread ) {
[_thread cancel];
self.thread = nil;
}
}
#end
#implementation ReceivingThread {
BOOL somethinghappend;
NSString *oldBundleIdentifier;
NSString *lastbundleIdentifier;
pid_t oldPID;
pid_t focusedPID;
}
-(instancetype)init {
if (!(self=[super init])) return nil;
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(mimi:) name:NSWorkspaceDidActivateApplicationNotification object:nil];
return self;
}
-(void)mimi:(NSNotification*)note {
NSRunningApplication *app = note.userInfo[NSWorkspaceApplicationKey];
lastbundleIdentifier = app.bundleIdentifier;
focusedPID = app.processIdentifier;
if (![oldBundleIdentifier isEqualToString:lastbundleIdentifier]) {
somethinghappend = YES;
}
oldBundleIdentifier = lastbundleIdentifier;
oldPID = focusedPID;
}
-(void)main {
[NSThread setThreadPriority:kThreadPriority];
while ( !self.isCancelled ) {
uint64_t nextLoop = 0;
#synchronized ( _threadManager ) {
if (somethinghappend) {
NSLog(#"%# %u",lastbundleIdentifier, focusedPID);
somethinghappend = NO;
}
uint64_t now = mach_absolute_time();
nextLoop = now + _threadManager.looptime;
}
mach_wait_until(nextLoop);
}
}
#end
and instance and starting is done like..
if (!_manager) _manager = [[ThreadManagerExample alloc] init];
[_manager start];
stopping
[_manager stop];
_manager = nil; // if needed
So as looptime is set to 1 sec it will check each second if the last received bundleIdentifier was changed. You could extend the -(void)mimi: method by checking if the bundleIdentifier differs from [[NSBundle mainBundle] bundleIdentifier] to know if you became active yourself and ignore that when needed.
Edit from Apple Docs
NSRunningApplication is thread safe, in that its properties are returned atomically. However, it is still subject to the main run loop policy described above. If you access an instance of NSRunningApplication from a background thread, be aware that its time-varying properties may change from under you as the main run loop runs (or not).
I am having an issue with an autorelease pool crash on shutdown which I've reduced to the small test case below that simply creates a window and then closes it. The crash disappears if the -fobjc-arc flag is taken away. Running on OS X 10.8.2, Clang 4.1 (421.11.66). I am hoping that someone with a more in depth understanding of ARC can enlighten me as to what is going on here - running with zombie objects on shows that it is the NSWindow object that is getting released too many times, or not retained enough, but I thought ARC was meant to take care of all this?
The stack trace is:
0 libobjc.A.dylib 0x00007fff8fad4f5e objc_release + 14
1 libobjc.A.dylib 0x00007fff8fad4230 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 464
2 com.apple.CoreFoundation 0x00007fff99d22342 _CFAutoreleasePoolPop + 34
3 com.apple.Foundation 0x00007fff936e84fa -[NSAutoreleasePool drain] + 154
4 com.apple.Foundation 0x00007fff936effa0 _NSAppleEventManagerGenericHandler + 125
5 com.apple.AE 0x00007fff93a5ab48 aeDispatchAppleEvent(AEDesc const*, AEDesc*, unsigned int, unsigned char*) + 307
6 com.apple.AE 0x00007fff93a5a9a9 dispatchEventAndSendReply(AEDesc const*, AEDesc*) + 37
7 com.apple.AE 0x00007fff93a5a869 aeProcessAppleEvent + 318
8 com.apple.HIToolbox 0x00007fff8d0c18e9 AEProcessAppleEvent + 100
9 com.apple.AppKit 0x00007fff8e95c916 _DPSNextEvent + 1456
10 com.apple.AppKit 0x00007fff8e95bed2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
11 com.apple.AppKit 0x00007fff8e953283 -[NSApplication run] + 517
12 Test 0x00000001070e1d68 main + 152 (Test.mm:31)
13 libdyld.dylib 0x00007fff8e10c7e1 start + 1
And the code for the test case is:
// Tested with `clang++ -fobjc-arc -g Test.mm -framework Cocoa -o Test && ./Test`
#import <Cocoa/Cocoa.h>
#interface MyApplication : NSApplication
#end
#implementation MyApplication
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
NSWindow * window = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 100, 100)
styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES];
[window close];
[super stop: self];
}
#end
int main()
{
#autoreleasepool
{
const ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
SetFrontProcess(&psn);
[MyApplication sharedApplication];
[NSApp setDelegate: NSApp];
[NSApp run];
}
return 0;
}
Using Instruments' Zombies profile showed that the NSWindow object gets put into the autorelease pool by the call to close:. ARC then correctly ends up with a reference count of zero once applicationDidFinishLaunching: completes and destroys the NSWindow instance. However, the autorelease pool still knows about the now-defunct NSWindow instance and then tries to release it on shutdown, causing the crash.
Autoreleasing objects being managed under ARC seems like a bad idea unless the autorelease pool holds zeroing weak references to its objects, which it doesn't seem to be doing here.
The problem can be prevented by telling the window not to autorelease on close by adding [window setReleasedWhenClosed: NO];.
ARC will only retain a newly created object if you assign it to a variable that has an extent greater than the current scope. Otherwise, the object would be leaked.
In your example, you're creating a new instance of NSWindow by calling alloc, which temporarily transfers ownership to the local variable, window. Since that variable ceases to exist at the end of method, ARC has to insert a release call to avoid leaking the window instance. As a result, the instance is no longer owned by anything, and therefore deallocates itself.
To fix this, declare a property of type NSWindow with strong semantics, and pass the window instance to the property setter method (or directly assign it to the corresponding instance variable -- either will work).
EDIT
To be clear, what you need to do is add a declared property (or at least an instance variable) to MyApplication, for example
#interface MyApplication : NSApplication
#property (strong, nonatomic) NSWindow *window;
#end
Then, in your implementation of applicationDidFinishLaunching, set the property:
#implementation MyApplication
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(100, 100, 100, 100)
styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:YES];
self.window = window;
...
}
#end
With ARC you need to use properties for things that are suppose to stay afloat for longer.
To abstract private properties, use anonymous category in your .m file.
There's more info in http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
This fixes it:
#interface MyApplication : NSApplication
#property NSWindow *window;
#end
#implementation MyApplication
- (void) applicationDidFinishLaunching: (NSNotification *) note
{
self.window = [[NSWindow alloc] initWithContentRect: NSMakeRect(100, 100, 100, 100)
styleMask: NSTitledWindowMask backing: NSBackingStoreBuffered defer: YES];
[self.window close];
[super stop: self];
}
#end
int main()
{
#autoreleasepool
{
const ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
SetFrontProcess(&psn);
[MyApplication sharedApplication];
[NSApp setDelegate: NSApp];
[NSApp run];
}
return 0;
}
Hope that helps.
I am defining a custom protocol:
#protocol NGSAuthProvider <NSObject>
- (BOOL)isReady;
- (BOOL)isSessionValid;
- (void)login;
- (void)logout;
- (NSString *)accessToken;
- (BOOL)handleOpenURL:(NSURL *)url;
#end
I want to have different providers. So one is a Facebook provider:
#interface NGSFacebookAuthProvider : NSObject <NGSAuthProvider>
#end
#interface NGSFacebookAuthProvider () <FBSessionDelegate>
#property BOOL ready;
#property(nonatomic, retain) Facebook *facebook;
#property(nonatomic, retain) NSArray *permissions;
#end
#implementation NGSFacebookAuthProvider
//Implementation of fbLogin, fbLogout and the methods in NGSAuthProvider that forward calls to self.facebook
- (NSString *)accessToken
{
return [self.facebook accessToken];
}
#end
I setup Objection to bind from my class to the protocol.
#interface NGSObjectionModule : ObjectionModule
#end
#implementation NGSObjectionModule
- (void)configure
{
self bind:[NGSFacebookAuthProvider class] toProtocol:#protocol(NGSAuthProvider)];
}
#end
I setup the Global Injector:
#implementation NGSAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ObjectionModule *module = [[NGSObjectionModule alloc] init];
ObjectionInjector *injector = [Objection createInjector:module];
[module release];
[Objection setGlobalInjector:injector];
}
I am using this in my RootViewController like this:
#interface RootViewController : UITableViewController
#end
#interface RootViewController ()
#property(nonatomic, retain) id<NGSAuthProvider> authProvider;
#end
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.authProvider = [[Objection globalInjector] getObject:#protocol(NGSAuthProvider)];
}
- (void)processConfig {
NSString *token = [self.authProvider accessToken];
// use the access token
}
#end
When I run this, I get the following error:
2011-07-26 21:46:10.544 ngs[6133:b603] +[NGSFacebookAuthProvider accessToken]: unrecognized selector sent to class 0x30c7c
2011-07-26 21:46:10.546 ngs[6133:b603] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NGSFacebookAuthProvider accessToken]: unrecognized selector sent to class 0x30c7c'
*** Call stack at first throw:
(
0 CoreFoundation 0x00e825a9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x00fd6313 objc_exception_throw + 44
2 CoreFoundation 0x00e8417b +[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x00df3966 ___forwarding___ + 966
4 CoreFoundation 0x00df3522 _CF_forwarding_prep_0 + 50
5 ngs 0x0000324b -[RootViewController processConfig] + 731
6 ngs 0x000041a2 __33-[RootViewController viewDidLoad]_block_invoke_0 + 50
So my class implements the protocol. It successfully is assigned to id<NGSAuthProvider>. I tried contructing [[NGSFacebookAuthProvider alloc] init] explicitly instead of using Objection and it still crashed.
I tried looping through the selectors using objc/runtime.h methods to see which selectors are there but the only thing it finds is initialize:
- (void)logSelectors:(id)obj
{
int i=0;
unsigned int mc = 0;
Method * mlist = class_copyMethodList(object_getClass([obj class]), &mc);
NSLog(#"%d methods", mc);
for(i=0;i<mc;i++)
NSLog(#"Method no #%d: %s", i, sel_getName(method_getName(mlist[i])));
free(mlist);
}
This has to be something simple that I am missing. I use protocols defined by Cocoa and don't have this issue. I have defined custom protocols for UIViewController-based delegates without issue.
I am stumped as to why Obj-C runtime can't find my methods! If I change id<NGSAuthProvider> to NGSFacebookAuthProvider and construct it explicitly then it all works.
SOLUTION:
The problem was I misunderstood how to bind to a protocol. One way that works is:
#implementation NGSObjectionModule
- (void)configure
{
[self bind:[[[NGSFacebookAuthProvider alloc] init] autorelease] toProtocol:#protocol(NGSAuthProvider)];
}
#end
What I would like to do is bind a class to a protocol, but Objection probably wouldn't know the initializer to call?
The issue is you're trying to use the static class method (denoted because you've got a +) instead of a method run on an instance of your object (which is what you've written it as, with a -)
Chris,
You could use Objection's meta class bindings that allows you to bind a meta class to a protocol and invoke class methods against the meta class instance.
For example,
[self bindMetaClass:[NGSFacebookAuthProvider class] toProtocol:#protocol(NGSAuthProvider)];
But only if you want to use class methods. Otherwise you can use the protocol bindings against a share instance.
I also faced the same issue as I was using
[self bind:[MyClass class] toProtocol:#protocol(MyProtocol)];
The right way for it to work is
[self bindClass:[MyClass class] toProtocol:#protocol(MyProtocol)];
I think this sketch will add clarity to what I am trying to achieve.
Essentially, I want to create iPhone app that allows user to select a schoolDistrict from a tableview and the app will slide that view over and reveal the next tableview full of schools. When they select a school, the next view displays the teachers, finally, when they select a teacher, it will display the courses taught by that teacher.
So, I am trying to construct some sample data for my viewtable "drilldown" and I am struggling to populate such a hierarchical data structure.
I'm hoping the sketch below helps.
https://skitch.com/aibkwik/rqu41/photo-feb-13-10-30-15-pm.jpg
original post follows:
UPDATED: I am trying to alloc an array that is deeply embedded in an object hierarchy. The top object instance is call "thisYear" and it has an array in it called "courses". "courses" also contains, among other things, an array called scoreCatetories. How would you initialize and add an object to the "scoreCategories" array? I've tried numerous things, including:
I'm trying all manner of combinations such as the one below - nothing is working.
...
[[thisYear courses] scoreCategories] = [[NSMutableArray alloc] init];
[[[thisYear courses] scoreCategories] addObject:myNewObj];
...
I'm trying to load some sample data - the code below is located in the
didFinishLaunchingWithOptions function of the appDelegate.
Here is the code snippet that is causing me grief.
Please see the .h files, further down in this post.
dGradeURin *thisYear;
thisYear = [[dGradeURin alloc] init];
[thisYear.howManyTerms initWithInteger: 4];
thisYear.gradeURin = #"Freshman";
//this paragraph compiles and runs fine
dCourse *tmpcourse;
tmpcourse = [[dCourse alloc] init];
tmpcourse.cName =#"Computer Science";
tmpcourse.school =#"Freedom High";
thisYear.courses = [[NSMutableArray alloc] init];
[thisYear.courses addObject:tmpcourse];
dScoringCategory *tmpSC;
tmpSC = [[dScoringCategory alloc] init];
tmpSC.name = #"Quizzes";
//these two lines are not working
[[thisYear courses] setScoreCategories:[[[NSMutableArray alloc] init] autorelease]];
[[[thisYear courses] scoreCategories] addObject:tmpSC];
//both the above lines compile with a warning: NSMutableArray may not respond to -setScoreCategories
// when I run the program, the first line causes crash with an exception... See stack trace at far bottom
Here are the .h header file snippets for each interface object definitions, in essence...
#interface dGradeURin : NSObject {
NSNumber *howManyTerms;
NSString *gradeURin;
NSMutableArray *courses;
}
#property (retain, nonatomic) NSNumber *howManyTerms;
#property (retain, nonatomic) NSString *gradeURin;
#property (retain, nonatomic) NSMutableArray *courses;
#interface dCourse : NSObject {
NSString *cName;
NSString *teacher;
NSString *school;
NSString *school_term;
NSString *gradingMethod;
NSNumber *whichterm;
NSMutableArray *scoreCategories;
}
#property (retain, nonatomic) NSString *cName;
#property (retain, nonatomic) NSString *school;
#property (retain, nonatomic) NSMutableArray *scoreCategories;
#interface dScoringCategory : NSObject {
NSString *name;
NSMutableArray *scores;
}
#property (retain, nonatomic) NSString *name;
#property (retain, nonatomic) NSMutableArray *scores;
#interface dScore : NSObject {
NSNumber *score;
NSDate *scoreDate;
NSString *description;
}
#property (retain, nonatomic) NSDate *scoreDate;
#property (retain, nonatomic) NSNumber *score;
#property (retain, nonatomic) NSString *description;
Here is the stack trace
2011-02-13 21:49:43.559 GradeJanx[86526:40b] -[__NSArrayM setScoreCategories:]: unrecognized selector sent to instance 0x4b76660
2011-02-13 21:49:43.561 GradeJanx[86526:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM setScoreCategories:]: unrecognized selector sent to instance 0x4b76660'
*** Call stack at first throw:
(
0 CoreFoundation 0x00db0be9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x00f055c2 objc_exception_throw + 47
2 CoreFoundation 0x00db26fb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x00d22366 ___forwarding___ + 966
4 CoreFoundation 0x00d21f22 _CF_forwarding_prep_0 + 50
5 GradeJanx 0x00002c50 -[AppDelegate_iPhone application:didFinishLaunchingWithOptions:] + 881
6 UIKit 0x002ba1fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163
7 UIKit 0x002bc55e -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 439
8 UIKit 0x002c6db2 -[UIApplication handleEvent:withNewEvent:] + 1533
9 UIKit 0x002bf202 -[UIApplication sendEvent:] + 71
10 UIKit 0x002c4732 _UIApplicationHandleEvent + 7576
11 GraphicsServices 0x016e6a36 PurpleEventCallback + 1550
12 CoreFoundation 0x00d92064 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
13 CoreFoundation 0x00cf26f7 __CFRunLoopDoSource1 + 215
14 CoreFoundation 0x00cef983 __CFRunLoopRun + 979
15 CoreFoundation 0x00cef240 CFRunLoopRunSpecific + 208
16 CoreFoundation 0x00cef161 CFRunLoopRunInMode + 97
17 UIKit 0x002bbfa8 -[UIApplication _run] + 636
18 UIKit 0x002c842e UIApplicationMain + 1160
19 GradeJanx 0x000028bc main + 102
20 GradeJanx 0x0000284d start + 53
Okay, this is all sorts of messed up, so let's just step back a little bit. An array is an indexed set of objects. An index is a non-negative integer that represents the location of an object. Each object is accessible in the array by an index. You can't do [[thisYear courses] scoreCategories] and expect anything to work, because you haven't actually provided an index to the object in the array. You could do something like [[thisYear courses] objectAtIndex:0], which would return the first element in the array. You could also do [[thisYear courses] objectAtIndex:1], which would return the second element in the array.
Whenever you insert an element into an NSMutableArray, it inserts it as the last object in the array. For instance, if the NSMutableArray you're inserting your object into doesn't have any elements contained within it, the index of the element you're inserting is 0. If it have 1 element in it, then the index of the element you're inserting would have an index of 1.
The code you're probably looking for is this:
NSMutableArray *scoreCategories = [NSMutableArray array];
[scoreCategories addObject:myNewObj];
[[thisYear courses] addObject:scoreCategories];
However, there are a lot of other problems with doing things this way. Before proceeding any further, I'd recommend you spend some time with a good Objective-C book.
Update
Okay, let's take another look at this. This is pretty tough without the context in which this code appears, but we'll try anyway.
dGradeURin *thisYear = [[[dGradeURin alloc] init] autorelease];
thisYear.howManyTerms = [NSNumber numberWithInteger:4];
thisYear.gradeURin = #"Freshman";
NSMutableArray *courses = [NSMutableArray array];
//set up a temporary course
dCourse *temporaryCourse = [[[dCourse alloc] init] autorelease];
temporaryCourse.cName = #"Computer Science";
temporaryCourse.school = #"Freedom High";
temporaryCourse.scoreCategories = [NSMutableArray array];
dScoringCategory *temporaryScoringCategory = [[[dScoringCategory alloc] init] autorelease];
temporaryScoringCategory.name = #"Quizzes";
[temporaryCourse.scoreCategories addObject:temporaryScoringCategory];
[courses addObject: temporaryCourse];
thisYear.courses = courses;
This first line creates an autoreleased object. That means that at some point, the memory this object is taken up will be freed for use somewhere else. If you don't free the memory of an object, then it will keep taking up memory, whether you ever use it or not. For more about this (and you really need to understand it to use Objective-C), check out Apple's documentation.
A couple of other things. If this is homework, and it looks like it might be from the content, then be sure you tag it as homework. I don't mind helping you here because it looks like most of your problems are syntactic, but don't forget next time.
Second, I don't know where you got those classes, but assuming you're using the interfaces correctly, they appear to be poorly designed. There's a lot of understanding required to make things work well rather than just work. I'd still recommend reading that book I linked above if you really want to learn this stuff.
If it's a declared property (with the #property syntax), then you can use the dot notation to set the property value:
// This assumes that scoreCategories has either the 'retain' or 'copy' property;
// if it instead uses 'assign', then you'll need to retain this array.
[thisYear courses].scoreCategories = [NSMutableArray arrayWithObjects:myNewObj, nil];
If it's not a declared property, just access it using the standard arrow notation:
[thisYear courses]->scoreCategories = [[NSMutableArray alloc] initWithObjects:myNewObj, nil];
Not the cleanest approach, but anyway:
[[thisYear courses] setScoreCategories:[[[NSMutableArray alloc] init] autorelease]];
[[[thisYear courses] scoreCategories] addObject:myNewObj];
Something like
for (Course *course in thisYear.courses) {
course.scoreCategories = [[[NSMutableArray alloc] init] autorelease];
}
I hate being that guy, but this kind of deep inspection (borrowed term there, hah!) smells like bad design to me.
Here's what i would do:
Have a property courses of the thisYear object that get's populated when needed. courses is an array that contains Course objects. The Course object has a property called scoreCategories and also a Method addScoreCategory. So for your code above, you'd do a
Course * currentCourse = [[thisYear courses] objectAtIndex:0]; // assuming you want the first course
if ([currentCourse isKindOfClass:[Course class]]) {
[currentCourse addScoreCategory:myNewObj];
}
In either the addScoreCategory or the init method of the Course class you'd create an NSMutableArray and assign it to the scoreCategories property.
I made a class. This is the h file.
// MyClass.h
#import <Foundation/Foundation.h>
#interface MyClass : NSObject <NSCoding> {
NSString *string1;
NSString *string2;
}
#property (nonatomic, retain) NSString *string1;
#property (nonatomic, retain) NSString *string2;
#end
This is the m file
// MyClass.m
#import "MyClass.h"
#implementation MyClass
#synthesize string1, string2;
- (void)encodeWithCoder:(NSCoder *)coder;
{
NSLog(#"encodeWithCoder");
[coder encodeObject:string1 forKey:#"string1"];
[coder encodeObject:string2 forKey:#"string2"];
}
- (id)initWithCoder:(NSCoder *)coder;
{
NSLog(#"initWithCoder");
self = [super init];
self.string1 = [coder decodeObjectForKey:#"string1"];
self.string2 = [coder decodeObjectForKey:#"string2"];
return self;
}
- (void)dealloc {
[string1 release];
[string2 release];
[super dealloc];
}
#end
I created an array of these objects like this
MyClass *object1 = [[MyClass alloc] init];
object1.string1 = #"object1 string1";
object1.string2 = #"string1 string2";
MyClass *object2 = [[MyClass alloc] init];
object2.string1 = #"object2 string1";
object2.string2 = #"object2 string2";
theArray = [[NSMutableArray alloc] initWithObjects:object1, object2, nil];
Then I saved the array like this
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:theArray] forKey:#"savedArray"];
Then I loaded the array from disk like this.
NSData *theData = [[NSUserDefaults standardUserDefaults] objectForKey:#"savedArray"];
if (theData != nil) {
NSLog(#"found something");
theArray = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:theData]];
}
The program runs normally without error until it gets to this
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
// ...
// Pass the selected object to the new view controller.
detailViewController.myObject = [theArray objectAtIndex:indexPath.row];
It crashes on that last line. It only crashes if I load the array using NSUserDefaults, however I don't notice anything I did wrong with that part.
When it crashes this is what the debugger says
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x5955e50'
*** Call stack at first throw:
(
0 CoreFoundation 0x02395919 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x024e35de objc_exception_throw + 47
2 CoreFoundation 0x0239742b -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x02307116 ___forwarding___ + 966
4 CoreFoundation 0x02306cd2 _CF_forwarding_prep_0 + 50
5 custom object array save test 0x00002872 -[RootViewController tableView:didSelectRowAtIndexPath:] + 156
6 UIKit 0x0032b718 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1140
7 UIKit 0x00321ffe -[UITableView _userSelectRowAtIndexPath:] + 219
8 Foundation 0x00038cea __NSFireDelayedPerform + 441
9 CoreFoundation 0x02376d43 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 19
10 CoreFoundation 0x02378384 __CFRunLoopDoTimer + 1364
11 CoreFoundation 0x022d4d09 __CFRunLoopRun + 1817
12 CoreFoundation 0x022d4280 CFRunLoopRunSpecific + 208
13 CoreFoundation 0x022d41a1 CFRunLoopRunInMode + 97
14 GraphicsServices 0x02bfa2c8 GSEventRunModal + 217
15 GraphicsServices 0x02bfa38d GSEventRun + 115
16 UIKit 0x002c7b58 UIApplicationMain + 1160
17 custom object array save test 0x00002160 main + 102
18 custom object array save test 0x000020f1 start + 53
19 ??? 0x00000001 0x0 + 1
)
terminate called after throwing an instance of 'NSException'
Edit:
I was able to fix the problem by replacing this line
theArray = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:theData]];
with this liine
theArray = [[NSMutableArray alloc] initWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:theData]];
so I guess the question now is why did that solve the problem?
I don't see anything wrong with your archiving/unarchiving code. I suspect that indexPath.row is out of range.
EDIT after OP edited question:
The error message says: -[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x5955e50
You'll notice that this means that 'theArray' is actually pointing at an NSString. Somewhere you have assigned the wrong thing to your 'theArray' variable.
EDIT again after another OP edit:
In your original code you had been using alloc/initWithArray: instead of arrayWithArray:.
The arrayWithArray: usage didn't work for you because that method returns an autoreleased object. So by the time you used it later that reference could have been pointing at just about anything, and hence in one case it happened to be pointing at a string.