In the UITableViewCell
constructor - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
what is the purpose of the reuseIdentifier
? What is the advantage of setting it to a
non-nil
value?
The reuseIdentifier
is used to group together similar rows in an UITableView. That is,
rows that differ only in their content, but otherwise have similar layouts.
A UITableView
will normally allocate just enough UITableViewCell
objects to display
the content visible in the table. If reuseIdentifier
is set to a non-nil
value, then
when the table view is scrolled, UITableView
will first attempt to reuse an already
allocated UITableViewCell
with the same reuseIdentifier
. If you have not set
reuseIdentifier
then the UITableView
will be forced to allocate new UITableViewCell
objects for each new item that scrolls into view, potentially leading to laggy animations.
What are different ways that you can specify the layout of elements in a UIView
?
Using InterfaceBuilder, you can add a XIB
file to your project, layout elements
within it, and then load the XIB
in your application code (either automatically, based
on naming conventions, or manually). Also using InterfaceBuilder you can create a
storyboard
for your application.
Alternatively, using only code you can use NSLayoutConstraints
to have elements in a
view arranged by Auto Layout. Finally, you can create CGRect
s describing the exact
coordinates for each element and pass them to UIView
's
- (id)initWithFrame:(CGRect)frame
method.
What is the difference between atomic
and nonatomic
properties? Which is the default
for synthesized properties? When would you use one vs the other?
Properties specified as atomic
are guaranteed to always return a fully initialized
object. This also happens to be the default state for synthesized properties so, while it's
a good practice to specify atomic
to remove the potential for confusion, if you leave it
off, your properties will still be atomic
.
This guarantee of atomic
properties comes at a cost to performance, however. If you have a property for
which you know that retrieving an uninitialized value is not a risk (e.g. if all access to
the property is already synchronized via other means), then setting it to nonatomic
can
gain you a bit of performance.
Imagine you wanted to record the time that your application was launched, so you created a
class that defined a global variable in its header: NSString *startTime;
. Then, in the
class's implementation you set the variable like so:
+ (void)initialize {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
startTime = [formatter stringFromDate:[NSDate date]];
}
If you then added the following line to the application:didFinishLaunchingWithOptions:
method in your AppDelegate
:
NSLog(@"Application was launched at: %@", startTime);
what would you expect to be logged in the debugger console? How could you fix this to work as expected?
The debugger console will log Application was launched at: (null)
because the global
startTime
variable has not yet been set. The initialize
method of an Objective-C class
is only called right before the first message is sent to that class. On the other hand,
any load
methods defined by Objective-C classes will be called as soon as the class is
added to the Objective-C runtime. Changing initialize
to load
will have the desired
result (but be cautious about doing too much in load
, as it may increase your
application's load time).
Identify the bug in the following code:
#import "TTAppDelegate.h"
@interface TTParent : NSObject
@property (atomic) NSMutableArray *children;
@end
@implementation TTParent
@end
@interface TTChild : NSObject
@property (atomic) TTParent *parent;
@end
@implementation TTChild
@end
@implementation TTAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
TTParent *parent = [[TTParent alloc] init];
parent.children = [[NSMutableArray alloc] init];
for (int i = 0; i < 10; i++) {
TTChild *child = [[TTChild alloc] init];
child.parent = parent;
[parent.children addObject:child];
}
return YES;
}
@end
What is the consequence of this bug? How would you fix it?
This is a classic example of a retain cycle. The parent will retain the children
array,
and the array will retain each TTChild
object added to it. Each child object that is
created will also retain its parent
, so that even after the last external reference to
parent
is cleared, the retain count on parent
will still be greater than 0 and it will
not be removed.
In order to fix this, the child's reference back to the parent needs to be declared as a
weak
reference like so:
@interface TTChild : NSObject
@property (weak, atomic) TTParent *parent;
@end
A weak reference will not increment the target's retain count, and will be set to nil
when the target is finally destroyed.
Note: For a more complicated variation on this question, you could consider two peers that
keep references to each other in an array. In this case, you will need to substitute
NSArray
/NSMutableArray
with an NSPointerArray
declared as:
NSPointerArray *weakRefArray = [[NSPointerArray alloc] initWithOptions: NSPointerFunctionsWeakMemory];
since NSArray
normally stores a strong reference to its members.
Identify the bug in the following code:
@interface TTWaitController : UIViewController
@property (strong, nonatomic) UILabel *alert;
@end
@implementation TTWaitController
- (void)viewDidLoad
{
CGRect frame = CGRectMake(20, 200, 200, 20);
self.alert = [[UILabel alloc] initWithFrame:frame];
self.alert.text = @"Please wait 10 seconds...";
self.alert.textColor = [UIColor whiteColor];
[self.view addSubview:self.alert];
NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
self.alert.text = @"Thanks!";
}];
}
@end
@implementation TTAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[TTWaitController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
How would you fix this issue?
When the above code dispatches work using NSOperationQueue
's method
addOperationWithBlock
, there is no guarantee that the block being enqueued will be
executed on the main thread. Notice that the content of the UILabel
is being updated
within the body of the block. UI updates that are not executed on the main thread can lead
to undefined behavior. This code might appear to be working correctly for a long time
before anything goes wrong, but UI updates should always happen on the main thread.
The easiest way to fix the potential issue is to change the body of the block so that the update is re-enqueued using the main thread's queue like so:
[waitQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.alert.text = @"Thanks!";
}];
}];