Obj C: How to add description to a block object? - objective-c

I have NSMutableArray of void(^)() blocks and I'd like to debug what's going on inside of this collection. Right now if I try to print it I get:
(lldb) po self.blockArray
<__NSArrayM 0x1712f090>(
<__NSMallocBlock__: 0x19d64e30>,
<__NSMallocBlock__: 0x19d60b50>,
<__NSMallocBlock__: 0x19cbb2b0>,
<__NSMallocBlock__: 0x19cbaa30>,
<__NSMallocBlock__: 0x19c83100>,
<__NSMallocBlock__: 0x170cbef0>
)
I want to add a description string to a each block and see it instead of address (order of blocks is important). Since obj c blocks are objects too I have a felling its possible.. Can anyone share an idea how to do it?

Idea 1: Log the blocks and description when they are created and identify manually.
Idea 2: Only for debugging and experimenting, use at your own risk. Add a description method to NSBlock and an associated description object to each block. My test app:
#implementation AppDelegate
static char kAssociatedObjectKey;
typedef void (^MyBlockType)(void);
- (NSString *)myDescription {
NSString *description = [super description];
id object = objc_getAssociatedObject(self, &kAssociatedObjectKey);
if (object)
description = [description stringByAppendingFormat:#" %#", object];
return description;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
Class blockClass = NSClassFromString(#"NSBlock");
Method descriptionMethod = class_getInstanceMethod([self class], #selector(myDescription));
BOOL didAddMethod = class_addMethod(blockClass, #selector(description),
method_getImplementation(descriptionMethod), method_getTypeEncoding(descriptionMethod));
MyBlockType a = ^{};
objc_setAssociatedObject(a, &kAssociatedObjectKey, #"block a", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
MyBlockType b = ^{};
objc_setAssociatedObject(b, &kAssociatedObjectKey, #"block b", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
MyBlockType c = ^{};
objc_setAssociatedObject(c, &kAssociatedObjectKey, #"block c", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSArray *array = #[a, b, c];
NSLog(#"%#", array);
}
#end
PS. Maybe idea 2 isn't a good idea. I'm not familiar with the Objective-C runtime but I think I understand what I'm doing.

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.

Building 3 methods in Objective C

I'm having trouble with a school problem in Objective C. I need to build 3 methods. The first method tells you if someone is in a line. If nobody is in the line it tells you nobody is in the line otherwise it tells you who is in the line and it lists the names on a new line.
The second method adds names to the line.
The third method removes a name from the line and tells you who was removed.
First method:
-(NSString*)stringWithDeliLine:(NSArray*) deliLine{
NSString *empty = #"The line is currently empty.";
//Some kind of formatted string
if(deliLine == nil || [deliLine count] == 0)
{
empty;
}
else
{
//formatted string
}
//not sure how to return either empty or formatted string
}
Second Method:
-(void)addName:toDeliLine:(NSString*)name:(NSMutableArray*)deliLine{
[deliLine addObject:name];
}
The third method I was going to use removeObject but the instructions said not to use it so I have no idea where to start.I have the signature I think.
-(NSString*)serveNextCustomerInDeliLine:(NSMutableArray*)deliLine{
return nil;
}
For the first method I'm not sure why my literal string won't work in the if statement. I thought I was saying look at the array if nothing is in the array then it's the first object and show the string literal. else show some kinda of formatted string. I've tried all kinds of strings but none seem to be working so that's why I have the comment formatted string. If someone could give me a hint that would be great. I don't need the answer just a clue on what to think about. This is long post sorry.
A possible implementation can be the following. Please note that I have not testes edge cases and I wrote the code without Xcode support
#import <Foundation/Foundation.h>
#interface Line : NSObject
- (NSString*)printLine;
- (void)addCustomer:(NSString*)customer;
- (NSString*)removeCustomer:(NSString*)customer;
#end
#import "Line.h"
#interface Line ()
#property (nonatomic, strong, readwrite) NSMutableArray<NSString*> *customers;
#end
#implementation Line
- (instancetype)init {
self = [super init];
if (self) {
_customers = [NSMutableArray array];
}
return self;
}
- (NSString*)printLine {
NSUInteger count = self.customers.count;
if(count == 0) {
return #"Empty";
}
NSMutableString *descr = [NSMutableString string];
for (NSString *customer in self.customers) {
[descr appendString:[NSString stringWithFormat:#"%# ", customer]];
}
return [descr copy];
}
- (void)addCustomer:(NSString*)customer {
[self.customers addObject:customer];
}
- (NSString*)removeCustomer:(NSString*)customer {
NSUInteger index = [self.customers indexOfObject:customer];
if(index == NSNotFound) {
return [NSString stringWithFormat:#"%# not removed", customer];
}
NSString *removedCustomer = [self.customers objectAtIndex:index];
[self.customers removeObjectAtIndex:index];
return removedCustomer;
}
#end
Usage:
Line *line = [[Line alloc] init];
[line addCustomer:#"customer"];
NSLog(#"%#", [line printLine]);
NSLog(#"%#", [line removeCustomer:#"customer"]);
NSLog(#"%#", [line printLine]);
Edit:
I've updated my answer, passing the array as a parameter is not necessary, just initialize deliLine as a mutable array property.
For you first method, you could do the following,
- (NSString *)deliLineContents {
NSString *empty = #"The line is currently empty.";
NSMutableString *namesInQueue = [[NSMutableString alloc] init];
if(self.deliLine == nil || [self.deliLine count] == 0) {
return empty;
} else {
// Loop through your array and return a string of all the names
for (NSString *string in self.deliLine ) {
[namesInQueue appendString:string];
}
}
return [NSString stringWithString:namesInQueue];
For your second method, you're already pretty much there, maybe look up how to construct method signatures.
- (void)addNameToDeliLine:(NSString*)name {
[self.deliLine addObject:name];
}
For your third method, not sure if this meets your requirement, if not let me know.
- (NSString *)customerRemovedFromLine {
// I've making an assumption that you want to remove the first customer
NSString *servedCustomer = [self.deliLine objectAtIndex:0];
[self.deliLine removeObjectAtIndex:0];
return servedCustomer;
}
You probably don't need to pass deliLine around, just create it as a property and access it with self.deliLine. Anyway hope this helps, good luck.

New in Objective C. Simple bug at runtime. Thread 1: breakpoint 1.1

I am learning Objective C and I have tried to override my superclass (NSObject) description method in my BNRItem class. Though it seems like I have done everything right, my NSLog does not seem to use my overridden description method. Instead I see a Thread 1 Breakpoint 1.1 in my definition of the description method and more precisely, where I defined the descriptionString.
Here is my console output.
2015-08-30 20:49:00.622 RandomItems[46034:1002101] Zero
2015-08-30 20:49:00.623 RandomItems[46034:1002101] One
2015-08-30 20:49:00.623 RandomItems[46034:1002101] Two
2015-08-30 20:49:00.623 RandomItems[46034:1002101] Three
(lldb)
My main.m file:
#import <Foundation/Foundation.h>
#import "BNRItem.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
// Create a mutable array object, store its address in items variable...
NSMutableArray *items = [[NSMutableArray alloc] init];
//Send the message addObject: to the NSMutableArray pointed
//by the variable item, passing a string each time
[items addObject:#"One"];
[items addObject: #"Two"];
[items addObject: #"Three"];
// Send another message, insertObject:atIndex;, to that same array object
[items insertObject:#"Zero" atIndex:0];
// For every item in the items array ...
for (NSString *item in items){
//Log the description of item
NSLog(#"%#", item);
}
// Create a BNRItem instance and log its instance variables in the console
BNRItem *item = [[BNRItem alloc] init];
// Set item name to Red Sofa
item.itemName = #"Red Sofa";
item.serialNumber= #"A1B2C";
item.valueInDollards = 100;
//
NSLog(#"%#", item);
//Destroy the mutable array object
items = nil ;
}
return 0;
}
My header file:
#import <Foundation/Foundation.h>
#interface BNRItem : NSObject
{
NSString *_itemName;
NSString *_serialNumber;
int _valueInDollards;
NSDate *_dateCreated;
}
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;
- (void)setValueInDollards:(int)v;
- (int)valueInDollards;
- (NSDate *)dateCreated;
#end
And finally, my implementation file:
#import "BNRItem.h"
#implementation BNRItem
- (void)setItemName:(NSString *)str
{
_itemName = str;
}
- (NSString *)itemName
{
return _itemName;
}
- (void)setSerialNumber:(NSString *)str
{
_serialNumber = str;
}
- (NSString *)serialNumber
{
return _serialNumber;
}
- ( void )setValueInDollards:(int)v
{
_valueInDollards = v;
}
- ( int )valueInDollards
{
return _valueInDollards;
}
-( NSDate * )dateCreated
{
return _dateCreated;
}
- ( NSString * )description
{
NSString *descriptionString = [[NSString alloc] initWithFormat:#"%# (%#): Worth %d, recorded on %#", self.itemName, self.serialNumber, self.valueInDollards, self.dateCreated];
return descriptionString;
}
#end
It sounds like you have simply set a breakpoint on a line in your -description method. The debugger is stopping your program at the breakpoint. There's no indication of an actual error.
If you hit the continue button in Xcode, your program would probably proceed just fine.
You can disable the breakpoint, tell Xcode to ignore all breakpoints, or delete the breakpoint if you don't want to break there. The breakpoint will look like a blue arrow in the margins to the left of your code. Right-click or Control-click on it to see options.
To make Xcode ignore breakpoints, toggle the breakpoint button in the debugging toolbar. It also looks like a right-pointing arrow. It will be filled in blue if breakpoints are enabled. It will be an outline if they're disabled.

Get property name as a string

I need a way to pass a property and get the name assigned to it. Any suggestions?
#property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return #"crazyObject"
You can try this:
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([self class], &propertyCount);
NSMutableArray * propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
NSLog(#"Names: %#", propertyNames);
It's as simple as this...expanding upon what Chuck already mentioned:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(#selector(prop))
#endif
You then use it like so:
NSString *strProp = STR_PROP(myProperty);
Background
Keep in mind that properties are really just, to quote Apple, "a syntactical shorthand for declaring a class’s accessor methods." In fact, by itself, the #property declaration doesn't even work. Your #synthesize statement translates the #property into the equivalent of two methods:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Which one is used depends on the context surrounding your self.crazyObject. (#synthesize also creates a matching instance variable if you didn't do it yourself.) The offshoot of all this is that you can't really translate to and from a property with one single method.
Proposed Solution
You can use what Apple already provides:
NSString *foo = NSStringFromSelector(#selector(myClassProperty));
Or do something custom:
Given that self.crazyObject really translates to either [self crazyObject] or [self setCrazyObject:foo] by the time your code is running, ou'll probably need two methods, like:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
You might then want at least 2 companion methods such as:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
Within these methods, you can use the Foundation functions NSStringFromSelector and NSSelectorFromString to convert back and forth between SEL and NSString. Use whatever string manipulations you like to convert back and forth between your setter string (setCrazyObject) and your property name (crazyObject).
A complete solution is hard to provide without knowing the exact use case, but hopefully this provides some more clues for anyone trying to accomplish something similar. There might even be some useful things made possible by combining this approach with Oscar's answer.
Here is a function that returns the name of an ivar, so basically it not only returns the properties but any ivar of the class. I haven't found a way to get the property directly so I used the ivar trick.
#import <objc/objc.h>
/// -----
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
After searching and debugging i find solution for me...
Added #import <objc/runtime.h>
Methods object_getIvar(id obj, Ivar ivar) send bad access and app crashes. i modify some code and it worked great:
+(NSString*)stringWithProperty:(id)property withClass:(id)controller
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([controller class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([controller valueForKey:name] == property)
{
break;
}
}
free(ivars);
}
return name;
}
Modifying the solution, it works when your object is allocated already, otherwise it returns nil:-
NSString * NSStringFromProperty(NSObject* property, NSObject* class)
{
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([class class], &propertyCount);
NSString *name = nil;
for (unsigned int i = 0; i < propertyCount; ++i)
{
name = [NSString stringWithUTF8String:property_getName(properties[i])];
NSObject *object = [class valueForKey:name];
if (object != nil && object == property)
{
break;
}
else
{
name = nil;
}
}
free(properties);
return name;
}
You can use
NSString *str = NSStringFromSelector(#selector(crazyObject));
The good thing about this approach is that:
Xcode will autocomplete word crazyObject for you.
When later on you will change the property name from crazyObject to myCrazyObject, Xcode will add a warning saying "unrecognized selector!" -- pretty good for debugging.
I use this method so often, that I even created a function, which allows to write less letters:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Now your final solution can look like this:
NSString *str = sfs(#selector(crazyObject));
From Get property name as string, without using the runtime reference library, just define:
#define propertyKeyPath(property) (#""#property)
#define propertyKeyPathLastComponent(property) [[(#""#property) componentsSeparatedByString:#"."] lastObject]
And then you can do something like this:
NSLog(#"%#", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
You may check my approach at Gist to get the string for a property with autocompletion and compile-time check.
How to use:
Get the property name for a class:
#interface AnyClass : NSObject
#property (strong) NSData *data;
#end
// == My approach ==
// C string for a class
PropertyNameForClass(AnyClass, data); // ==> "data"
// NSString for a class
PropertyStringForClass(AnyClass, data); // ==> #"data"
// Bad approach (no autocompletion; no compile-time check):
NSString *propertyName = #"data";
Get the property name for a protocol:
#protocol AnyProtocol
#property (strong) NSDate *date;
#end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> #"date"
Unconventional, hacky, ugly, late, but... as strong-named as it gets and works like a charm:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:#"."] lastObject] componentsSeparatedByString:#" "] lastObject] stringByReplacingOccurrencesOfString:#"]" withString:#""] : #""
Sample usage:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname)); // surname
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate])); // birthDate
...

Anyway to get string from variable name?

Say I have my class
#interface Person : NSObject { NSString *name; }
I need to get the name of NSString's within my class
Person *person = [[Person alloc] init];
NSLog(#"Name of variable %s\n", _NameofVariable_(person->name));
Thanks for the answers, here's the solution I came up from the replies
//returns nil if property is not found
-(NSString *)propertyName:(id)property {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self, thisIvar) == property)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
As easy as
#define VariableName(arg) (#""#arg)
Then you do:
NSObject *obj;
NSString *str = VariableName(obj);
NSLog(#"STR %#", str);//obj
You can get the names of a class's instance variables with the Objective-C runtime API function class_copyIvarList. However, this is rather involved, rarely done and almost never the best way to accomplish something. If you have a more specific goal in mind than mere curiosity, it might be a good idea to ask about how to accomplish it in Objective-C.
Also, incidentally, person.name doesn't specify an instance variable in Objective-C — it's a property call. The instance variable would be person->name.
You might use preprocessor stringification and a bit of string twiddling:
NSUInteger lastIndexAfter(NSUInteger start, NSString *sub, NSString *str) {
NSRange found = [str rangeOfString:sub options:NSBackwardsSearch];
if(found.location != NSNotFound) {
NSUInteger newStart = NSMaxRange(found);
if(newStart > start)
return newStart;
}
return start;
}
NSString *lastMember(NSString *fullName) {
if(!fullName) return nil;
NSUInteger start = 0;
start = lastIndexAfter(start, #".", fullName);
start = lastIndexAfter(start, #"->", fullName);
return [fullName substringFromIndex: start];
}
#define NSStringify(v) (##v)
#define _NameofVariable_(v) lastMember(NSStringify(v))
If the person object is exposed as a property of the class, you can use objc_msgSend to get the value.
So, if you could access person using
[object person]
You could also do
objc_msgSend(object, "person")
For more details on message sending, including how to pass arguments to methods, see the Objective-C Runtime Programming Guide section on Messaging
The following works as a macro:
#define STRINGIZE(x) #x