I've recently added test coverage analysis to our codebase, and thankfully the classes that I expected to be well covered are clocking in above 95% coverage. I love the ones that hit 100%, since that's obviously the goal, but a handful of my classes are stuck at 96-98%, because one line isn't being hit. That one line is the #implementation ClassName line, which I find really confusing.
Logically if all of the class's executable lines were exercised, the class was obviously instantiated.
I'm using the standard Xcode methods (__gcov_flush()) for generating .gcda and .gcno usage data, and I'm using CoverStory for the analysis and HTML generation.
It's not a huge deal; obviously the class is well covered, but it's annoying to keep mental notes of the classes that aren't at 100% because the dang #implementation line is missed for whatever reason.
I can't find a pattern between the files that have the #implementation line hit and those that have it missed. Has anyone else experienced this and/or know what I might try to alleviate it? Could it even be a bug with CoverStory perhaps?
I've had the same problem with my current project and you can fix it using:
Edit: For XCText, it should be XCTAssertEqualObjects since NSString * are objects
Kiwi:
[NSStringFromClass([[YourClass new] class]) should] equal:NSStringFromClass[YourClass class])]
or
XCTAssertEqualObjects(NSStringFromClass([[YourClass new] class]), NSStringFromClass([YourClass class]));
#implementation YourClass will hit while an instance of that class going to be dealloc'ed.
You can confirm that by create a empty demo class, put a checkpoint on #implementation DemoClass, and call [DemoClass new], the debugger will stop while [DemoClass .cxx_destruct] is being called, which means the instance has zero retainCount and going to be dealloc'ed.
You can also confirm that by view the code coverage html file created by XcodeCoverage; select a file, click "function" which will list all functions coverage report for that file, and you will see [DemoClass .cxx_destruct] list as a function in either green or red.
In other word, If #implementation YourClass is not being called, there is a good chance that you had retain cycles/static variables in your class implementation that prevent your class from deallocating. (e.g. contains NSURLSession object but forget to call finishTaskAndInvalidate, or contains repeat NSTimer but didn't call invalidate , or it is singleton class which contains static variables etc.)
Related
I'm trying to make a category for a class that gets defined in a source file I don't have access to, namely RunnerViewController.
The two important files here are iPad_RunnerAppDelegate.h and FilesBrowser.mm. I do not have access to the header file's corresponding source file.
iPad_RunnerAppDelegate.h contains a forward declaration to the RunnerViewController class, and can reference that class with no difficulties.
However, if I try to #include "iPad_RunnerAppDelegate.h" in FilesBrowser.mm and then try to create a category in the same file, it doesn't recognise the class name.
Despite this error appearing, I can still use the RunnerViewController class inside FilesBrowser.mm, I just can't make categories for it.
What's happening here, and how would I fix it?
I've had to do this same thing but it was a long time ago. The problem is that without some indication of where to apply the category, your code cannot work alone. What you need is info to the compiler to let it know where it's going to insert the category.
In FilesBrowser.mm, you will tell it by adding this BEFORE "iPad_RunnerAppDelegate.h":
#interface RunnerViewController: UIViewController // or whatever it actually subclasses
#end
Now the compiler knows that its going to insert the category against an Objective C class named RunnerViewController.
But - you're not completely done. Now you need to insure that the runtime / loader do the class loading and category insertions at the proper time. This is where my knowledge gets a bit fuzzy, so it may not turn out to be a problem at all.
What I believe needs to occur is for the RunnerViewController class to get loaded first, then later at some point the category you wrote applied to it (but this may not be necessary). So if RunnerViewController is statically linked into your app (using a .a archive), then you're good for sure. But if that class is in a dylib loaded dynamically, and your category is statically linked - well that might be a problem. I just don't know how long Apple waits to try and deal with the categories (which by the way the -ObjC link flag is for.
In any case try the code above first and just see what happens. If RunnerViewController is dynamically loaded, and you get some hard runtime error, there is a workaround where you would make your category go into a module/framework that is also dynamically linked, but gets linked after RunnerViewController.
In the end I believe you have a good chance of making this happen.
Quick question. I've got a project with a class with no implementation file.
Then in the AppDelegate I've got:
#import "AppDelegate.h"
#import "SomeClass.h"
#interface AppDelegate ()
#property (nonatomic, strong) SomeClass *myProperty;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.myProperty hello];
// self.myProperty = [[SomeClass alloc] init]; // uncomment and fails as expected.
return YES;
}
Shouldn't somebody tell me that there is no implementation file ? Some sort of warning or anything?
If I do an alloc] init] it'll not compile as expected.
That code actually compiles.
Here is the project in github.
https://github.com/nmiyasato/noImplementation
Thanks
No. This isn't detectable at compile time or at link time in Objective-C.
First, the compiler knows exactly nothing about "header files" or "implementation files." (This is changing a little with the new module system, but that's not what we're discussing here.)
#import is not handled by the compiler. It's handled by the pre-processor. It takes the file SomeClass.h and splats it into AppDelegate.m as text before the compiler even sees line one. So all the compiler has to work with is this one giant file with all the text of all the headers plus this implementation (while there is "whole module optimization" now, that's a link step, not a compile step). It doesn't have any access to the rest of the project.
So the compiler has no way to know that you haven't provided an implementation. And in ObjC, even if the compiler looked at all the code, it couldn't actually know that there's no implementation anywhere because you can add implementations at runtime. In fact, it's pretty common to do this. It's how all of Core Data works. The implementations also may be linked in via a shared framework (which is very common), and may even be linked in at runtime on OS X. Or the implementations may be in a static library, so lacking a .m is still unhelpful.
It's even possible that the result of self.myProperty is random "other thing" that is only pretending to be SomeClass. Yeah, I know that sounds crazy. Welcome to class clusters with Core Foundation bridging. That's a thing. So there might not even be an implementation in the way you're thinking. Objective-C is a pretty insanely dynamic language.
As an example, the following is legal ObjC (it even works):
#interface NSString (Hello)
- (void)hello;
#end
#implementation NSString (Hello)
- (void)hello {
NSLog(#"I'm string's Hello!");
}
#end
...
self.myProperty = (SomeClass *)#"";
[self.myProperty hello];
You'd think maybe the linker could figure it out, but by the time we get to the linker, all object types are id and all methods are just selectors and method signatures. Most of the type information is gone.
So why does this fail to link if you call [[SomeClass alloc] init]? First note that it does compile, it just doesn't link. The reason is that [self.myProperty hello] is a message to an object. The linker doesn't know or care about the type of the object. It just needs a pointer to the instance. But [SomeClass alloc] is a message to a class. In order to link it, the linker has to have a pointer to the class. You'll find that any message to a class that isn't implemented will create a linker error (try [SomeClass initialize]).
In your code nothing happens at runtime because self.myProperty is nil, so there's no error. That would be the same even if you had an implementation. In the vast majority of cases the lack of an implementation file is going to be caught during link because somewhere in your system you probably call +alloc. So in practice, this shouldn't come up often at all, and this rare case is extremely difficult to detect without breaking a lot of legitimate ObjC.
Background.
Please consider the following steps:
1) In Xcode create a new "Single View Application".
2) Create a category NSObject+Extension.h and .m files:
// .h
#interface NSObject (Extension)
- (void)someMethod;
#end
// .m
#implementation NSObject (Extension)
- (void)someMethod {
NSLog(#"someMethod was called");
}
#end
3) Ensure that NSObject+Extension.m file is included into a main target.
4) Add the following line to AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSString new] performSelector:#selector(someMethod)];
return YES;
}
5) Ensure that #import "NSObject+Extension.h line does not exists anywhere in the app!
6) Run Application.
The output is
2013-08-27 04:12:53.642 Experimental[32263:c07] someMethod was called
Questions
I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.
Is there a way to avoid this behavior? I do want my categories to work only when I explicitly import them, not when I just have them linked to a target I use to run.
I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.
There are no namespaces on Objective-C objects. If you declare that a class has a method (whether via a category or on the primary #interface) then every instance of that class will have that method.
The way that Objective-C deals with "private" methods is by choosing not to tell other people about the methods in question (which is accomplished by not #import-ing the file that declares those methods). This, coupled with -Wundeclared-selector (warn if you use a selector that the compiler doesn't know about) is about as good of a guard as you're going to get.
But regardless, if you compile the .m file into your final binary, the method will exist, even if no one else "knows" about it.
Are there way to avoid this behavior? I do want my categories to work only when I explicitly import them, not just when I have them linked to a target I use to run.
Yeah, use -Wundeclared-selector, and Xcode will warn you.
Including the header just makes it so the compiler knows about it. It compiles it regardless because xCode compiles every file included in a target. At runtime, the method will be there, so even if you didn't include it for compile time checking, the object will still respond to that category method.
I'm starting to make use of static code analysis to find memory management problems in my code. I've found it very useful, but there are a couple of bits of code I've written that I'm sure aren't causing memory leaks (instruments doesn't report any) but are being reported by the analyser. I think it's a question of me writing the code in a non-friendly manner. Here's an example
for (glyphUnit *ellipsisDot in ellipsisArray) {
CGPathRef newDot = CGPathCreateCopyByTransformingPath(ellipsisDot.glyphPath, &ellipsisTransform);
CGPathRelease(ellipsisDot.glyphPath); // Incorrect decrement of the reference count of an object that is not owned at this point by the caller
ellipsisDot.glyphPath = newDot;
}
where glyphUnit is a simple custom class that has a GCPathRef as a property, which the custom class releases in its dealloc method. So in this loop I'm transforming the path and storing it in anewDot then releasing the original glyphPath so I can assign the newly created one to it. I can see how this is getting the code analyser confused, with it giving a message I'm decrementing an object I don't own. Is there another way swap in the new path without confusing it?
It should be,
for (glyphUnit *ellipsisDot in ellipsisArray) {
CGPathRef newDot = CGPathCreateCopyByTransformingPath(ellipsisDot.glyphPath, &ellipsisTransform);
ellipsisDot.glyphPath = newDot;
CGPathRelease(newDot);
}
You are creating newDot by doing CG CreateCopy operation and you need to do release on that variable. So the analyser is warning that you dont own ellipsisDot.glyphPath param to release it. You are trying to release the wrong param here. When you put that release statement in the second line as in question, ellipsisDot.glyphPath and newDot are pointing to two separate instances. Only on the third line, you were assigning newDot to ellipsisDot.glyphPath.
It turns out that I forgot about defining setters in my custom glyphUnit class. Being in the ARC world for objects and used to synthesizing my methods I had forgotten the need to manage my retain counts for core foundation references. I had been releasing glyphPath in my dealloc, but was not using a setter method. As #Sven suspected, I was simply using a synthesized assign and making up for my lack of setter method by doing some less than intuitive releases in my code snippet above. I've now added a setter method as below to glyphUnit
- (void)setGlyphPath:(CGPathRef)newPath
{
if (_glyphPath != newPath)
{
CGPathRelease(_glyphPath);
_glyphPath = CGPathRetain(newPath);
}
}
After adding this, I now had the necessary retain in place to change my code snippet to the one #ACB described and my code ran nicely (without it, it obviously caused an EXC_BAD_ACCESS).
Kudos to #Sven for inferring my mistake and setting me in the right direction... no pun intended.
In my iOS application I have a 5 view controllers that all deal with the same feature (groups). These view controllers can be pushed on top of eachother in a few different configurations. I made a file called GroupViewHelper.h which uses #implementation to provide some functions for the groups feature. The functions look through the view controller stack and send a "refresh" message to a view controller of a specific type. The file looks like this:
#implementation UIViewController (GroupViewHelper)
- (void) refreshManageGroupsParent
{
// ...
}
- (void) refreshGroupDetailsParent
{
// ...
}
#end
My code works great and everything behaves as expected, but I get 14 warnings that are all very similar to this at build time:
ld: warning: instance method 'refreshGroupDetailsParent' in category from /Users/x/Library/Developer/Xcode/DerivedData/myapp-ayshzmsyeabbgqbbnbiixjhdmqgs/Build/Intermediates/myapp.build/Debug-iphonesimulator/myapp-dev.build/Objects-normal/i386/GroupMembersController.o conflicts with same method from another category
I think I'm getting this because I'm using a .H which is included in multiple places, but how do I correctly use #implementation in this situation?
I think I'm getting this because I'm using a .H which is included in multiple places
Well, sort of, but the real problem is that you've put the #implementation in the .h file in the first place. If you only included that .h file in one place, you would get away with it—but it would still not be the right way to do it.
but how do I correctly use #implementation in this situation?
Put it in a file called GroupViewHelper.m, and add that file to your project's sources, and put the #interface in GroupViewHelper.h.
Or, ideally, call them UIViewController+GroupViewHelper.m and UIViewController+GroupViewHelper.h, because that's the idiomatic way to name category files. (And if you use Xcode's "New File…" menu item to create a new Objective-C category file, that's what it will give you.)
In other words, interfaces and implementations for categories on existing classes work exactly the same as interfaces and implementations for new classes.
I have encountered exactly this issue. I had imported a reference to a header file, on a .m page. However, it also contained a reference to another header file, which contained a reference to another header file - that also referenced the conflicted header file. So indirectly the same header file was imported twice, causing the error.
In my case, the .m file did not need this reference. I was able to delete it, removing the error. My advice is check the files where you have included a reference to the offending header file, and verify that it actually is required.