NSWorkspace sharedWorkspace runningApplications causing memory leak; alternative option? - objective-c

I'd like to know whether anyone has a suggestion for an alternative to using runningApplications, as something like the following appears to be leaking memory:
https://openradar.appspot.com/24067155
https://github.com/bradjasper/NSRunningApplicationMemoryLeaks
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(checkApps:) userInfo:nil repeats:YES];
}
- (void) checkApps : (id) sender {
#autoreleasepool {
NSArray *appsArray = [[NSWorkspace sharedWorkspace] runningApplications];
for (NSRunningApplication *a in appsArray) {
NSLog(#"%#", [a localizedName]);
}
}
}
Is the only option to wait until Apple provides a solution? I'm working in a sandboxed environment, so some NSTask-based alternatives may not work. Thanks in advance for any ideas.

The answer to your question, is there another sandboxable option?: is no. This is how you're supposed to look for running applications.
You might try KVO (on the sharedWorkspace's runningApplications property) instead. The documentation suggests doing just that rather than what you're doing:
Instead of polling, use key-value observing to be notified of changes to this array property.

After a fair amount more troubleshooting, I eventually found that the memory leak issue only occurs when building/running the app/project from Xcode (Version 7.2 (7C68)). If I build the project, and then head into Finder and manually launch the app built, memory allocation appears to stabilize.
I don't have Zombie objects enabled, and I've made no changes from the default project settings. This must be a bug within Xcode.

Related

NSRunningApplication memory issue

I've noticed a significant memory usage having the following function executed by a timer:
_timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(test)
userInfo:nil
repeats:YES];
- (void)test {
NSRunningApplication *app = [NSWorkspace sharedWorkspace].frontmostApplication;
app.processIdentifier;
}
The code basically does nothing.
Accessing almost any property (bundleIdentifier, bundleURL, description...) of a NSRunningApplication instance results into memory usage increasing at ~1MB/sec (considering the specified time interval). And the worst thing is that it never stops. I haven't tried to put it to the limit yet though...
I've tried to profile it using Instruments (Leaks template), but it finds no memory leaks.
Any clue?
Edit #1:
I've performed a simple experiment, creating a console application with a single swift file:
import Cocoa
while true {
guard let app = NSRunningApplication(processIdentifier: 315) else {
break
}
}
Put any pid you have running.
It takes a gig in a few seconds...
Edit #2:
My latest finding is that Process Type directly affects the behavior.
Consider:
TransformProcessType(&psn, UInt32(processType))
If processType = kProcessTransformToBackgroundApplication or kProcessTransformToUIElementApplication, I face the issue.
If process type = kProcessTransformToForegroundApplication (default value), everything works perfectly fine.

iOS: Deprecation of AudioSessionInitialize and AudioSessionSetProperty

I'm very new to Objective-C, and am trying to update some code that's about 3 years old to work with iOS 7. There are two or two instances of AudioSessionSetProperty and AudioSessionInitialize appearing in the code:
1:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
AudioSessionInitialize(NULL,NULL,NULL,NULL);
[[SCListener sharedListener] listen];
timer = [NSTimer scheduledTimerWithTimeInterval: 0.5 target: self selector: #selector(tick:) userInfo:nil repeats: YES];
// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
And 2:
- (id)init {
if ([super init] == nil){
return nil;
}
AudioSessionInitialize(NULL,NULL,NULL,NULL);
Float64 rate=kSAMPLERATE;
UInt32 size = sizeof(rate);
AudioSessionSetProperty (kAudioSessionProperty_PreferredHardwareSampleRate, size, &rate);
return self;
}
For some reason this code works on iOS7 in the simulator but not a device running iOS7, and I suspect that these deprecations are the cause. I've been reading through the Docs and related questions on this website, and it appears that I need to use AVAudioSession instead. I've been trying to update the code for a long time now, and I'm unsure of how to properly switch over to AVAudioSession. Does anyone know how these two methods above need to look?
Side note: I've managed to hunt down an article that outlines the transition:
https://github.com/software-mariodiana/AudioBufferPlayer/wiki/Replacing-C-functions-deprecated-in-iOS-7
But I can't seem to apply this to the code above.
The code I'm trying to update is a small frequency detection app from git:
https://github.com/jkells/sc_listener
Alternatively, if someone could point me to a sample demo app that can detect frequencies on iOS devices, that would be awesome.
As you have observed, pretty much all of the old Core Audio AudioSession functions have been deprecated in favour of AVAudioSession.
The AVAudioSession is a singleton object which will get initialised when you first call it:
[AVAudioSession sharedInstance]
There is no separate initialize method. But you will want to activate the audio session:
BOOL activated = [[AVAudioSession sharedInstance] setActive:YES error:&error];
As regards setting the hardware sample rate using AVAudioSession, please refer to my answer here:
How can I obtain the native (hardware-supported) audio sampling rates in order to avoid internal sample rate conversion?
For other compares & contrasts between Core Audio audioSession and AVFoundation's AVAudioSession here are some of my other answers around the same topic:
How Do I Route Audio to Speaker without using AudioSessionSetProperty?
use rear microphone of iphone 5
Play audio through upper (phone call) speaker
How to control hardware mic input gain/level on iPhone?
I wrote a short tutorial that discusses how to update to the new AVAudioSession objects. I posted it on GitHub: "Replacing C functions deprecated in iOS 7."

iOS5 crashes during runMode:beforeDate:

I have a problem with compatibility of my application with an iOS5 b7 and GM versions.
The issue occurs in the next lines of code:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
App crashes with signal EXC_BAD_ACCESS after some iterations.
The number of passed iterations is random (from 2 till 7).
Also everything works quite well on iOS4 and iOS3.
The same issue occurs in Apple's sample: XMLPerformance Sample.
What do you think about this?
October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.
4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample.
The problem was in NSAutoreleasePool. There is #property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. When the app starts to download Top300 Paid Apps RSS new thread is created using [NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];. So in that thread we should keep local autorelease pool. It is done in next way:
- (void)downloadAndParse:(NSURL *)url {
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
So in downloadAndParse: everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:
- (void)finishedCurrentSong {
// sending new item to delegate and other ...
countOfParsedSongs++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedSongs = 0;
}
}
As you see there lines :
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
So exactly that lines causes the exception. If I comment them everything works great.
But I decided not only to comment that lines but also replace NSAutoreleasePool in - (void)downloadAndParse:(NSURL *)url with #autorelease block as it is said that it is more efficient:
- (void)downloadAndParse:(NSURL *)url {
#autoreleasepool {
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
}
}
Now everything works fine. The only problem that I haven't resolved is:
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.
Thanks.
This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"
In your code, try this to crash as soon as possible:
[item release], item = nil;
It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.
If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).
#include <pthread.h>
- (void)myFunction
{
NSLog(#"Thread (%d)",
pthread_mach_thread_np(pthread_self()));
}
Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.
Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.
Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?
Just my little contribution.
As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).
Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.
With iOS4, you need NSAutoreleasePool, but not with iOS5.

