With the new xcode7 Apple introduced generics and nullability to Objective-C ( Developer guide )
But it seems to be very different from what we have on swift.
Nullability:
- (nonnull NSString *)something {
return nil;
}
This should raise a warning! And you can even assign the return value of this method to a nonnull variable like:
//#property (copy, nonnull) NSString *name
obj.name = [obj something];
Generics:
Looking this example:
#property (nonatomic, strong, nonnull) NSMutableArray <UIView *> *someViews;
a warning is raised when something different from a UIView is inserted on the array
[self.someViews addObject:#"foobar"]; //<- this raises an error
but not in this case:
self.someViews = [#[#"foobar"] mutableCopy];
nor in this case:
NSString *str = [self.someViews firstObject];
So the question is, I'm using generics and nullability in a wrong way or they are far away from the Swift implementation?
self.someViews = [#[#"foobar"] mutableCopy];
mutableCopy is inherited from NSObject, where it is declared to return id. It is not declared by NSArray specifically and NSArray des not decide the return type.
NSString *str = [self.someViews firstObject];
This does give a warning for me.
Related
I think it's very difficult to print out the value of all properties of any class in objective-c, in the case the type of the property is complex.
But if the class that contains properties with simple types (like, NSString, int, double, boolean), is there any way to NSLog automatically instead of NSLog manually the value of each property?
Updated:
All the solutions you gave me are still manually. Is there any way like iterate through all properties of a class, and NSLog the variable_name and the variable_value. That's what I expected.
You can do this by overriding -(void)description method.
Example:
Let's say we have simple Car class.
#interface Car : NSObject
#property (copy, nonatomic) NSString *model;
#property (copy, nonatomic) NSString *make;
#property (strong, nonatomic) NSDate *registrationDate;
#property (assign, nonatomic) NSInteger mileage;
#property (assign, nonatomic) double fuelConsumption;
#end
#implementation
- (NSString*)description {
return [NSString stringWithFormat:#"<%#:%p %#>",
[self className],
self,
#{ #"model" : self.model,
#"make" : self.make,
#"registrationDate": self.registrationDate,
#"mileage" : #(self.mileage),
#"fuelConsumption" : #(self.fuelConsumption)
}];
}
#end
Putting this in NSDictionary will create very nice output in console.
On the other hand, you can create category on NSObject class and do something like this:
- (NSString*)myDescriptionMethod {
NSMutableDictionary *dict = [NSMutableDictionary new];
unsigned int count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
const char *property = property_getName(properties[i]);
NSString *propertyString = [NSString stringWithCString:property encoding:[NSString defaultCStringEncoding]];
id obj = [self valueForKey:propertyString];
[dict setValue:obj forKey:propertyString];
}
free(properties);
return [NSString stringWithFormat:#"<%# %p %#>",
[self class],
self,
dict];
}
Then you will avoid overriding -(void)description method in your classes.
Get it from here
The most elegant way to achieve what you're looking for in Objective-C with NSObject subclasses, it to override the NSObject method description.
For example (assuming your Class has a property called propertyX):
-(NSString *)description
{
return [NSString stringWithFormat:#"<myCustomObject: %#, propertyX: %f, %f>",
[self objectID], [self propertyX].x, [self propertyX].y];
}
The default description implementation of NSObject will simply return the memory address pointed to for the object, like so:
NSLog(#"%#", self);
2015-06-15 14:20:30.123 AppName[...] myCustomObject: 0x000000>
However, by overriding this base Class method as shown above, you will be able to customise this behavior, and the log will look like this:
2015-06-15 14:20:30.123 AppName[...] myCustomObject: 0x000000 someProperty, Property: blah, blah>
There is a nice tutorial, which discusses this further here.
Example :-
+ (NSString *)description;
[NSString description];
Gives you information about the class NSString.
This is a mystery:
I'm invoking setPrimitiveValue:forKey: on an NSManagedObject. The key is a legit, persistent, modeled attribute of the object. However, setPrimitiveValue:forKey: fails, often setting the value for a different, arbitrary attribute. The docs say this behavior is expected when invoking setPrimitiveValue:forKey: for an unmodeled key. So it seems Core Data thinks the key is unmodeled.
The strange part:
When the key is hardcoded as a string literal, the primitive value is indeed set successfully. It only fails when the key is a variable. The variable I'm using happens to be passed from the keyPath argument of observeValueForKeyPath:ofObject:change:context:
The keyPath variable is the same as the string literal. isEqual: returns true and the hash values are equal. The keyPath variable is of type __NSCFString. Does anyone know why setPrimitiveValue:forKey: would behave any differently? (This behavior is on OS X 10.9.1)
An update with better information:
The misbehaving key traced back to a string loaded from a file on disk. The example below is an isolated case. If the attribute string "mainAttr" is written to disk and read back in, then setPrimitiveValue:forKey: sets the value for the wrong attribute, not "mainAttr".
Core data object:
#interface Boo : NSManagedObject
#property (nonatomic, retain) NSNumber * mainAttr;
#property (nonatomic, retain) NSNumber * attr1;
#property (nonatomic, retain) NSNumber * attr2;
#property (nonatomic, retain) NSNumber * attr3;
#end
-
#import "Boo.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSManagedObjectContext *context = managedObjectContext();
NSString *key = #"mainAttr";
// write to disk, read back in
NSString *path = [#"~/Desktop/test.txt" stringByExpandingTildeInPath];
[key writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
key = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
Boo *boo = [NSEntityDescription insertNewObjectForEntityForName:#"Boo" inManagedObjectContext:context];
[boo setPrimitiveValue:#(5) forKey:key];
NSLog(#"Boo: %#", boo);
}
return 0;
}
You need the below 3 statements to set the value. Try it.
[self willChangeValueForKey:key];
[boo setPrimitiveValue:#(5) forKey:key];
[self didChangeValueForKey:key];
My problem is that I have a class property that is of type NSMutableArray, as defined in my header file, yet when I attempt to modify one of the array elements (an NSDictionary) I receive the following runtime error:
2013-01-16 14:17:20.993 debtaculous[5674:c07] * Terminating app due
to uncaught exception 'NSInternalInconsistencyException', reason:
'-[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent
to immutable object'
Header declaration:
// BudgetViewController.h
#import <UIKit/UIKit.h>
#interface BudgetViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
- (IBAction)afterTaxIncomeEditingDidEnd:(id)sender;
#property (strong, nonatomic) NSMutableArray *budgetArray;
#property (strong, nonatomic) IBOutlet UITextField *afterTaxIncome;
#property (strong, nonatomic) IBOutlet UITableView *budgetTableView;
#end
Method that generates the error:
-(void)applyCCCSWeights
{
NSMutableDictionary *valueDict;
NSString *newAmount;
for (id budgetElement in [self budgetArray]) {
valueDict = [[NSMutableDictionary alloc] initWithDictionary:budgetElement];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]];
[valueDict setValue:newAmount forKeyPath:#"amount"];
[[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
NSLog(#"%0.2f (%0.2f)", [[budgetElement objectForKey:#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
// Note the replaceObjectAtIndex:0 above is just a placeholder. This will be replaced with the correct index.
budgetArray is surely immutable, you have to create it mutable.
Probably you're doing something like this:
budgetArray= [NSArray arraWithObjects; obj1, obj2, nil];
And ignoring the compiler warning. Make it mutable:
budgetArray= [[NSMutableArray alloc]init];
I'm fairly certain you cannot change a mutable object during enumeration.
This SO question may help: Setting an object during fast enumeration problems
In your init method, put this:
budgetArray = [[NSMutableArray alloc] init];
Also, why not use dictionary and array literal syntax?
-(void)applyCCCSWeights {
NSMutableDictionary *valueDict;
NSString *newAmount;
for (NSDictionary *budgetElement in [self budgetArray]) {
valueDict = [budgetElement mutableCopy];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]];
valueDict[#"amount"] = newAmount;
_budgetArray[0] = valueDict;
NSLog(#"%0.2f (%0.2f)", [budgetElement[#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
Notice that [[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
becomes: _budgetArray[0] = valueDict;
You can't change an array while doing a fast iteration over the array. On the other hand, it's entirely unnecessary; the code is absolutely inefficient: Just make the elements of the array NSMutableDictionaries, then change the dictionaries directly, instead of creating a copy and then changing an element in the copy.
Noticed later that you use NSJSONSerialization; look at the flags and don't pass 0 blindly.
Short Version:
I define a property with (nonatomic, retain) and assumed that the property would be retained. But unless I call retain when assigning a dictionary to the property, The app crashes with an EXEC BAD ACCESS error.
Long Version:
I have a singleton which has a dictionary. The header is defined like this
#interface BRManager : NSObject {
}
#property (nonatomic, retain) NSMutableDictionary *gameState;
+ (id)sharedManager;
- (void) saveGameState;
#end
In the implementation file, I have a method that's called in the init. This method loads a plist form the bundle and makes a copy of it in the users documents folder on the device.
- (void) loadGameState
{
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"gameStateTemplate.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
}else{
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:destinationPath];
}
}
Now here's how I thought this should work. In the header I define the gameState property with (nonatomic, retain). I assumed (probably incorrectly) that 'retain' meant that the gameState dictionary would be retained. However, I have another method in my singleton (saveGameState) that get's called when the AppDelegate -> 'applicationWillResignActive'.
- (void) saveGameState
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *plistPath = [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
[gameState writeToFile:plistPath atomically:YES];
}
This throws an EXEC BAD ACCESS error on gameState. If I modify loadGameState to retain the gameState dictionary, everything works as it should. eg:
gameState = [[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath] retain];
I'm guessing this is the correct behaviour, but why? Does (nonatomic, retain) not mean what I think it means, or is something else at play here?
I've not really grok'd memory management yet, so I stumble on this stuff all the time.
You must use the accessor:
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
or (is equivalent to):
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]];
instead of
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
which only sets the ivar without any notion of property.
Where do you declare gameState as an ivar? I'm presuming you do so in the implementation.
The real problem is that in your implementation, you access gameState directly and don't actually invoke the property you've declared. To do so you must send self the appropriate message:
[self gameState]; // invokes the synthesized getter
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]]; // invokes the synthesized setter -- solves your problem
or
whatever = self.gameState; // invokes the getter
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]; // invokes the synthesized setter -- solves your problem
Make sure you get around to groking that memory management literature... this is a very basic question which, according to the strict rules of StackOverflow, I shouldn't be answering. Good luck!
I can pass basic data between classes, but when I try to pass a NSString* from my UIApplicationDelegate I get an EXC_BAD_ACCESS / NSZombie.
Is there something special I have to do to return an NSObject? Does this have to do with threading? (I thought the atomic setting on the property would take care of that?)
AppDelegate.h:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
NSString * currentNoteName;
}
#property (atomic, assign) NSString *currentNoteName;
#end
AppDelegate.m:
- (void)timerCallback:(NSTimer *)timer {
currentNoteName = [NSString stringWithCString:(tone->freq).c_str() encoding:NSUTF8StringEncoding];
// This works:
NSLog(#"Current Note Name in timerCallback: %#", currentNoteName);
OtherObject.m:
// Returns a Zombie object & EXC_BAD_ACCESS:
NSString *currentNoteName = [appDelegate currentNoteName];
If not using ARC, you must using retain property:
#property (atomic, retain) NSString *currentNoteName;
and assign a value for it, using setter:
self.currentNoteName = [NSString stringWithCString: ...];
and don't forget to release instance of this ivar in your dealloc implementation of AppDelegate:
- (void) dealloc {
[currentNoteName release], currentNoteName = nil;
[super dealloc];
}
you are assigning a value and autoreleasing the NSString instance. Use retain instead.
The probleis is "assign", because the string from " [NSString stringWithCString" is auto-released.
Maybe u can change it to "copy" or "retain". (i think copy is better).