In order to understand IMP, I wrote some test codes and found a problem in the process of testing.
I got a surprise 😂
Here is the code.
#interface Test : NSObject
#property (nonatomic, copy) NSString *name;
#end
#implementation Test
- (NSString *)description{
return [[super description] stringByAppendingString:self.name];
}
- (void)dealloc{
NSLog(#"Test dealloc %#",self);
}
- (void)test:(NSString *)str{
NSLog(#"- (void)test %#",str);
}
+ (void)test:(NSString *)str{
NSLog(#"+ (void)test %#",str);
}
- (void)test{
NSLog(#"- (void)test");
}
+ (void)test{
NSLog(#"+ (void)test");
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// IMP imp = [[Test new] methodForSelector:#selector(test:)];
// NSLog(#"%p",imp);
// IMP imp1 = [Test instanceMethodForSelector:#selector(test)];
IMP imp2 = [Test methodForSelector:#selector(test:)];
// NSLog(#"%p",imp2);
imp2();
NSLog(#"%p",imp2);
// imp2 = [Test methodForSelector:#selector(test)];
// imp2();
// imp();
// imp = [[Test new] methodForSelector:#selector(test:)];
// NSLog(#"%p",imp);
// imp();
// imp1();
// imp1();
// imp2();
// imp2();
// void (*func)(id,SEL,NSString *) = (void *)imp;
// func([Test class],#selector(test:),#"");
// void (*func1)(id,SEL,NSString *) = (void *)imp1;
// func1([Test class],#selector(test:),#"ha");
// void (*func2)(id,SEL,NSString *) = (void *)imp2;
// func2([Test class],#selector(test:),#"haha");
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
There's no problem when I run a single IMP.But when I run more than one IMP will crash for BAC_ACCESS and it only happens with the function which has arguments.So,why it can work with one and crash with two.
Of course you have a crash, because you need path id, SEL and also NSString * where needed.
Better cast to actual function prototype to have compiler errors when you call it wrong, instead of using direct IMP variables.
Apple should forbid () operator for IMPs and declare is as void * instead.
But people rarely use IMP for direct method calls, so there is no complains.
Related
I have a IKImageBrowserView which has its own controller - BrowserController.h + .m
#interface BrowserController : NSWindowController{
NSMutableArray *_images;
}
#property (strong,nonatomic) IBOutlet IKImageBrowserView *imageBrowser;
-(void)addMultipleImages;
When I run the app for the first time, the staring image loads, but when I click a button to add other images and call a method from another class I don't get any results. I have noticed that my _imageBrowser loses the reference and becomes nil.
How could I solve this issue?
AppDelegate.m
#import "AppDelegate.h"
#import "BrowserController.h"
#implementation AppDelegate{
BrowserController *imageBrowserController;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
imageBrowserController = [BrowserController sharedManager];
}
- (IBAction)doSomething:(id)sender {
[imageBrowserController addMultipleImages];
}
#end
BrowserController.m
#import "BrowserController.h"
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
if(_path != path)
{
_path = path;
}
}
/* required methods of the IKImageBrowserItem protocol */
#pragma mark -
#pragma mark item data source protocol
/* let the image browser knows we use a path representation */
- (NSString *)imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}
/* give our representation to the image browser */
- (id)imageRepresentation
{
return _path;
}
/* use the absolute filepath as identifier */
- (NSString *)imageUID
{
return _path;
}
#end
#interface BrowserController ()
#end
#implementation BrowserController
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)awakeFromNib{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
- (void)updateDataSource{
[_imageBrowser reloadData];
}
-(NSUInteger) numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser{
return [_images count];
};
-(id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index{
return [_images objectAtIndex:index];
};
- (void)updateDatasource
{
[_imageBrowser reloadData];
}
- (void)addMultipleImages{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[_images addObject:p];
[_images addObject:p];
[_imageBrowser reloadData];
}
+ (id)sharedManager {
static BrowserController *sharedMyManager = nil;
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
#end
There is some confusion as to the name of the class where you mention it's called ImageBrowserController at the start of your question and it looks like it's called BrowserController at the end of your question.
The issue is that you allocate _images in awakeFromNib which is never called given the class is created via the singleton pattern (sharedInstance) and not loaded from a .nib file.
Therefore move the code from awakeFromNib into init and dump awakeFromNib:
BrowserController.m:
- (id)init
{
self = [super init];
if (self) {
myImageObject *p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
return self;
}
Further confusion: you have implemented an initWithFrame method. Why?
This question already has answers here:
Under automatic reference counting, why are retain, release, and dealloc not allowed?
(4 answers)
Closed 9 years ago.
I'm using the latest xcode.
implementation file:
SimpleCar.m:
#import "SimpleCar.h"
#implementation SimpleCar
// set methods
- (void) setVin: (NSNumber*)newVin {
[vin release];
vin = [[NSNumber alloc] init];
vin = newVin;
}
- (void) setMake: (NSString*)newMake {
[make release];
make = [[NSString alloc] initWithString:newMake];
}
- (void) setModel: (NSString*)newModel {
[model release];
model = [[NSString alloc] initWithString:newModel];
}
// convenience method
- (void) setMake: (NSString*)newMake
andModel: (NSString*)newModel {
// Reuse our methods from earlier
[self setMake:newMake];
[self setModel:newModel];
}
//get methods
- (NSString*) make; {
return make;
}
- (NSString*) model;{
return model;
}
- (NSNumber*) vin;{
return vin;
}
-(void) dealloc
{
[vin release];
[make release];
[model release];
[super dealloc];
}
#end
interface file:
SimpleCar.h:
#import <Foundation/Foundation.h>
#interface SimpleCar : NSObject {
NSString* make;
NSString* model;
NSNumber* vin;
}
// set methods
- (void) setVin: (NSNumber*)newVin;
- (void) setMake: (NSString*)newMake;
- (void) setModel: (NSString*)newModel;
// convenience method
- (void) setMake: (NSString*)newMake
andModel: (NSString*)newModel;
// get methods
- (NSString*) make;
- (NSString*) model;
- (NSNumber*) vin;
#end
I get an error in the implementation file when I type "[vin release], [model release], [make release]" and I cannot run the program.
ARC is turned on, therefore memory management is automatic.
A modern definition of that class would be declared as:
#interface SimpleCar : NSObject
#property(copy) NSString *make;
#property(copy) NSString *model;
#property(copy) NSNumber *vin;
- initWithMake:(NSString*)make model:(NSString*)model vin:(NSNumber*)vin;
#end
And would be implemented as:
#implementation SimpleCar
- initWithMake:(NSString*)make model:(NSString*)model vin:(NSNumber*)vin
{
if (self = [super init]) {
_make = [make copy];
_model = [model copy];
_vin = [vin copy];
}
return self;
}
#end
You wouldn't typically implement a convenience method like setMake:andModel:. It just adds API footprint without really buying much in the way of convenience. It also raises questions like "What happens when I observe either make or model?".
To give the short version of the story here, I'm trying to make an example of a memory leak and why you would want to use the leaks performance tool in the Instruments application. I need to use this method of detecting memory leaks, but the example that I've created is not creating any memory leaks that the tool detects (yes, apparently I'm failing at failing here -_-). Here's the code:
// Memory_Leak_ExampleViewController.h
// Memory Leak Example
#import <UIKit/UIKit.h>
#import "StringReturner.h"
#interface Memory_Leak_ExampleViewController : UIViewController {
IBOutlet UITextField* xTF;
IBOutlet UITextField* yTF;
IBOutlet UITextView* result;
StringReturner* sr;
}
-(IBAction)addTogether;
-(IBAction)releaseSR;
#end
// Memory_Leak_ExampleViewController.m
// Memory Leak Example
#import "Memory_Leak_ExampleViewController.h"
#implementation Memory_Leak_ExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
sr = [[StringReturner alloc] initWithFrame:self.view.frame];
}
- (IBAction)addTogether
{
[result setText:[sr returnEq:[xTF.text intValue]:[yTF.text intValue]]];
}
- (IBAction)releaseSR
{
[sr release];
}
#end
//StringReturner.h
#import <UIKit/UIKit.h>
#interface StringReturner : UIView {
NSString* string;
NSString* returnString;
}
-(NSString*)returnEq:(int)x:(int)y;
#end
// StringReturner.m
#import "StringReturner.h"
#implementation StringReturner
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (NSString*)returnEq:(int)x:(int)y
{
string = [[NSString alloc] initWithString:#""];
int result = x+y;
string = [NSString stringWithFormat:#"%d + %d = %d", x, y, result];
return string;
}
- (void)dealloc {
[super dealloc];
}
#end
All of the IBActions and IBOutlets are set up correctly, so shouldn't there be a memory leak after adding once and then releasing the instance of StringReturner? If not, what am I not doing wrong?
Initializing NSString with static strings like #"" doesn't result in a real string being allocated and put on heap so Leaks doesn't detect it. It's an optimization.
Try leaking the other string created by stringWithFormat. Instruments Leaks will pick that up right away. Put a [string retain] after string = [NSString stringWithFormat:#"%d + %d = %d", x, y, result]; to create the leak.
i have a static class witch has two property,like below ...
#interface Global : NSObject
{
BarcodeScanner* scanner;
NSInteger warehouseID;
}
#property(assign) BarcodeScanner* scanner;
#property(assign) NSInteger warehouseID;
+(Global *)sharedInstance;
#end
#import "Global.h"
#implementation Global
#synthesize scanner,warehouseID;
+ (Global *)sharedInstance
{
static Global *globalInstance = nil;
if (nil == globalInstance) {
globalInstance = [[Global alloc] init];
globalInstance.scanner = [[BarcodeScanner alloc] init];
globalInstance.warehouseID = 1;
}
return globalInstance;
}
-(void) dealloc
{
[super dealloc];
}
#end
now when i analyze project in Xcode i got warning for memory leak for scanner and warehouseID properties , and when i try to release them in dealloc method like ...
[[[Global sharedInstance] scanner]release];
i got warning "incorrect decrement of the reference count of an object..."
how should i resolve this problem.
so thanks for any help.
The warning is because your code does not match the rules Analyzer uses. To avoid the warning
make the scanner property retain
change the the instantiation or BarcodeScanner to be autorelease
add a release for scanner in dealloc
Example (reformatted just to save space):
#class BarcodeScanner;
#interface Global : NSObject {
BarcodeScanner* scanner;
NSInteger warehouseID;
}
#property(retain) BarcodeScanner* scanner;
#property(assign) NSInteger warehouseID;
+(Global *)sharedInstance;
#end
#implementation Global
#synthesize scanner,warehouseID;
+ (Global *)sharedInstance {
static Global *globalInstance = nil;
if (nil == globalInstance) {
globalInstance = [[Global alloc] init];
globalInstance.scanner = [[[BarcodeScanner alloc] init] autorelease];
globalInstance.warehouseID = 1;
}
return globalInstance;
}
-(void) dealloc {
[scanner release];
[super dealloc];
}
#end
just leave it to autorelease pool
globalInstance.scanner = [[[BarcodeScanner alloc] init] autorelease];
- (BOOL)coolMethod:(NSString*)str
{
//do some stuff
Webservice *ws = [[WebService alloc] init];
NSString *result = [ws startSynchronous:url];
if ([result isEqual:#"Something"])
{
//More calculation
return YES;
}
return NO;
}
I am using OCUnit
In the following method how can i mock my WebService Object, or the result to the method "startSynchronous" to be able to write an independent unit test?
Is it possible to inject some code in there to either create a mock web service or return a mock data on startSynchronous call?
One way is to use categories and override methods you want, you can even override the init method to return a mock object:
#interface Webservice (Mock)
- (id)init;
#end
#implementation Webservice (Mock)
- (id)init
{
//WebServiceMock is a subclass of WebService
WebServiceMock *moc = [[WebServiceMock alloc] init];
return (Webservice*)moc;
}
#end
The problem with this is that if you want to make the object return different results in different tests in 1 test file you cannot do that. (You can override each method once per test page)
EDIT:
This is an old question I posted, I thought I would update the answer to how I write testable code and unit test it nowadays :)
ViewController Code
#implementation MyViewController
#synthesize webService;
- (void)viewDidLoad
{
[super viewDidLoad];
[self.webService sendSomeMessage:#"Some_Message"];
}
- (WebService *)webService
{
if (!_webService)
_webService = [[WebService alloc] init];
return _webService;
}
#end
Test Code
#implementation MyViewControllerTest
- (void)testCorrectMessageIsSentToServer
{
MyViewController *vc = [[MyViewController alloc] init];
vc.webService = [OCMock niceMockForClass:[WebService class]];
[[(OCMockObject *)vc.webService expect] sendSomeMessage#"Some_Message"];
[vc view]; /* triggers viewDidLoad */
[[(OCMockObject *)vc.webService verify];
}
#end
Building on top of the WebService answer from aryaxt, here's a little trick to be able to get different results in different test.
First, you need a singleton object which will be used to store the desired answer, right before the test
TestConfiguration.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
void MethodSwizzle(Class c, SEL orig, SEL new);
#interface TestConfiguration : NSObject
#property(nonatomic,strong) NSMutableDictionary *results;
+ (TestConfiguration *)sharedInstance;
-(void)setNextResult:(NSObject *)result
forCallToObject:(NSObject *)object
selector:(SEL)selector;
-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector;
#end
TestConfiguration.m
#import "TestConfiguration.h"
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
};
#implementation TestConfiguration
- (id)init
{
self = [super init];
if (self) {
self.results = [[NSMutableDictionary alloc] init];
}
return self;
}
+ (TestConfiguration *)sharedInstance
{
static TestConfiguration *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[TestConfiguration alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
-(void)setNextResult:(NSObject *)result
forCallToObject:(NSObject *)object
selector:(SEL)selector
{
NSString *className = NSStringFromClass([object class]);
NSString *selectorName = NSStringFromSelector(selector);
[self.results setObject:result
forKey:[[className stringByAppendingString:#":"] stringByAppendingString:selectorName]];
}
-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector
{
NSString *className = NSStringFromClass([object class]);
NSString *selectorName = NSStringFromSelector(selector);
return [self.results objectForKey:[[className stringByAppendingString:#":"] stringByAppendingString:selectorName]];
}
#end
Then you would define your "Mock" category to define mock methods , such as :
#import "MyWebService+Mock.h"
#import "TestConfiguration.h"
#implementation MyWebService (Mock)
-(void)mockFetchEntityWithId:(NSNumber *)entityId
success:(void (^)(Entity *entity))success
failure:(void (^)(NSError *error))failure
{
Entity *response = (Entity *)[[TestConfiguration sharedInstance] getResultForCallToObject:self selector:#selector(fetchEntityWithId:success:failure:)];
if (response == nil)
{
failure([NSError errorWithDomain:#"entity not found" code:1 userInfo:nil]);
}
else{
success(response);
}
}
#end
And finally, in the tests themselves, you would swizzle the mock method in the setup , and define the expected answer in each test, before the call
MyServiceTest.m
- (void)setUp
{
[super setUp];
//swizzle webservice method call to mock object call
MethodSwizzle([MyWebService class], #selector(fetchEntityWithId:success:failure:), #selector(mockFetchEntityWithId:success:failure:));
}
- (void)testWSMockedEntity
{
/* mock an entity response from the server */
[[TestConfiguration sharedInstance] setNextResult:[Entity entityWithId:1]
forCallToObject:[MyWebService sharedInstance]
selector:#selector(fetchEntityWithId:success:failure:)];
// now perform the call. You should be able to call STAssert in the blocks directly, since the success/error block should now be called completely synchronously.
}
Remark : in my example, the TestConfiguration uses class/selector as a key instead of object/selector. That means every object of the class will use the same answer for the selector. That is most likely your case, as webservice are often singleton. But it should be improved to an object/selector maybe using the objet's memory address instead of its class