I'm using instruments and I have a lot of memory being leaked by this method. It is used everywhere in my app, and when I built it, I used some c++ (i think).
Please let me know where the leak is happening if possible?
Instruments just says Malloc 16 bytes and has about 60 MB worth of these guys...
/*
Parses a dicionary into an object
*/
+(id)makeObject:(id)object fromDictionary:(NSDictionary *)dict{
#autoreleasepool {
NSArray *keys = #[#"#\"NSMutableArray\""];
//Init result
id result = object;
//Iterate every key
for (id key in [dict allKeys]) {
//Convert key to const char
const char * c = [key cStringUsingEncoding:NSASCIIStringEncoding];
//Use c to see if the class has this property
if (class_getProperty([object class], c)) {
//get the property
objc_property_t property = class_getProperty([result class], c);
//Get the property name and type
const char *name = property_getName(property);
const char *type = property_copyAttributeValue(property, "T");
//Cast const char to string
NSString *pNameString = [NSString stringWithFormat:#"%s",name];
NSString *typeString = [NSString stringWithFormat:#"%s",type];
//Add relationships
if ([keys containsObject:typeString]) {
//Get array of objects
NSArray *relationship = [dict objectForKey:pNameString];
NSMutableArray *allSubObjects = [[NSMutableArray alloc]init];
//Parse each individual object
for (NSDictionary *relationshipObj in relationship) {
//Create class from relationship
Class class = NSClassFromString(pNameString);
//Create object
id sub = [self makeObject:[[class alloc]init] fromDictionary:relationshipObj];
[allSubObjects addObject:sub];
}
[result setValue:allSubObjects forKey:pNameString];
}else{
//If so set the property for the key
[result setValue:[dict objectForKey:key] forKey:key];
}
}else{
//NSLog(#"%# did not respond to : %#", result, key);
}
}
//Return result
return result;
}
}
EDIT:
Instruments shows these two items most likely the culprits. What's the best way to fix this?
//Cast const char to string
NSString *pNameString = [NSString stringWithFormat:#"%s",name];
NSString *typeString = [NSString stringWithFormat:#"%s",type];
1) [NSString cStringUsingEncoding]
This allocate some memory internally which will not freed until the NSString object is deallocated. I don't know exactly but could it be possible repeatedly sending cStringUsingEncoding incur unused memory building up?
2) property_copyAttributeValue
Documentation says,
Return Value A C array of pointers of type objc_property_t describing
the properties declared by proto. Any properties declared by other
protocols adopted by this protocol are not included. The array
contains *outCount pointers followed by a NULL terminator. You must
free the array with free().
You need to free const char *type.
3) NSMutableArray *allSubObjects = [[NSMutableArray alloc]init]
It should be NSMutableArray *allSubObjects = [NSMutableArray array].
4) Object creation
Class class = NSClassFromString(pNameString);
//Create object
id sub = [self makeObject:[[class alloc]init] fromDictionary:relationshipObj];
I'd put this code block in its own autorelease pool.
[class alloc]init] is also suspicious.
Related
I've got a c++ function that gets a std::map object and convert it to CFMutableDisctionryRef in order to use it on method CFNotificationCenterPostNotification. Here's my implementation :
void IPCNotificationSender::send(const char *identifier, map<const char *, const char *> dict)
{
NSMutableDictionary *myDict = [NSMutableDictionary dictionary];
CFStringRef cfIdentifier = CFStringCreateWithCString(NULL, identifier,
kCFStringEncodingMacRoman);
for (std::map<const char *, const char *>::iterator it=dict.begin(); it!=dict.end(); ++it)
{
NSString *key = [NSString stringWithUTF8String:it->first];
NSString *val = [NSString stringWithUTF8String:it->second];
myDict[key] = key;
}
CFMutableDictionaryRef myCFDict = (CFMutableDictionaryRef)CFBridgingRetain(myDict);
CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(), cfIdentifier, NULL, myCFDict, TRUE);
CFRelease(myCFDict);
CFRelease(cfIdentifier);
}
However, there seems to be a memory leak in the NSString *key object where it should be released automatically. I've tried to implement the conversion on top of objective-C function type and still got the same results... I tend to believe that the mixture between c++ and objective-C, although valid, causes some issues with objective-c garbage collector.
Where did I go wrong in my implementation ?
thanks
C++ issues:
this map looks bad. it should be map<string, string>
you are passing map by value not by const rerence
Objective C issue:
Based on clues which gives accepted answer I suspect what is there actual problem.
Your C++ code runs continuously without reaching auto release pool. So when you are using Objective C API where auto release pool is involved this objects are not getting released since auto release pool never gets control.
So I would write this like this:
NSString *ConvertToObjC(const string& s)
{
return [NSString stringWithUTF8String: s.c_str()];
}
NSDictionary *ConvertToObjC(const map<string, string>& cppMap)
// here I use templates which do lots of magic, but this is off topic,
{
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity: cppMap.count()];
for (const auto& x : cppMap)
{
result[ConvertToObjC(x.first)] = ConvertToObjC(x.second);
}
return result;
}
void IPCNotificationSender::send(const string& identifier,
const map<string, string>& cppMap)
{
#autoreleasepool {
auto ident = ConvertToObjC(identifier);
auto myDic = ConvertToObjC(cppMap);
CFNotificationCenterPostNotification(
CFNotificationCenterGetDistributedCenter(),
(CFStringRef)CFBridgingRetain(ident),
NULL,
(CFDictionaryRef)CFBridgingRetain(myDict),
TRUE);
}
}
I have stumbled in the same problem, there seems to be a problematic behaviour of the memory management in shared c++/objective c projects.
The solution was to create objects which you can manually free them.
In your code, try the following:
for (std::map<const char *, const char *>::iterator it=dict.begin(); it!=dict.end(); ++it)
{
CFStringRef key = CFStringCreateWithCString(NULL, it->first,
kCFStringEncodingMacRoman);
CFStringRef val = CFStringCreateWithCString(NULL, it->second,
kCFStringEncodingMacRoman);
myDict[(__bridge NSString * _Nonnull __strong)(key)] = (__bridge NSString * _Nonnull __strong)(val);
CFRelease(key);
CFRelease(val);
}
Thanks in advance for taking your time out for reading and hoping to assist me. I am relatively new to coding and the current problem I am facing is;
I have built a sqlite3 database, with three columns which are respectively stored as; string, float and an int. When trying to read and represent the columns, I am having trouble in finding a method in order to represent the float and int values.
The three properties which are stores in the database are set as;
#property (nonatomic, strong) NSString *name;
#property (nonaomtic, strong) NSNumber *price;
#property (nonaomtic, strong) NSNumber *quantity;
I'm not sure if maybe changing the latter two properties into float and int would make a difference?
However, the main confusion for me lies in the following code I have made in order to call on the data base;
-(void)readDataFromDatabase{
[self.stock removeAllObjects];
sqlite3 *database;
if (sqlite_open([self.databasePath UTF8String], &database) == SQLITE_OK) {
char *sqlStatement = "select * from entries";
sqlite3_stmt *compiledStatement;
if (sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK){
while(sqlite3_step(compiledStatement) == SQLITE_ROW{
char *n = sqlite3_column_text(compiledStatement, 1);
char *p = sqlite3_column_int(compiledStatement, 2);
chat *q = sqlite3_column_int(compiledStatement, 3);
NSString *name = [NSString stringWithUTF8String:n];
NSNumber *price = [NSString stringWithUTF8String:p];
NSNumber *quantity = [NSString stringWithUTF8String:q];
Data *data = [[Data alloc] initWithData:name thePrice: price theQuantity:quantity];
[self.stock addObject:data];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
}
My common sense tells me the problem lies in this section;
NSString *name = [NSString stringWithUTF8String:n];
NSNumber *price = [NSString stringWithUTF8String:p];
NSNumber *quantity = [NSString stringWithUTF8String:q];
I'm not sure how I should approach the NSNumber? I'm aware the NSString stringWithUTF8String is not correct, but I don't know the equivalent for NSNumber.
I would really appreciate any insight and help on this matter.
Thank you!
You are on the right track with what you've mentioned for float and int. Float does not need to be referenced like NSString because it's size does not vary between 32-bit and 64-bit systems. If your integer is larger then I would recommened using NSInteger for that value:
char *n = sqlite3_column_text(compiledStatement, 1);
float p = sqlite3_column_text(compiledStatement, 2);
int q = sqlite3_column_text(compiledStatement, 3);
NSString *name = [NSString stringWithUTF8String:n];
float price = p;
NSInteger quantity = q;
Note: You don't the * for the float or int values, since they are not pointers.
You might even be able just to do (If you are already declaring the properties in your header):
name = [NSString stringWithUTF8String:sqlite3_column_text(compiledStatement, 1)];
price = sqlite3_column_text(compiledStatement, 2);
quantity = sqlite3_column_text(compiledStatement, 3);
To check the values you can use NSLog:
NSLog(#"%# %.02f %li", name, price, quantity);
For completeness sake, if you wanted to make the float and int into NSNumber:
NSString *name = [NSString stringWithUTF8String:n];
NSNumber *price = [NSNumber numberWithFloat:p];
NSNumber *quantity = [NSNumber numberWithInt:q];
With the properties in your header declared:
name = [NSString stringWithUTF8String:n];
value = [NSNumber numberWithFloat:p];
quantity = [NSNumber numberWithInt:q];
NSLog(#"%# %#.02f %#", name, price, quantity);
There are multiple ways to get your result, and that's what makes Objective-C quite flexible.
I uses ARC
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
Not much information is given. It goes straight to main
int main(int argc, char *argv[])
{
#autoreleasepool {
//int retVal = UIApplicationMain(argc, argv, nil, nil);
int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([newIsiKotaAppDelegate class]));
return retVal;
}
}
I manage to spot that the last code executed is this:
self.lblTimeLeft.attributedText = arString;
I provided additional code to test things out
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
PO1(#"Hello World");
while(false);// Error happen after this code is executed
}
The code is part of the routine that provides UITableViewCell to display. So error happen after
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = indexPath.row;
NSDictionary * rowDictionary = _marManagedObjectArray [row];
BGCinemaScheduleCell * BCS = [[BGCinemaScheduleCell alloc]init];
BCS.dicCinemaDictionary =rowDictionary;
return BCS; //This is where the error happen
}
It seems that the NSAttributedString works fine till it's actually displayed or something.
The error seems to happen on
TAttributes::TAttributes(__CFDictionary const *)
The content of asSchedule is usual
-(NSAttributedString *) asSchedule:(NSArray *) arTimes
{
NSMutableArray * timeBefore = [NSMutableArray array];
NSMutableArray * timeAfter = [NSMutableArray array];
for (NSString * strTime in arTimes) {
NSDate * date = [NSDate dtWithCurrentDateAndProvidedTime: strTime];
NSTimeInterval dblInterval =[date timeIntervalSinceNow];
if (dblInterval>0)
{
[timeAfter addObject:strTime];
}
else{
[timeBefore addObject:strTime];
}
}
NSString * strTimeBefore = [timeBefore componentsJoinedByString:#" "];
NSString * strTimeAfter = [timeAfter componentsJoinedByString:#" "];
NSString *yourString = [NSString stringWithFormat:#"%# %#", strTimeBefore, strTimeAfter];
// start at the end of strTimeBefore and go the length of strTimeAfter
NSRange boldedRange = NSMakeRange([strTimeBefore length] + 1, [strTimeAfter length]);
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:yourString];
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
PO1(NSStringFromRange(boldedRange));
PO1(#(attrString.length));
return attrString;
}
Update:
I changed self.lblTimeLeft.attributedText = arString; to self.lblTimeLeft.attributedText = arString.copy; to no avail.
I wonder if this could be the problem:
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
I think the original sample use KCFont something instead of NSFontAttributeName
You were almost there:
I wonder if this could be the problem: [attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
Your code says:
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
...
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
It should say:
UIFont *boldFont = [UIFont boldSystemFontOfSize:12];
...
[attrString addAttribute:NSFontAttributeName value:boldFont range:boldedRange];
According to Table 1 of the docs (link below) and my own app code, you should be passing an NSFont not a font name.
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/AttributedStrings/Articles/standardAttributes.html#//apple_ref/doc/uid/TP40004903-SW2
Can't tell from the code whether your project uses ARC or not, but it looks like your asSchedule method returns a string with a refcount of 0. It's up to you to retain a reference to that string, either old-school using the retain keyword (probably a bad idea) or by assigning it to a property declared with the strong keyword (or retain, if pre-ARC).
The best tool for debugging this stuff is Instruments, using the Zombies inspection. If I am right that you are trying to use the string after its refcount hits 0 and the memory has been released, you'll see the history of refcount increments and decrements pretty clearly.
Try alloc and initing the Attributedstring and then assign it to the self.lblTimeLeft.attributedText
I have a sample project here on github where I created a c++ wrapper class for an external C++ library that I want to use in Objective-C.
I don't understand why my returned pointers are sometimes correct and sometimes wrong. Here's sample output:
Test Data = 43343008
In Compress 43343008
Returned Value = 43343008
Casted Value = 43343008
Test Data = 2239023
In Compress 2239023
Returned Value = 2239023
Casted Value = 2239023
Test Data = 29459973
In Compress 29459973
Returned Value = 29459973
Casted Value = l.remote
Test Data = 64019670
In Compress 64019670
Returned Value =
Casted Value = stem.syslog.master
In the above output you can see that the 1st and 2nd click of the button outputs the results I was expecting. In each of the other clicks either the returned value or casted value are invalid. I'm assuming this is because my pointer is pointing to an address I wasn't expecting. when running the app multiple times, any button click could be right or wrong.
I also tried with a single thread but experienced similar results.
The complete code is on github but here are the important bits.
ViewController.m
#import "ViewController.h"
extern const char * CompressCodeData(const char * strToCompress);
#implementation ViewController
...
// IBAction on the button
- (IBAction)testNow:(id)sender
{
[self performSelectorInBackground:#selector(analyze) withObject:nil];
}
- (void)analyze
{
#synchronized(self) {
const char *testData = [[NSString stringWithFormat:#"%d",
(int)(arc4random() % 100000000)] UTF8String];
NSLog(#"Test Data = %s", testData);
const char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
}
}
#end
SampleWrapper.cpp
#include <iostream>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
using namespace std;
extern "C"
{
extern void NSLog(CFStringRef format, ...);
/**
* This function simply wraps a library function so that
* it can be used in objective-c.
*/
const char * CompressCodeData(const char * strToCompress)
{
const string s(strToCompress);
// Omitted call to static method in c++ library
// to simplify this test case.
//const char *result = SomeStaticLibraryFunction(s);
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
}
}
You are returning a pointer to at object that has been deallocated.
const string s(strToCompress);
…
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
s does not exist after CompressCodeData() function is over, so the pointer to it's internal memory is invalid.
You could allocate a chunk of memory to hold the response, but it would be up to the caller to release it.
char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
free(compressed);
NSLog(#"Casted Value = %#\n\n", casted);
…
const char * CompressCodeData(const char * strToCompress)
…
char *result = strdup(s.c_str());
Another solution is to pass in the memory to store the data into.
char compressed[2048]; // Or whatever!
CompressCodeData(testData, compressed, sizeof(compressed));
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
…
void CompressCodeData(const char * strToCompress, char *result, size_t size)
…
s.copy(result, size - 1);
result[s.length() < size ? s.length() : size-1] = '\0';
Is it possible to use UIAutomation with cocos2d or any opengl application for that matter?
Specifically I want to use the zucchini framework to test my cocos2d game but that just uses UIAutomation anyway.
You can create custom steps in Zucchini and specify the coordinates to tap, e.g.
'Choose the red bird' : ->
target.tap({x:278, y:36})
'Press Fire' : ->
target.tap({x:170, y:260})
So, I got started with calabash-iOS and expanding on its backdoor. This is just for starters but with this you can get the accessibility label of the current CCScene, so you can check what screen is currently on and thus use for scripting actions. I'm not used to working with objc runtime, but as you can see it's possible to get properties, methods etc. A bit more digging and it should be possible to wrap more functionality, and hopefully something to wrap the cocos2d CCNode structure as well. This is a work in progress.
To use this you need to install https://github.com/calabash/calabash-ios and then implement the below function in the app delegate. Don't forget to set .accessibilityLabel to something like #"menu", #"game" or similar in your code. Optimally, only for the *-cal target, you don't want this code in production builds.
-(NSString*)calabashBackdoor:(NSString*)aIgnorable {
DLog(#"calabashBackdoor: %#", aIgnorable);
// UIApplication* app = [UIApplication sharedApplication];
if (aIgnorable != nil) {
NSArray* p = [aIgnorable componentsSeparatedByString:#" "];
NSString* command = [p objectAtIndex:0];
if ([command isEqualToString:#"getCurrentSceneLabel"]) {
CCDirector* director = [CCDirector sharedDirector];
DLog(#"director.runningScene.accessibilityLabel: %#", director.runningScene.accessibilityLabel);
return director.runningScene.accessibilityLabel;
}
else if ([command isEqualToString:#"class_copyMethodList"]) {
CCDirector* director = [CCDirector sharedDirector];
id inspectThisObject = director.runningScene;
DLog(#"inspectThisObject: %#, %#", [inspectThisObject class], inspectThisObject);
unsigned int count;
// To get the class methods of a class, use class_copyMethodList(object_getClass(cls), &count).
Method* methods = class_copyMethodList(object_getClass(inspectThisObject), &count);
//NSMutableString* returnstring = [NSMutableString string];
NSMutableArray* arrayOfMethodnames = [NSMutableArray array];
if (methods != NULL) {
for (int i = 0; i < count; i++) {
Method method = methods[i];
NSString* stringMethod = NSStringFromSelector(method_getName(method)); //NSStringFromSelector(method->method_name);
[arrayOfMethodnames addObject:stringMethod];
}
// An array of pointers of type Method describing the instance methods implemented by the class—any instance methods implemented by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
free(methods);
}
DLog(#"arrayOfMethodnames: %#", arrayOfMethodnames);
return [arrayOfMethodnames componentsJoinedByString:#","];
}
else if ([command isEqualToString:#"class_copyPropertyList"]) {
CCDirector* director = [CCDirector sharedDirector];
id inspectThisObject = director.runningScene;
DLog(#"inspectThisObject: %#, %#", [inspectThisObject class], inspectThisObject);
unsigned int count;
// An array of pointers of type objc_property_t describing the properties declared by the class. Any properties declared by superclasses are not included. The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
//
// If cls declares no properties, or cls is Nil, returns NULL and *outCount is 0.
//
objc_property_t* properties = class_copyPropertyList(object_getClass(inspectThisObject), &count);
NSMutableArray* arrayOfProperties = [NSMutableArray array];
if (properties != NULL) {
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
const char* CCS = property_getName(property);
NSString* str = [NSString stringWithUTF8String:CCS];
[arrayOfProperties addObject:str];
}
free(properties);
}
DLog(#"arrayOfProperties: %#", arrayOfProperties);
return [arrayOfProperties componentsJoinedByString:#","];
}
else {
DLog(#"Unhandled command: %#", command);
}
}
return #"calabashBackdoor nil!";
}
In Prefix.pch put this
#ifdef DEBUG
# define DLog( s, ... ) NSLog( #"<%p %#:(%d)> %#", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
# define DLog(...) /* */
#endif
#define ALog(...) NSLog(__VA_ARGS__)
When you get calabash-ios and running, add this in step_definitions/somesteps.rb:
Then(/^I backdoor (.+)$/) do |x|
backdoor("calabashBackdoor:", x)
end