UIAutomation testing with Cocos2d, possible? - cocos2d-iphone

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

Related

How to convert items of NSArray to utf16 (char16_t)?

I have a function that gets all contents of directory whether files or directories and I am using contentsOfDirectoryAtPath to collect the content of a directory then I save the names of files/directories into a container called contentsStore which accepts key&value items of UTF-16 string char16_t. look at the following code to make your vision clear:
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_dirPath error:nil];
for(unsigned int i= 0; i< [dirContents count]; i++){
if(isDir){
// `contentsStore` is key&value container that accepts utf-16 string (char16_t)
contentsStore.Add([[dirContents objectAtIndex:i] UTF8String], "directory");
} else {
contentsStore.Add([[dirContents objectAtIndex:i] UTF8String], "file");
}
}
Note that I don't post the entire code because it's big but I just added the important parts that related to the problem. Also, I am using Objective-C just as a bridge to achieve my goal to use Cocoa in macOS but the main language that I use is C++, so, the entire code is a combination of C++/Objective-C.
How to make the objectAtIndex method to output the item's content as UTF-16 char16_t?
the following will give you an idea. The [Filemanager defaultmanager] is actually supporting your task. You can convert the path to C-string and convert then into string of char16_t aka basic_string<char16_t>.
NSString *_dirPath = [[NSBundle mainBundle] bundlePath];
NSError *error = nil;
NSFileManager *manager = [[NSFileManager defaultManager] init];
NSArray *dirContents = [manager contentsOfDirectoryAtPath:_dirPath error:&error];
if (!error && dirContents.count ) {
for(unsigned int i = 0; i < [dirContents count]; i++){
NSString *path = [dirContents objectAtIndex:i];
BOOL isDir;
std::string usingkey = "file";
if ([manager fileExistsAtPath:path isDirectory:&isDir] && isDir) {
usingkey = "directory";
}
const char *fileRepresentation = [manager fileSystemRepresentationWithPath:path];
// function declared below..
std::u16string char16string = to_utf16(fileRepresentation);
// and use it to store your C++ storageObject, value&key pair
// don't know of what datatype is usingkey in your source
// just assumed std::string
contentsStore.Add(char16string, usingkey);
}
}
you will have to include the following in your .mm implementation
#include <string>
#include <codecvt>
#include <iostream>
#implementation Yourclassname
//std::u16string is same as basic_string<char16_t>
std::u16string to_utf16( std::string str )
{ return std::wstring_convert< std::codecvt_utf8_utf16<char16_t>, char16_t >{}.from_bytes(str); }
#end

AVAssetWriter fails with AVAssetWriterStatusFailed when reused

