Why unloaded class is still accessible in Cocoa? - objective-c

I loaded a class dynamically with [NSBundle load]. And unloaded it dynamically with [NSBundle unload]. Anyway it looks the class is still alive after unloading.
My code is:
// In separated bundle.
#implementation EEExampleBundle
+ (void)test
{
NSLog(#"TTTTT");
}
#end
// In executable file.
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
#autoreleasepool
{
id EEExampleBundle = nil;
#autoreleasepool
{
NSString* path = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"EEExampleBundle.framework"];
NSBundle* sampleBundle = [NSBundle bundleWithPath:path];
[sampleBundle load];
EEExampleBundle = (id)[sampleBundle classNamed:#"EEExampleBundle"];
[EEExampleBundle test];
BOOL r = [sampleBundle unload];
NSLog(#"unload result = %d", r);
}
[EEExampleBundle test];
}
return 0;
}
The output is:
2011-09-25 01:08:52.713 driver[2248:707] TTTTT
2011-09-25 01:08:52.714 driver[2248:707] unload result = 1
2011-09-25 01:08:52.716 driver[2248:707] TTTTT
Why the class code is still working? Is this normal? Or should I do any extra step to unload the code completely?
P.S
I'm not using ARC. I turned it off explicitly.

(more of a comment than an answer, nevertheless:) That's due to the inner #autoreleasepool block, no? You won't be able to create a new instance from your bundle, but you do keep the ones already created alive (else, that'd generate fancy bugs).

Related

How to parse and take only this string value

I wanted to get only array string value app. As example(SLGoogleAuth ,HalfTunes,TheBackgrounder,Calculiator) . But don't know how to do?
It's a code.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSLog(#"apps: %#", [workspace performSelector:selectorALL]);
}
It's output:
Thanks in advance
You do not want to parse that. NSLog prints out a description of an object. You want to access that value directly.
[LSApplicationWorkspace allApplications];
returns NSArray of LSApplicationProxy. LSApplicationProxy class has a ivar _bundleURL that contains information that you need. You need runtime functions to access it. Working example below:
// #import <objc/runtime.h>
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSArray* appProxies = [workspace performSelector:selectorALL];
Ivar bundleUrlIvar = class_getInstanceVariable([appProxies.firstObject class], "_bundleURL");
NSMutableString* result = [NSMutableString string];
for (id appProxy in appProxies)
{
NSURL* url = object_getIvar(appProxy, bundleUrlIvar);
// at this point you have the information and you can do whatever you want with it
// I will make it a list as you asked
if (url)
{
[result appendFormat:#",%#", [url lastPathComponent]];
}
}
if (result.length > 0)
{
// remove comma from beginning of the list
[result deleteCharactersInRange:NSMakeRange(0, 1)];
}
NSLog(#"apps: %#", result);
Note that this will be rejected by AppStore as you are using private apis. So use at your own discretion.

Why does the position of #autoreleasepool matter?

I'm having trouble understanding how #autoreleasepool work. Consider the following example in which I am creating an AVAssetReader for an audiofile. To make the memory impact matter, I repeated this step 1000 times.
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
void memoryTest() {
NSURL *url = [[NSURL alloc] initWithString:#"path-to-mp3-file"];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:NULL];
}
int main(int argc, const char * argv[]) {
// Breakpoint here (A)
#autoreleasepool {
for(int i = 0; i < 1000; i++) {
memoryTest();
}
}
// Breakpoint here (B)
return 0;
}
Before and after the loop at breakpoint A and breakpoint B I took a look at the memory usage of my application in the Xcode Debug Navigation. At point A my App consumes about 1.5MB of memory. At point B it is about 80MB. Interestingly the memory usage at B drops to about 4MB when I put the autoreleasepool inside the loop like so:
for(int i = 0; i < 1000; i++) {
#autoreleasepool { memoryTest(); }
}
Why does the position matter? (The breakpoint - in both cases - is outside of the autoreleasepool!) In either case the consumed memory at point B is proportional to the number of loops. Am I missing something else here to free up its memory?
I thought of the memory chart in the navigator to be delayed, but adding usleep just before the breakpoint does not change anything. If I change memoryTest() to
void memoryTest() {
NSURL *url = [NSURL URLWithString:#"path-to-mp3-file"];
}
the position of #autoreleasepool does not matter. Isn't that weird?
I am using Xcode 6, OS X SDK 10.10 with ARC enabled.
Temporary memory is being created and autorelease'ed by the code inside the loop by your code and the API calls it makes. This memory is not released until the pool is drained. The usual point it is is drained is in the run loop. But your loop does not allow the run loop to execute so by placing the #autoreleasepool inside the loop the pool can drain as your code runs.

Why is this autorelease error occurring with ARC enabled?

I wrote a small CLI program to delete specific Safari cookies for me. Functionally it's fine, but it's throwing up warnings about objects being "autoreleased with no pool in place". My project has ARC enabled, hence why I don't have any autorelease pools.
Here's my code:
// NSLog replacement from http://stackoverflow.com/a/3487392/1376063
void IFPrint (NSString *format, ...) {
va_list args;
va_start(args, format);
fputs([[[NSString alloc] initWithFormat:format arguments:args] UTF8String], stdout);
fputs("\n", stdout);
va_end(args);
}
int main(int argc, const char * argv[])
{
NSString *urlSearchString;
if (argc > 1) {
urlSearchString = [[NSString alloc] initWithUTF8String:argv[1]];
}
else {
IFPrint(#"No URL provided, quitting.");
return 1;
}
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSString *filterString = [[NSString alloc] initWithFormat:#"domain ENDSWITH '%#'", urlSearchString];
NSPredicate *filter = [NSPredicate predicateWithFormat:filterString];
NSArray *matchedCookies = [cookieStorage.cookies filteredArrayUsingPredicate:filter];
for (int i = 0; i < matchedCookies.count; i++) {
[cookieStorage deleteCookie:[matchedCookies objectAtIndex:i]];
}
IFPrint(#"Removed %li cookies", matchedCookies.count);
return 0;
}
The message I get is:
objc[15502]: Object 0x107b2bf00 of class NSThread autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears in the Xcode debugger or when running the release binary directly (slight digression: shouldn't these messages be stripped out of the "release" build?). The line that causes it seems to be:
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
Similarly, if I run it without passing an argument, I get a similar message:
objc[15630]: Object 0x100114ed0 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
objc[15630]: Object 0x100114f80 of class __NSCFData autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears to come from the IFPrint function I'm using (however this doesn't show up when I use the IFPrint when I provide a proper argument).
I'm a bit out of my depth here, can anyone show me where (and how) I've gone wrong?
ARC still requires an autorelease pool. Methods like [NSPredicate predicateWithFormat:filterString] continue to release an autoreleased object (though you no longer need to concern yourself all that much since ARC handles it). Furthermore the internal implementation of any library method you call may create arbitrarily many autoreleased objects while running.
You should wrap your code in an autorelease pool via the #autoreleasepool mechanism.
Wrap the entire body of main with #autoreleasepool like so:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// your code
}
}
All you need to do is add an autoreleasepool in your main.
int main(int argc, const char * argv[])
{
#autoreleasepool
{
//Your code
}
}

How can I get list of classes already loaded into memory in specific bundle (or binary)?

It's possible to get list of classes from a bundle via NSBundleDidLoadNotification. But I can't figure out how I can get them from already loaded bundle. (same bundle with code)
I'm trying to get class list of my application bundle. More specifically, the classes only in my application binary.
I looked at objc_getClassList, but it returns ALL classes and it's obviously too heavy for me. I need lightweight method. I found objc_copyClassNamesForImage by googling, but it's not documented, and I don't know how to use it safely. I think I can try to use it conventionally, but I want to find another more safe option before going there.
Another option would be to iterate through all the classes registered with the runtime and use +[NSBundle bundleForClass:] on each one to figure out which one it came from. You can then sort things into sets based on the result.
Something like this:
#interface NSBundle (DDAdditions)
- (NSArray *)definedClasses_dd;
#end
#implementation NSBundle (DDAdditions)
- (NSArray *)definedClasses_dd {
NSMutableArray *array = [NSMutableArray array];
int numberOfClasses = objc_getClassList(NULL, 0);
Class *classes = calloc(sizeof(Class), numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for (int i = 0; i < numberOfClasses; ++i) {
Class c = classes[i];
if ([NSBundle bundleForClass:c] == self) {
[array addObject:c];
}
}
free(classes);
return array;
}
#end
Then you can call:
NSLog(#"%#", [[NSBundle mainBundle] definedClasses_dd]);
Try this magic:
- (NSArray *)getClassNames {
NSMutableArray *classNames = [NSMutableArray array];
unsigned int count = 0;
const char **classes = objc_copyClassNamesForImage([[[NSBundle mainBundle] executablePath] UTF8String], &count);
for(unsigned int i=0;i<count;i++){
NSString *className = [NSString stringWithUTF8String:classes[i]];
[classNames addObject:className];
}
return classNames;
}
I could find some example for the function objc_copyClassNamesForImage at here.
http://www.opensource.apple.com/source/objc4/objc4-493.9/test/weak.m
// class name list
const char *image = class_getImageName(objc_getClass("NotMissingRoot"));
testassert(image);
const char **names = objc_copyClassNamesForImage(image, NULL);
testassert(names);
testassert(classInNameList(names, "NotMissingRoot"));
testassert(classInNameList(names, "NotMissingSuper"));
if (weakMissing) {
testassert(! classInNameList(names, "MissingRoot"));
testassert(! classInNameList(names, "MissingSuper"));
} else {
testassert(classInNameList(names, "MissingRoot"));
testassert(classInNameList(names, "MissingSuper"));
}
free(names);
The source code is unofficial but from Apple. So I decided to use this code until I find any better way.

unrecognized selector with category NSMutableData

I'm adding a category to NSData as follows:
// PacketCategories.h
#interface NSData(PacketSplit)
- (NSArray *)splitTransferredPackets:(NSData **)leftover;
#end
// PacketCategories.m
#implementation NSData(PacketSplit)
- (NSArray *)splitTransferredPackets:(NSData **)leftover {
NSMutableArray *ret = [NSMutableArray array];
const unsigned char *beginning = [self bytes];
const unsigned char *offset = [self bytes];
NSInteger bytesEnd = (NSInteger)offset + [self length];
while ((NSInteger)offset < bytesEnd) {
uint64_t dataSize[1];
NSInteger dataSizeStart = offset - beginning;
NSInteger dataStart = dataSizeStart + sizeof(uint64_t);
NSRange headerRange = NSMakeRange(dataSizeStart, sizeof(uint64_t));
[self getBytes:dataSize range:headerRange];
if (dataStart + dataSize[0] + (NSInteger)offset > bytesEnd) {
NSInteger lengthOfRemainingData = [self length] - dataSizeStart;
NSRange dataRange = NSMakeRange(dataSizeStart, lengthOfRemainingData);
*leftover = [self subdataWithRange:dataRange];
return ret;
}
NSRange dataRange = NSMakeRange(dataStart, dataSize[0]);
NSData *parsedData = [self subdataWithRange:dataRange];
[ret addObject:parsedData];
offset = offset + dataSize[0] + sizeof(uint64_t);
}
return ret;
}
#end
And then trying to call that category:
#import "PacketCategories.h"
NSMutableData *data = [NSMutableData data];
// Read some data
[data appendBytes:buffer length:bytesRead];
NSArray *dataPackets = [data splitTransferredPackets:&readLeftover];
Which gets the following error:
-[NSConcreteMutableData splitTransferredPackets:]: unrecognized selector sent to instance 0x6e6f7b0
[ERROR] The application has crashed with an unhandled exception. Stack trace:
Any ideas? Does NSConcreteMutableData not inherit from NSData?
Other suggested answers (Objective-C Category Causing unrecognized selector) have suggested that the file isn't linked in, which is not possible because other categories defined in this file are used just fine.
Thanks
Ookay. I had the same problem, but with a different outcome. Briefly, the problem was in my project file. The category files showed up in the project navigator - I can load/edit, etc. But the linker did not know to link with them. The way I found this was pulling on another thread - creating a dummy concrete class in my category files to force the linker to include them. No luck. Then I tried to instantiate an instance of the dummy class in my app. Eureka - I now get a linker error! So, I simply removed and re-added the category files to the project and now all is well. Not sure how the project file got out of whack ( svn merge? ), but there it is.
Delete the Category files and add them again checking the target.
Solved for me.