Why does NSApplescript executeAndReturnError: seem to leak memory?

I'm not sure, but this:
-(void)quitFinder {
NSAppleScript *restartFinder = [[NSAppleScript alloc] initWithSource:#"tell application \"Finder\" to quit"];
[restartFinder executeAndReturnError:nil];
[restartFinder release];
}
when called like this:
[self performSelectorOnMainThread:#selector(quitFinder) withObject:nil waitUntilDone:YES];
yields this:
in Instruments memory leak tool. Am I doing something wrong?
Is this a one-time thing, or does it happen every time you call it? In the first case, it’s likely to be a false positive for some permanent state set up by OSA/AppleScript.
Consider switching to OSAKit. The API is very similar to NSAppleScript, but broader, so this should be close to painless.
If OSAKit doesn't leak, then the problem may very well be in NSAppleScript, and I'd recommend filing a bug with your Instruments trace document attached.

Skype Mac API - Use AppleScript or 5 year old API?

I have a x86_64 app that I would like to have optionally read Skype status messages. However, the 5 year old skype mac framework is 32-bit, and if there is a way to have that compile within a 64-bit app, I haven't found it.
My question is, basically, how should I go about doing this? I really only need to get and set the USERSTATUS AWAY/ONLINE string.
Using AppleScript, a "Should Skype allow this" dialog pops up... every time. This is highly inefficient and downright irritating.
Advice?
I'm considering writing a 32-bit CLI wrapper, but that seems like overkill.
I used Notification Watcher to learn that Skype's API just works with NSDistributedNotifications. Repeating those notifications worked like a charm for a 64bit app.
Check out Scripting Bridge: Introduction to Scripting Bridge Programming Guide for Cocoa
If I remember right, the permission dialog does not come up once you allow permission.
I my Skype Apple Scripts I have to GUI to click them. If they come up.
tell application "Skype" to launch
delay 15
(* this part if the security API window comes up*)
tell application "System Events"
tell application process "Skype"
if exists (radio button "Allow this application to use Skype" of radio group 1 of window "Skype API Security") then
click
delay 0.5
click button "OK" of window "Skype API Security"
end if
end tell
end tell
delay 5
I've found out that if you open "Skype.app" by viewing bundle contents -> Frameworks you'll find a 64bit and 32bit skype.framework
This is an answer in reply to a request from twitter. I used this code after asking this question way back when. I have not needed to look into the Skype API since this works just fine, but I imagine that its been updated since I last tried to use it. Anyhow...
Here's a list of the NSDistributedNotifications that I use when communicating to skype:
SKSkypeAPINotification
SKSkypeAttachResponse
SKSkypeBecameAvailable
SKAvailabilityUpdate
SKSkypeWillQuit
Just like any other kind of NSDistributedNotification, you simply register and process the results:
[[NSDistributedNotificationCenter defaultCenter]
addObserver:self selector:#selector(setStatusAfterQuit:)
name:#"SKSkypeWillQuit"
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
These are the iVars that I keep to sync with Skype:
NSString *applicationName;
NSString *mostRecentStatus;
NSString *mostRecentStatusMessage;
NSString *mostRecentUsername;
int APIClientID;
BOOL isConnected;
BOOL needToSetMessage;
NSString *nextMessage;
NSString *nextStatus;
Here's an example of how to connect to skype:
-(void) skypeConnect{
if (!isConnected){
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAvailabilityRequest"
object:nil
userInfo:nil
deliverImmediately:YES];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAttachRequest"
object:applicationName
userInfo:nil
deliverImmediately:YES];
}
}
Here's an example of getting the status message (after you've registered with Skype):
-(void) processNotification:(NSNotification *) note{
if ([[note name] isEqualToString:#"SKSkypeAttachResponse"]){
if([[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue] == 0){
NSLog(#"Failed to connect to Skype.");
isConnected = NO;
}else {
NSLog(#"Connected to Skype.");
APIClientID = [[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue];
isConnected = YES;
[self sendCommand:#"GET PROFILE MOOD_TEXT"];
if (needToSetMessage){
[self sendCommand:[NSString stringWithFormat:#"SET USERSTATUS %#",nextStatus]];
[self sendCommand:[NSString stringWithFormat:#"SET PROFILE MOOD_TEXT %#",nextMessage]];
needToSetMessage = NO;
nextMessage = #"";
nextStatus = #"";
}
}
}
}