I am trying to use the AVAssetWriter to encode video. I am using ARC. What I am trying to do is stream video over a socket.
Now here is the problem. The setup I have works the first time around. I am unable to use the AVAssetWriter again, without restarting my application first. If I don't restart my application, when I call [m_writer startWriting] I receive the following error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x14d06fe0 {Error Domain=NSOSStatusErrorDomain Code=-12983 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12983)}
I made sure that file was deleted when the cleaup method was called.
I used iExplorer to navigate to the temp folder where the file was
being created. I also made sure that the status of the AVAssetWriter
after calling finshWriting is AVAssetWriterStatusFinished.
Edit: The error persists even If I used a different file name.
I create an instance of the VideoEncoder class ( code below) and then I feed it raw images. Then I wait for something to be written to the output file by the AVAssetWriter using a dispatch_source_set_event_handler(), i.e.:
m_inputFile = [NSFileHandle fileHandleForReadingAtPath: m_writer.path];
m_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [m_inputFile fileDescriptor], 0, m_readQueue);
dispatch_source_set_event_handler(m_readSource, ^{
// Read bytes from file and send over socket
}
dispatch_source_set_cancel_handler(m_readSource, ^{
[self cleanup];
}
dispatch_resume(m_readSource);
I have created a wrapper for the AVAssetWriter called VideoEncoder. Here is how I initialize the AVAssetWriter:
#implementation VideoEncoder : NSObject
{
AVAssetWriter* m_writer;
AVAssetWriterInput* m_writerInput;
AVAssetWriterInputPixelBufferAdaptor* m_pixelBufferAdaptor;
}
-(id) initWithPath:(NSString*)path width:(int)width height:(int)height parameters:(NSDictionary*) parameters
{
if (self = [super init])
{
self.path = path;
[[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
NSURL* url = [NSURL fileURLWithPath: self.path];
m_writer = [[AVAssetWriter alloc] initWithURL:url fileType:AVFileTypeQuickTimeMovie error: nil];
NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithInt: width], AVVideoWidthKey,
[NSNumber numberWithInt:height], AVVideoHeightKey,
parameters, AVVideoCompressionPropertiesKey,
nil];
m_writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
m_writerInput.expectsMediaDataInRealTime = YES;
[m_writer addInput:m_writerInput];
NSDictionary* pixelBufferOptions = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt: kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithInt: width*4], kCVPixelBufferBytesPerRowAllignmentKey,
[NSNumber numberWithInt; width], kCVPixelBufferWidthKey,
[NSNumber numberWithInt; height], kCVPixelBufferHeightKey,
nil];
m_pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
initWithAssetWriterInput: m_writerInput
sourcePixelBufferAttributes: pixelBufferOptions];
[m_writer startWriting];
[m_writer startSessionAtSourceTime: kCMTimeZero];
}
}
This is how I feed it raw images:
-(BOOL) encodeFrame:(CVPixelBufferRead)pixelBuffer time(CMTime)time;
{
if (m_writer.status == AVAssetWriterStatusFailed)
{
return NO;
}
if (m_writerInput.readyForMoreMediaData == YES)
{
if ([m_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:time])
{
return YES:
}
}
return NO;
}
This is how the pixel buffers are generated:
-(bool) encodeImage:(const char*) frameBuffer width:(int)width height:(int)height
{
CVPixelBufferRef pixelBuffer = NULL;
CVReturn ret;
ret = CVPixelBufferCraeteWithBytes(
kCFAllocatorDefault,
with,
height,
kCVPixelFormatType_32BGRA,
(void*) frameBuffer,
width * 4,
NULL, NULL, NULL, NULL, &pixelBuffer);
if (ret == kCVReturnSuccess && pixelBuffer)
{
CFTimeInterval currTime = CACurrentMediaTime();
if (m_frameIndex > 0 )
{
int ticks = (int)((currTime - m_prevTime) * 1000) / 30;
CMTime addTime;
ticks = ticks <= 0 ? 1 : ticks;
addTime = CMTimeMake(ticks, 30);
m_timestamp = CMTimeAdd(m_timestamp, addTime);
}
m_prevTime = currTime;
[m_impl encodeFrame: pixelBuffer time: m_timestamp];
CVBufferRelease(pixelBuffer);
m_frameIndex ++;
}
}
This is how I terminate the AVAssetWriter:
-(void) finishWritingWithCompletionHandler(void (^)(void))handler
{
[m_writerInput markAsFinished];
[m_writer finishWithCompletionHandler: ^{
handler();
}
}
And here is how I use the wrapper. I use this wrapper form a C++ class. Instantiation:
-(void) makeFilename
{
NSString* filename = [NSString* stringWithFormat: #"temp%d.tmp", 1];
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: filename];
return path;
}
bool CFrameEncoder::StartEncoding()
{
m_impl = CFRetain((__bridge*void)[[VideoEncoder alloc] initWithPath:[self makeFilepath] width:800 height:480 parameters: params]);
}
Dealloc:
bool CFrameEncoder::StopDecoding()
{
[(__bridge VideoEncoder*) m_impl terminate];
CFRelease(m_impl);
}
The terminate function (of the VideoEncoder class):
-(void) terminate
{
if (m_readSource)
{
dispatch_source_cancel(m_readSource);
}
}
// The dispatch source cancel handler
-(void) cleanup
{
m_readQueue = nil;
m_readSource = nil;
[m_inputFile closeFile];
NSString* path = m_writer.path;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[m_writer finishWithCompletionHandler:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
m_writer = nil;
[[NSFileManager defaultManager] removeItemAtPath: path error: nil];
}
The AVAssetWriter class can have only a limited number of instances. If your application goes over this limit, then the AVAssetWriter begins to fail with AVAssetWriterStatusFailed when you try to startWriting.
This means that there is something in my code that is preventing the AVAssetWriter from being deallocated. Does anybody know where?

How to set cookieAcceptPolicy for ephemeral NSURLSession

I am trying to use ephemeral NSURLSessions to provide separate cookie handling for several tasks within my app. These tasks are not directly bound to a UI.
Problem: Whatever I do, the cookieAcceptPolicy of the ephemeral NSHTTPCookieStorage remains NSHTTPCookieAcceptPolicyNever.
Here's my code:
// use a pure in-memory configuration with its own private cache, cookie, and credential store
__block NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
// do anything to accept all cookies
config.HTTPShouldSetCookies = YES;
config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
config.HTTPCookieStorage.cookieAcceptPolicy = NSHTTPCookieAcceptPolicyAlways;
__block NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask* task = [session dataTaskWithURL:[NSURL URLWithString:#"https://test.cgmlife.com/Catalogs"]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPCookieStorage* cookies = session.configuration.HTTPCookieStorage;
NSLog(#"%#", cookies);
NSLog(#"%lu", cookies.cookieAcceptPolicy);
}];
[task resume];
The output from NSLog is always:
Ephemeral <NSHTTPCookieStorage cookies count:0>
1
where 1 is the value for NSHTTPCookieAcceptPolicyNever (expected 0 for NSHTTPCookieAcceptPolicyAlways). The headers in the response are there.
What can I do to have the NSHTTPCookieStorage remember my cookies while the session is alive? I don't need and don't want any persistence. I just want to keep cookies in memory so that they are reused for further requests in the same session.
It looks like ephemeral sessions don't store cookies ever. eskimo1 says on the devforums:
ISTR that ephemeral session configurations don't work the way that
folks expect based on the documentation. I never got around to
looking at this in detail but it seems like you have. You should file
a bug about this; the implementation and documentation are clearly out
of sync, so one of them needs to be fixed.
It looks like the iOS9 ephemeralSessionConfiguration works as expected. On iOS8 though, it seems like the cookie storage returns Never for its policy and it can't be reset. The implementation appears to be no-storage despite the private class name, instead of memory-only.
For iOS8, I was able to substitute a rudimentary implementation though, and it seems to work (at least in the simulator with light testing). Implementing the new methods that take task objects was essential.
#import <Foundation/Foundation.h>
#interface MemoryCookieStorage : NSHTTPCookieStorage
#property (nonatomic, strong) NSMutableArray *internalCookies;
#property (atomic, assign) NSHTTPCookieAcceptPolicy policy;
#end
#implementation MemoryCookieStorage
- (id)init
{
if (self = [super init]) {
_internalCookies = [NSMutableArray new];
_policy = NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain;
}
return self;
}
- (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy {
return self.policy;
}
- (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy {
self.policy = cookieAcceptPolicy;
}
- (NSUInteger)_indexOfCookie:(NSHTTPCookie *)target
{
return [_internalCookies indexOfObjectPassingTest:^BOOL(NSHTTPCookie *cookie, NSUInteger idx, BOOL *stop) {
return ([target.name caseInsensitiveCompare:cookie.name] == NSOrderedSame &&
[target.domain caseInsensitiveCompare:cookie.domain] == NSOrderedSame &&
(target.path == cookie.path || [target.path isEqual:cookie.path]));
}];
}
- (void)setCookie:(NSHTTPCookie *)cookie
{
if (self.cookieAcceptPolicy != NSHTTPCookieAcceptPolicyNever)
{
#synchronized(_internalCookies) {
NSInteger idx = [self _indexOfCookie:cookie];
if (idx == NSNotFound)
[_internalCookies addObject:cookie];
else
[_internalCookies replaceObjectAtIndex:idx withObject:cookie];
}
}
}
- (void)deleteCookie:(NSHTTPCookie *)cookie
{
#synchronized(_internalCookies) {
NSInteger idx = [self _indexOfCookie:cookie];
if (idx != NSNotFound)
[_internalCookies removeObjectAtIndex:idx];
}
}
- (NSArray *)cookies
{
#synchronized(_internalCookies) {
return [_internalCookies copy];
}
}
static BOOL HasCaseSuffix(NSString *string, NSString *suffix)
{
return [string rangeOfString:suffix options:NSCaseInsensitiveSearch|NSAnchoredSearch|NSBackwardsSearch].length > 0;
}
static BOOL IsDomainOK(NSString *cookieDomain, NSString *host)
{
return ([cookieDomain caseInsensitiveCompare:host] == NSOrderedSame ||
([cookieDomain hasPrefix:#"."] && HasCaseSuffix(host, cookieDomain)) ||
(cookieDomain && HasCaseSuffix(host, [#"." stringByAppendingString:cookieDomain])));
}
- (NSArray *)cookiesForURL:(NSURL *)URL
{
NSMutableArray *array = [NSMutableArray new];
NSString *host = URL.host;
NSString *path = URL.path;
#synchronized(_internalCookies)
{
for (NSHTTPCookie *cookie in _internalCookies)
{
if (!IsDomainOK(cookie.domain, host))
continue;
BOOL pathOK = cookie.path.length == 0 || [cookie.path isEqual:#"/"] || [path hasPrefix:cookie.path];
if (!pathOK)
continue;
if (cookie.isSecure && [URL.scheme caseInsensitiveCompare:#"https"] != NSOrderedSame)
continue;
if ([cookie.expiresDate timeIntervalSinceNow] > 0)
continue;
[array addObject:cookie];
}
}
array = (id)[array sortedArrayUsingComparator:^NSComparisonResult(NSHTTPCookie *c1, NSHTTPCookie *c2) {
/* More specific cookies, i.e. matching the longest portion of the path, come first */
NSInteger path1 = c1.path.length;
NSInteger path2 = c2.path.length;
if (path1 > path2)
return NSOrderedAscending;
if (path2 > path1)
return NSOrderedDescending;
return [c1.name caseInsensitiveCompare:c2.name];
}];
return array;
}
- (NSArray *)sortedCookiesUsingDescriptors:(NSArray *)sortOrder
{
return [[self cookies] sortedArrayUsingDescriptors:sortOrder];
}
- (void)getCookiesForTask:(NSURLSessionTask *)task completionHandler:(void (^) (NSArray *taskCookies))completionHandler
{
NSArray *urlCookies = [self cookiesForURL:task.currentRequest.URL];
completionHandler(urlCookies);
}
- (void)setCookies:(NSArray *)newCookies forURL:(NSURL *)URL mainDocumentURL:(NSURL *)mainDocumentURL
{
NSString *host = mainDocumentURL.host;
for (NSHTTPCookie *cookie in newCookies)
{
switch (self.cookieAcceptPolicy)
{
case NSHTTPCookieAcceptPolicyAlways:
[self setCookie:cookie];
break;
case NSHTTPCookieAcceptPolicyNever:
break;
case NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain:
if (IsDomainOK(cookie.domain, host))
[self setCookie:cookie];
break;
}
}
}
- (void)storeCookies:(NSArray *)taskCookies forTask:(NSURLSessionTask *)task
{
NSURL *mainURL = task.currentRequest.mainDocumentURL ?: task.originalRequest.mainDocumentURL ?: task.originalRequest.URL;
[self setCookies:taskCookies forURL:task.currentRequest.URL mainDocumentURL:mainURL];
}
#end
It should be possible to test sessionConfiguration.HTTPCookieStorage.cookieAcceptPolicy == NSHTTPCookieAcceptPolicyNever after creating the ephemeral session to see if the HTTPCookieStorage needs to be replaced with an instance of the above class (shouldn't need it on iOS9). There are probably some bugs... I just needed this for a demo and it worked well enough for that. But they shouldn't be too hard to fix if any come up.

Why I often got ExcBadAccess in this code:

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

Memory leak in obj-c/c++ code

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.