How to avoid copy and pasting? - cocoa

I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!

I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.

Related

What issues could arise when using GCD dispatchAfter() in this use case

I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.

Update predicate on arraycontroller

I have an array controller and I have bound an entity to it, sort descriptor and predicate.
If I change the predicate format when the app runs, it works, so the binding is working.
My problem is when I want to change the predicate, f.ex. with a search term or some string that a user inputs, nothing happens, but when I add a record to the core data database, the tableview does update.
So my question is, how do I tell the array controller that the predicate has changed and it should update itself. Here is a code that runs when I enter search term, it also works, and I get all the NSLogs output correctly. Just my tableview is not updating itself.
- (IBAction)didChangeSearch:(id)sender {
if (sender == searchField) {
NSString *searchterm = [sender stringValue];
if (searchterm.length > 1) {
predicate = [NSPredicate predicateWithFormat:#"name contains [c]%#", #"m"];
NSLog(#"Putting predicate to the job : %#", searchterm);
} else {
predicate = nil;
NSLog(#"There is nolonger any predicate");
}
}
NSLog(#"I just got %#", [sender stringValue]);
}
I would like to say in the start that I am very new to bindings, have never used them until tonight, got a good feeling for them, and liked it, saves me so much code and I finally understood it (as much as 1 day can).
You should use self.predicate = ..... This will ensure that the proper KVO notifications are sent out, which will make your tableview update immediately (this assumes that "predicate" is a property and is bound to your array controller's filter predicate binding).

Iterate over NSTableview or NSArrayController to get data

I have an NSTableview which s bound to a NSArrayController. The Table/Arraycontroller contains Core Data "Person" entities. The people are added to the NSTableview by the GUI's user.
Let's say a person entity looks like
NSString* Name;
int Age;
NSString* HairColor;
Now I want to iterate over what is stored in the array controller to perform some operation in it. The actual operation I want to do isn't important I don't really want to get bogged down in what I am trying to do with the information. It's just iterating over everything held in the NSArraycontroller which is confusing me. I come from a C++ and C# background and am new to Cocoa. Let's say I want to build a NSMutableArray that contains each person from nsarraycontroller 1 year in the future.
So I would want to do something like
NSMutableArray* mutArray = [[NSMutableArray alloc] init];
foreach(PersonEntity p in myNsArrayController) // foreach doesn't exist in obj-c
{
Person* new_person = [[Person alloc] init];
[new_person setName:p.name];
[new_person setHairColor:p.HairColor];
[new_person setAge:(p.age + 1)];
[mutArray addObject:new_person];
}
I believe the only thing holding me back from doing something like the code above is that foreach does not exist in Obj-c. I just don't see how to iterate over the nsarraycontroller.
Note: This is for OSX so I have garbage collection turned on
You're looking for fast enumeration.
For your example, something like
for (PersonEntity *p in myNsArrayController.arrangedObjects)
{
// Rest of your code
}
You can also enumerate using blocks. For example:
[myNsArrayController enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
PersonEntity *p = object;
// Rest of your code
}];
There's pro's and cons to both approaches. These are discussed in depth in the answer to this question:
Objective-C enumerateUsingBlock vs fast enumeration?
You can find a great tutorial on blocks in Apple's WWDC 2010 videos. In that they say that at Apple they use blocks "all the time".

How to test with NSUserDefaults?

In most of my classes that work with defaults I make the defaults object settable:
#property(retain) NSUserDefaults *defaults;
This is supposed to make testing easier:
// In a nearby test class:
- (void) setUp {
[super setUp];
NSUserDefaults *isolatedDefaults = [[NSUserDefaults alloc] init];
[someObjectBeingTested setDefaults:isolatedDefaults];
}
But now I have found out then when I create a fresh defaults object, there are already some values in it. Is that possible? I thought I could create an empty, isolated defaults object by calling -init. Do I have a bug somewhere in the testing code, or do I really have to do something more complex (like stubbing or mocking) if I want to test my defaults-based code?
In the end I have created a simple NSUserDefaults replacement that can be used to control the defaults environment in tests. The code is available on GitHub.
From the NSUserDefaults documentation:
init: Returns an NSUserDefaults object initialized with the defaults for the current user account.
This should normally not be empty. I am not really sure what you want to test here, since it would be a waste of time to test NSUserDefaults functionality.
But say you need some keys to be not registered yet for your test to always have the same initial point: then just remove them in setUp (and restore them later in tearDown if you want to).
Something like:
- (void) setUp {
[super setUp];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"myTestKey"];
// synchronize the change, or just use resetStandardUserDefaults:
[someObjectBeingTested setDefaults:[NSUserDefaults resetStandardUserDefaults]];
}
If you don't have a specific list of keys but need to wipe out everything, you will have to use the CoreFoundation Preferences Utilities, see CFPreferencesCopyKeyList.
We also needed to override NSUserDefaults for testing, but didn't want to change any of the application code.
So we wrote a category on NSUserDefaults that allows us to override values returned by objectForKey: at runtime using method swizzling.
It looks like this in Objective C:
NSLog(#"Value before: %d", [[NSUserDefaults standardUserDefaults] boolForKey:#"Example"]);
// Value before: 0
[[NSUserDefaults standardUserDefaults] overrideValue:#(YES) forKey:#"Example"];
NSLog(#"Value after: %d", [[NSUserDefaults standardUserDefaults] boolForKey:#"Example"]);
// Value after: 1
And like this in Swift:
print(UserDefaults.standard.bool(forKey: "ExampleKey")) // false
UserDefaults.standard.overrideValue(true, forKey: "ExampleKey")
print(UserDefaults.standard.bool(forKey: "ExampleKey")) // true
You can find our code on Github: https://github.com/jakob/NSUserDefaultsOverride

What is the better way of handling temporary strings?

I have a situation where I need to use some strings temporarily but I've read so many conflicting things that I'm a bit confused as to what the best way to proceed is.
I need to assign some strings inside an if structure but use them outside the if structure so they need to be created outside the if, I was thinking something like:
NSString *arbString = [[NSString alloc] init];
if(whatever)
{
arbString = #"Whatever"
}
else
{
arbString = #"SomethingElse"
}
myLabel.text = arbString;
[arbString release];
I have seen examples where people just used:
NSString *arbString;
to create the string variable
Google's Objective C guide says it's preferred to autorelease at creation time:
"When creating new temporary objects, autorelease them on the same line as you create them rather than a separate release later in the same method":
// AVOID (unless you have a compelling performance reason)
MyController* controller = [[MyController alloc] init];
// ... code here that might return ...
[controller release];
// BETTER
MyController* controller = [[[MyController alloc] init] autorelease];
So I have no idea, which is the best practice?
In the example you posted, you actually lose the reference to the NSString you created when you assign it in arbString = #"Whatever". You then release the string constant (which is unreleasable, by the way).
So there's a memory leak, since you never release the NSString you created.
Remember that all these types are pointers, so = only reassigns them.
As for the question, in this example, you don't need the [[NSString alloc] init]. You don't need to copy the string into a local variable anyway, you can just set myLabel.text to the string constant #"Whatever".
(edit: that's not to say that you can't use your pointer arbString, arbString = #"Whatever"; myLabel.text = arbString is fine. But this is just pointer assignment, not copying)
If you needed to manipulate the string before you returned it, you would create an NSMutableString, and either release or auto-release it. Personally, create autoreleased objects using class methods, so in this example, I'd use [NSString string], or [NSString stringWithString:], which return autoreleased objects.

Resources