I have an ios 6 app which instantiates 3 singletons in the App Delegate as below:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
Constants *constSingleton = [Constants getSingleton];
EntryContainerSingleton * eSingle = [EntryContainerSingleton getSingleton];
LocationMgrSingleton *loc = [LocationMgrSingleton getSingleton];
return YES;
}
However the problem happens that all three calls are executing in different threads simultaneously. EntryContainerSingleton depends on Constants to do some tasks. But Constants is not instantiated completely when it is executing those tasks.
How can I handle this situation?
I was googling around and in previous versions of iOS people have used the NSOperation queue to do this.
However, Im not sure whether this is a good approach in iOS 6. And even if it is I haven't used NSOperation queue before and all the samples on the web are from previous versions and are instantiated in some class which is not an APP Delegate.
If someone can give me some sample code on how to do this in App Delegate to get me started Id really appreciate it
EDIT
Entries controller singleton
-(id)init
{
self = [super init];
NSLog(#"%s %d", __PRETTY_FUNCTION__, __LINE__);
[self getEntriesFromServer];
..............
.............
constSingleton = [Constants getSingleton];
[self addSelfAsObserverToNotifications];
return self;
}
Inside entriescontrollersingleton
-(void)getEntriesFromServer
{
NSLog(#"%s %d", __PRETTY_FUNCTION__, __LINE__);
if(!constSingleton)
{
NSLog(#"constSingleton is null");
}
__block NSString *dateString1 = [constSingleton getCurrentDateTime];
NSLog(#"Sending notification: Async Call started successfully at time %#", dateString1);
[[NSNotificationCenter defaultCenter] postNotificationName:#"didStartAsyncProcess"
object:self];
.......
}
Console output
[96894:c07] -[AppDelegate application:didFinishLaunchingWithOptions:] 21
[96894:c07] +[Constants getSingleton] 39
[96894:c07] -[Constants init] 65
[96894:c07] -[EntryContainerSingleton init] 75
[96894:c07] -[EntryContainerSingleton getEntriesFromServer] 154
[96894:c07] constSingleton is null
[96894:c07] Sending notification: Async Call started successfully at time (null)
[96894:c07] -[Constants incrementNumBackgroundNetworkTasksBy1:] 87
If the Entries singleton needs access to the Constants singleton, it should call [Constants getSingleton] to obtain it. Make sure you implement your singleton methods correctly (see Objective-C Singleton implementation, am i doing it right?)
Every time you need access to a singleton object, you should call [ClassName getSingleton]. There shouldn't be any reason to store a singleton as an instance variable in your application delegate.
Related
I have 2 classes as NSObject subclasses:
1st class is more likely act as adapter. It send data to Class2 for process async task. When delegaton fired I would like to post back data to adaptor class.
In adaptor class:
Class2 *cls = [[Class2 alloc] init];
[ftc fetchLocation];
In Class2.m
-(void)fetchLocation{
if(IS_OS_8_OR_LATER) {
[self.locationManager requestAlwaysAuthorization];
}
self.locationManager = [[CLLocationManager alloc] init];
if ([CLLocationManager locationServicesEnabled]){
NSLog(#"Enable");
}
self.locationManager.delegate =self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager requestWhenInUseAuthorization];
[self.locationManager requestAlwaysAuthorization];
[self.locationManager startUpdatingLocation];
}
When I call fetch-location method from adaptor, it really calls and reads lines, but after that, Class2 disappears and gone back to Adapter class without waiting delegation (didUpdateLocations)
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSString *latitude = [NSString stringWithFormat:#"%f",self.locationManager.location.coordinate.latitude];
NSString *longtitude = [NSString stringWithFormat:#"%f",self.locationManager.location.coordinate.longitude];
NSString *altitude = [NSString stringWithFormat:#"%f",self.locationManager.location.altitude];
NSString *speed = [NSString stringWithFormat:#"%f",self.locationManager.location.speed];
NSDictionary *locationDictionary = #{#"latitude":latitude,#"longtitude":longtitude,#"altitude":altitude,#"speed":speed};
if (locations.count >0 && [locations isKindOfClass:[NSArray class]]) {
[self.delegate userLocationHasUpdated:self :locationDictionary];
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
return;
}
}
But if I just run Class2 and remove adapter from compile (with first initialiser) it runs as expected, How can I achieve to handle delegation methods from another class that fired ?
Best Regards.
You have several options, really. One could be making your second class a property of your first class (singleton pattern would fit nice here, I guess). Then you can either declare a protocol in your second class and notify your first class via delegate methods (non-singleton implementation) or use NSNotificationCenter to post a notification (singleton implementation).
The second option would be to pass a block with completion handler to the second class. You could declare your method in the second class like this, for example (adjust return type and arguments of the block, if needed):
- (void)updateLocationWithCompletionHandler:(void (^)(void))completion;
Implement it so that you call the completion block after you get the geolocation update results.
And call it from the first class like:
[self.secondClass updateLocationWithCompletionHandler:^
{
// Your completion code here.
}];
Hope this helps (sorry, didn't check the code in Xcode, get rid of typos, if any).
Possible duplicate of iOS CLLocationManager in a separate class. You have to create a singleton class for to get the location if you want to have seperate class for handing the location manager. You will find the guidance from the shared link
I'm using Magical Record 2.3.0 beta 5 and I have troubles understanding how to get my NSManagedObjects for the current thread. I have a long running NSOperation where I need my PSPlayer (NSManagedObject).
When I init the NSOperation, I keep an id of my PSPlayer and re-fetch the same object in the operation's main method. According to Apple that the way to do it.
#implementation TAPlayerUpdateOperation
- (instancetype)initWithPlayer:(PSPlayer *)player;
{
self = [super init];
if (self) {
self.playerMD5Id = player.md5Id;
}
}
- (void)main
{
#autoreleasepool {
__block BOOL keepUpdating = YES;
PSPlayer *player = [[PSPlayer MR_findAllWithPredicate:[NSPredicate predicateWithFormat:#"md5Id == %#", self.playerMD5Id]] firstObject];
NSLog(#"player.md5Id = %#", player.md5Id);
// rest of my operation logic
}
}
#end
When I run my app with -com.apple.CoreData.ConcurrencyDebug 1, I get a crash when accessing the property in the NSLog statement.
What is the correct way to get my NSManagedObject so that it is safe for the current thread?
I've pinned the problem down to the following snippet where it crashes as well.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PSPlayer *player =[[PSPlayer MR_findAll] firstObject];
NSLog(#"player = %#", player.name);
});
cheers,
Jan
You need to ensure that everything is saved and merged before the fetch would work. If you're using MR then it's better to take the managed object and call inContext: on it supplying the other context and have it do the work (it also avoids a predicate).
I expect the crash is because you use player.md5Id instead of self.playerMD5Id so you're accessinh the managed object on the wrong thread.
Update
After posting this as an issue to the AFNetworking repo, turns out this is in fact a usage issue on my part. Per the response to my issue:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
So, long story short: If you are using AHTTPSessionManager in the manner described below, make sure to call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate
Original Question
I have a subclassed AFHTTPSessionManager called GTAPIClient that I am using to connect to my REST API. I realize the docs state to use as a singleton but there are a few cases where I need to gin up a new instance. However, it seems that whenever I do so, the object is never deallocated. Currently, GTAPIClient literally does nothing except NSLog itself when deallocated.
Here's some sample code that demonstrates the behavior
GTAPIClient.m
#implementation GTAPIClient
- (void)dealloc
{
NSLog(#"Dealloc: %#", self);
}
#end
GTViewController.m
#import "GTBaseEntityViewController.h"
//Models
#import "GTBaseEntity.h"
//Clients
#import "GTAPIClient.h"
#interface GTBaseEntityViewController ()
#property (nonatomic, weak) GTAPIClient *client;
#property (nonatomic, weak) GTBaseEntity *weakEntity;
#end
#implementation GTBaseEntityViewController
- (IBAction)makeClient:(id)sender {
self.client = [[GTAPIClient alloc] init];
NSLog(#"I just made an API client %#", self.client);
//Another random object assigned to a similar property, just to see what happens.
self.weakEntity = [[GTBaseEntity alloc] init];
NSLog(#"I just made a random object %#", self.weakEntity);
}
- (IBAction)checkClient:(id)sender {
NSLog(#"Client: %#", self.client);
NSLog(#"Entity: %#", self.weakEntity);
}
#end
NSLog output
Fire makeClient:
//It seems to me that both NSLog's should return (null) as they are assigning to a weak property
2014-06-22 16:41:39.143 I just made an API client <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:41:39.144 I just made a random object (null)
Fire checkClient
//Again, both NSLog's should return null for the two objects. However...client is still around. Also, it's overridden dealloc method never fired.
2014-06-22 16:44:43.722 Client: <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:44:43.723 Entity: (null)
For reference, I am using v2.3.1 of AFNetworking. Compiler is warning me that assigning retained object to weak property will release after assignment - which is correct, and functions as expects with my random object. There is nothing else going on in the app. No other view controllers, no other methods on the GTAPIClient, all singleton functionality is removed. Any thoughts on what I am doing wrong here?
Posting the response from Mattt Thompson here to assist future readers:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
If, like many apps, your app uses a singleton Session Manager and one URL Session for your entire app, then you don't need to worry about this.
Replicating your scenario and running it through Instruments shows that AFURLSessionManagers are retained by the NSURLSessions they create, as AFURLSessionManager acts as the delegate for every NSURLSession created. This creates a retain cycle and thus the AFHTTPSessionManager cannot be released. Whether this is a bug in either library or not a bug at all, I'm not sure. You may want to report it on the AFNetworking GitHub page (https://github.com/AFNetworking/AFNetworking).
__weak typeof(manager) weak_manager = manager;
[manager requestWithMethod:method URLString: uri parameters:param
success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(YES, responseObject, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion) {
completion(NO, error, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}];
I am trying to setup an NSInovcation system to launch selectors into background threads using performSelectorInBackground: - So far everything is successful when running the system on instance methods (-), but I also want to support class methods (+). I have adjusted my code to provide an invokeInBackgroundThread for both types of class and everything worked except for one problem. When the class methods are invoked I get my console flooded with "autoreleased with no pool in place" messages. No idea what is causing it. The code which is based off the DDFoundation open source project is shown below.
#implementation NSObject (DDExtensions)
...
+ (id)invokeInBackgroundThread
{
DDInvocationGrabber *grabber = [DDInvocationGrabber invocationGrabber];
[grabber setInvocationThreadType:INVOCATION_BACKGROUND_THREAD];
return [grabber prepareWithInvocationTarget:self];
}
- (id)invokeInBackgroundThread
{
DDInvocationGrabber *grabber = [DDInvocationGrabber invocationGrabber];
[grabber setInvocationThreadType:INVOCATION_BACKGROUND_THREAD];
return [grabber prepareWithInvocationTarget:self];
}
...
...
- (void)forwardInvocation:(NSInvocation *)ioInvocation
{
[ioInvocation setTarget:[self target]];
[self setInvocation:ioInvocation];
if (_waitUntilDone == NO) {
[_invocation retainArguments];
}
if (_threadType == INVOCATION_MAIN_THREAD)
{
[_invocation performSelectorOnMainThread:#selector(invoke)
withObject:nil
waitUntilDone:_waitUntilDone];
} else {
[_invocation performSelectorInBackground:#selector(invoke)
withObject:nil];
}
}
...
+(void)doSomething;
[[className invokeOnBackgroundThread] doSomething];
Main thread has autorelease pool by default, if you start extra thread - it's your job to create the pool. Actually, nothing complicated here, just
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Work...
[pool release];
Also, if you have a lot of threads, I'd suggest you to take a look at NSOperation instead of running threads with [performSelectorInBackground]. NSOperation (with wrapping queue) is more flexible solution for such tasks.
Hy everybody,
I have a screensaver made with obj-c and cocoa. Everything works fine under OsX 10.6.2 except the following.
Within my screensaver I have a WebView with some application running. When I try to call my objective-c app (the screensaver) via javascript, I get an error and the screensaver and the system preferences panel crash.
System Preferences[86666]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException'
reason: '-[NSCFArray drain]: unrecognized selector sent to instance 0x20049b1e0'
*** Call stack at first throw:(
0 CoreFoundation 0x00007fff8123a444 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x00007fff81f130f3 objc_exception_throw + 45
2 CoreFoundation 0x00007fff812931c0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 CoreFoundation 0x00007fff8120d08f forwarding + 751
4 CoreFoundation 0x00007fff812091d8 _CF_forwarding_prep_0 + 232
5 WebCore 0x00007fff847adee0 _ZN3JSC8Bindings12ObjcInstance10virtualEndEv + 48
6 WebCore 0x00007fff8470d71d _ZN3JSC16RuntimeObjectImp18getOwnPropertySlotEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 397
7 JavaScriptCore 0x00007fff80862b66 NK3JSC7JSValue3getEPNS_9ExecStateERKNS_10IdentifierERNS_12PropertySlotE + 486
)
I know this looks like some memory leak, but as you will see in the code, I really have nearly no objects allocated.
This only happens, when I start the screensaver with the "Test" button from the screensaver system prefs.
When I start the screensaver via terminal or if it starts automatically, the same action (calling obj-c from javascript) works fine.
Maybe someone has any idea, where the error could come from. Here is some code from the implementation:
#implementation ScreensaverView
- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview {
self = [super initWithFrame:frame isPreview:isPreview];
if (self) {
[self setAnimationTimeInterval:-1];
[self setAutoresizesSubviews:YES];
// ::::::::::::::::::::::: Init stuff ::::::::::::::::::
// init
quitFlag = false;
previewMode = isPreview;
// find out the path the screensaver bundle
pMainBundle = [NSBundle bundleForClass:[self class]];
pBundlePath = [pMainBundle bundlePath];
// read Info.plist
infoDict = [pMainBundle infoDictionary];
}
return self;
}
- (void)startAnimation
{
[super startAnimation];
// combine: bundle path + filename for screensaver file
NSString *pathToScreensaver = [NSString stringWithString:pBundlePath];
NSString *valueScreensaverFile;
if(!previewMode)
{
valueScreensaverFile = [infoDict objectForKey:#"ScreensaverFile"];
}
else
{
valueScreensaverFile = [infoDict objectForKey:#"PreviewFile"];
}
// add filename to bundle path
pathToScreensaver = [pathToScreensaver stringByAppendingString:valueScreensaverFile];
// complete NSURL to the screensaver file
NSURL *screensaverUrl = [NSURL fileURLWithPath: pathToScreensaver];
webView = [WebView alloc];
[webView initWithFrame:[self frame]];
[webView setDrawsBackground:NO];
// delegation policy for interactive mode
[webView setPolicyDelegate: self];
[webView setUIDelegate:self];
// load screensaver
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:screensaverUrl]];
scriptObject = [webView windowScriptObject];
[scriptObject setValue:self forKey:#"screensaver"];
[self addSubview:webView];
}
- (void)stopAnimation
{
[[webView mainFrame] stopLoading];
[webView removeFromSuperview];
[webView release];
[super stopAnimation];
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
{
if (selector == #selector(quitScreenSaver)) {
return NO;
}
if(selector == #selector(gotoUrl:) ){
return NO;
}
return YES;
}
+(NSString *)webScriptNameForSelector:(SEL)selector
{
if(selector == #selector(quitScreenSaver))
{
return #"quitNoOpen";
}
if(selector == #selector(gotoUrl:))
{
return #"openAndQuit";
}
return nil;
}
- (void) quitScreenSaver
{
quitFlag = true;
[super stopAnimation];
}
- (void) gotoUrl:(NSString *) destinationURL
{
if(destinationURL == NULL)
{
return;
}
NSString * path = destinationURL;
NSURL * fileURL = [NSURL URLWithString:path];
[[ NSWorkspace sharedWorkspace ] openURL:fileURL];
[self quitScreenSaver];
}
#end
I hope that's enough code for you to see some problems / solutions.
I would really appreciaty any answers.
Somehow an NSCFArray (NSMutableArray) is being sent a "drain" message that's meant for an NSAutoreleasePool.
You might be able to get a bit more info on what the array is by implementing the drain method for NSMutableArray, so you can trap the now-recognized selector and print out the contents of the array object. Try adding this somewhere in your code:
#interface NSMutableArray (drain)
- (void) drain;
#end
#implementation NSMutableArray (drain)
- (void) drain
{
NSLog(#"drain message received by object: %#", self);
}
#end
If you don't see any messages show up in the Console, try changing the "NSMutableArray" in the above code to "NSObject".
One thing to be aware of is that when you start the screensaver via the "Test" button in System Prefs, you actually have 2 instances of your screensaver view running in the same process' address space on different threads. One (with isPreview==YES) is the little preview in the SysPrefs window (which continues running even when the full-screen version is started), and the other one is the full-screen version. They are both running in the SysPrefs.app process. So, you have to be careful to check all Notifications/etc. to see if they are coming from the view instance you expect.
I don't see any obvious problems from a quick glance at the code you posted, but it may be somewhere else. Do you use Notifications anywhere?
I put a similar webview-in-a-screensaver project on github at http://github.com/kelan/WikiWalker, where I initially had some similar problems (though I wasn't using any javascript stuff). It's not perfect code, but might help. I also did some tricks to forward notifications to the main thread (for drawing) in a . See the "Threaded Notification Support" parts of WWScreenSaverView.{h,m}.
Something to try:
Open up a terminal window and enter the following line to run System Preferences with NSZombieEnabled:
env NSZombieEnabled=YES "/Applications/System Preferences.app/Contents/MacOS/System Preferences"
Perform the steps that lead to the crash.
Run the Console app, set the filter in the upper right to "System Preferences", and look for NSZombie messages.
Hope this helps!
Just to troubleshoot, did you try not releasing the WebView?
Also, maybe set the WebView's delegates to nil before releasing it first?