Unless otherwise noted, all questions assume a working installation of clang
running on a modern 64-bit Mac with Automatic Reference Counting enabled in a
modern runtime.
1.1 What is Cocoa’s policy around catching exceptions thrown by frameworks?
- a) Exceptions thrown by frameworks are fatal and should never be caught.
There is no valid reason to ever catch an exception thrown across framework boundaries. Candidates coming from other languages where exceptions play a more prominent role, e.g. Java, may assume they are benign. Structuring code with exceptions in Objective-C is considered an extremely poor practice and can lead to general program instability.
1.2 Can NSAssert be disabled in release builds.
- c) NSAssert can be disabled by defining a condition at build time.
Specifically, NSAssert can be disabled by defining NS_BLOCK_ASSERTIONS
or
passing the flag -DNS_BLOCK_ASSERTIONS=1
. This is a bit of basic knowledge
that can often be surprising to those used to assert()
in C, which is disabled via NDEBUG
.
NSAssert can have other surprising effects as well, so its use is discouraged
in canonical Objective-C.
1.3 What will be the outcome of compiling and then executing the following program?
@import Foundation;
@interface CFIFruitList : NSObject
@property (nonatomic, copy) NSMutableArray<NSString *> *fruitNames;
@end
@implementation CFIFruitList
- (instancetype)init {
self = [super init];
if (self) {
NSMutableArray *favoriteFruits = [@[ "Apple", "Banana", "Pear", "Orange", "Peach" ] mutableCopy];
self.fruitNames = favoriteFruits;
}
return self;
}
- (void)removeAName {
if (self.fruitNames.count == 0) return;
[self.fruitNames removeObjectAtIndex:self.fruitNames.count - 1];
}
@end
int main(int argc, char **argv) {
CFIFruitList *fruitList = [[CFIFruitList alloc] init];
[fruitList removeAName];
return EXIT_SUCCESS;
}
- c) This program will crash at runtime.
The cause of the crash is a combination of the property declaration, the
initializer, and the method call. The property declaration declares
fruitNames
as having a setter that takes a copy
of the new value. In Cocoa
collections, doing so returns a collection that is immutable. Hence a message
like -removeObjectAtIndex:
will compile, be considered well-typed, and yet
cause a crash because the receiver is, in fact, not mutable. Candidates that do
not notice the property declaration or misunderstand the semantics of copy
may
assume that this program will run without error.
1.4 For a method with the following signature:
- (BOOL)performOperationWithError:(NSError **)error;
- c)
error
is passed by writeback because it is__autoreleasing
.
A "canonical invocation" looks like the following:
NSError *error = nil;
if (![object performOperationWithError:&error]) { /* handle error */ }
A candidate may assume, because the address of error
is being passed, that
error
itself is being passed by reference and normally they would be correct.
However, modern Objective-C makes assurances that any object-type double pointer
always has an __autoreleasing
inner pointer regardless of the declaration (To
override this, one must declare the inner pointer strong
or weak
). Hence
error
is passed by writeback
, not just reference
.
2.1 Which of the following is the the proper declaration of a convenience initializer?
- a)
+ (instancetype)foo;
Traditionally, a convenience initializer existed to perform allocation and initialization
in one "convenient" method call - hence it cannot be an instance method. Under
MRR, a convenience initializer also added one other convenience: returning an
object with a reference count of +0
. Initializers are required under the
language to return objects +1
, hence -init
-family initializers are not
correct.
2.2 What occurs when messaging an object that is nil
?
- c) Nothing happens.
This is a well-known property of the Objective-C language that may be confusing
to those coming from languages where interactions with null
cause crashes.
2.3 In general, what occurs when sending a non-nil object a message for a selector it does not implement?
- b) An exception is thrown.
The history of the decision to crash when invalid messages were sent to non-conforming objects is apocryphal at best. Candidates may confuse this question with the previous one, but make no mistake an object must actually implement or forward a message to be able to respond to it in a semantically meaningful way.
2.4 For a non-nil, user-defined Objective-C object, foo
, what is the result of evaluating the expression *(__bridge void **)foo
?
- b) The class metadata of the object is obtained.
This question tests the candidate's knowledge of the basic layout of Objective-C
classes. This answer is correct because an Objective-C object is any structure
that has an isa
pointer as its first member. Hence, a dereference of any
object will return that isa
pointer. The "user-defined" portion is important.
Under the modern runtime, the definition of object has expanded to include
tagged pointers which may reuse the bits for their own storage. User-defined
objects cannot take advantage of a tagged pointer representation, and so
the deference is a well-defined operation. Experienced candidates may also recall
non-pointer isa
and isa
masking becoming facilities of the modern runtime. As
of this writing, macOS uses "packed isa
", iOS uses the full magic isa
, and the
simulators and Windows use plain pointer isa
.
3.1 Does the following code cause a retain cycle? If so, why? If not, why not?
- d) No, it does not leak because a weak reference to self is captured by the block.
Any answer to this question relies on the implementation details of
NSParameterAssert
as well as block capture semantics. d
, however, is the most correct. NS*Assert
implementations that do not have C
in their names take a strong reference to
self
in the definition of the macro, so can implicitly cause retain cycles
if used in blocks. However, in this case, we have shadowed the declaration of
self
with our own weakSelf
property and there will be no cycle here.
Avoiding NS*Assert
and recognizing its fragility is an important skill in
modern Objective-C.
__weak id weakSelf = self;
self.someProperty = ^ (BOOL someFlag) {
id self = weakSelf;
NSParameterAssert(someFlag);
[self doSomethingElse];
};
3.2 What is the name of the rule whereby user-allocated CoreFoundation objects are the responsibility of the caller to release?
- b) "The Create Rule"
A la CFCreate*
. The Create Rule mirrors the malloc
rule in C: That which one
CFCreate
s, one must CFRelease
. This is why copying functions are named
CFCreateCopy*
or CFCreateMutableCopy*
as well.
3.3 Which of the three bridging casts (__bridge
, __bridge_retained
, __bridge_transfer
)
releases its operand after the cast is performed?
- c)
__bridge_transfer
This attribute is the most rarely used of the three but is required in cases
whereby cast objects have been over-retained by a particular abstraction like a
CFArrayRef
and must be cast back into Objective-C land.
3.4 Which of the three bridging casts (__bridge
, __bridge_retained
, __bridge_transfer
)
retains its operand after the cast is performed?
- b)
__bridge_retained
This attribute is the second most used of the three. It must be used when taking command of an object as a C-style pointer representation. ARC creates an extra retain for safety, then hands the responsibility of further releases to the user.
3.5 Which statement about Automatic Reference Counting is most correct?
- b) Automatic Reference Counting generally manages the memory of most types, but some manual reference balancing may be required.
ARC, while a well-developed and feature-rich piece of technology, is still just semantic analysis. The entire semantics of Objective-C cannot be cached out in any one compiler implementation, so ARC is necessarily incomplete. There are many cases where it is necessary, and even desirable, for the user to take over for ARC and handle things themselves. Some candidates may have been informed that ARC is infallible, but this is most certainly not the case.
4.1 A selector (SEL
) is:
- a) A unique runtime-relevant name for an Objective-C method.
A selector is an interned string representation of a runtime-relevant handle
that can be used to pick out a particular method's IMP
pointer, or implementation
pointer.
4.2 Where may the Objective-C runtime allocate blocks?
- d) Blocks may appear on both the heap and the stack.
Blocks are strange newcomers to the Objective-C language. They can change the storage semantics of variables that they capture and automatically relocate themselves from the stack to the heap if necessary. All of this is done transparently so many candidates may have never had to think of where blocks reside in memory.
4.3 How may times will +initialize
be called by the runtime?
- b)
+initialize
can be called many times by the runtime.
+initialize
can be called many times as it takes for each class in the
hierarchy to receive the message. It is a poor place to actually perform
initialization in most cases, and if it must be used, the class of the object
should be used as a guard to ensure the initializing code is called exactly once
as is usually desired.
4.4 What is meant by a "Non-Fragile Runtime ABI"?
- b) The ability to add instance variables to a class without recompilation of subclasses.
The Non-Fragile Runtime is precisely the reason this exam begins with a disclaimer about the modern runtime. Objective-C classes used to be subject to the "sliding iVar" problem. See here for more.
4.5 Observe the following code declared in a framework linked with and loaded by a program:
// MyFramework.h
@interface NSObject (NoExceptionMessaging)
- (void)doesNotRecognizeSelector:(SEL)aSelector;
@end
// MyFramework.m
@implementation NSObject (NoExceptionMessaging)
- (void)doesNotRecognizeSelector:(SEL)aSelector { }
@end
4.6 What is a possible behavior the program may encounter if it attempts to send an object a message with a selector it does not respond to:
- d) All of the above.
The behavior of extensions that override methods is entirely undefined and is a source of bugs. Extensions may be loaded at any time by the runtime in any order and so which implementation of a particular method "wins" the race cannot be relied upon.
4.7 What is the Runtime Type Encoding of the signature of an instance method that takes
no arguments and returns void
?
- a) "v@:"
Runtime type encodings are relevant to those that need to perform runtime-level
dynamic construction of classes. Candidates may assume from the description of
the selector provided that there will be no runtime encoding, however every
Objective-C instance method is given at least two arguments: self
and _cmd
.
5.1 In the context of methods in the init family, what is a difference, if any, between id
and instancetype
.
- a) There is no difference.
Methods in the init family are expected to have a rigid structure in modern Objective-C. As
part of that rigidity, id
and instancetype
have the same semantics when used in return position.
The additional type safety afforded by instancetype
over id
applies only to methods outside the init family.
5.2 Which is the most correct definition of a top-level constant of type NSString *
?
- d)
const NSString *const MyConstant = @"MyConstant";
Strings that are missing const
-qualification on the pointer type may be modified and are hence
not actually constant. String that are missing the const
-qualifier on the class type are
still constant pointers, but may not be passed to some routines that expect non-constant data. Note
that this behavior can be desirable - notably to avoid CV-qualifier mismatch warnings. However the
most correct answer is d.
5.3 What is a difference, if any, between properties marked atomic
and those marked nonatomic
?
- c) The synthesized body of a
nonatomic
property does not ensure integrity of the returned value, while anatomic
property does ensure integrity.
This is a critical question for any candidate claiming proficiency in Objective-C.
nonatomic
and atomic
properties are often cargo-culted into a codebase with
little regard as the semantics they entail. The distinction exists so that code
that wishes to access a value from multiple threads can mark that it will give a
coherent response (atomic
) rather than "any response at all" (nonatomic
).
Nowhere in the semantics of the language does it guarantee thread safety and it
is a serious mistake to assume this is the case.
5.4 True or False, @synthesize
is required for the proper declaration of object properties.
- b) False
@synthesize
has not been a requirement for property declarations backed by
instance variables for multiple major releases. It remains in poorly-written
tutorials and outdated references because it has been cargo-culted into modern
Objective-C. There is still a use for @synthesize
, though. If a protocol
has a requirement for which the class provides a backing instance variable, @synthesize
can be used to associate the storage and the requirement without the need
for explicit declarations.
5.5 What is the role of NSObject
in the declaration of the protocol below?
@protocol MyProtocol <NSObject, NSCopying>
- (void)doTheThing;
@end
- b) It indicates the
NSObject
protocol is an ancestor ofMyProtocol
.
Many candidates may not know that NSObject
is both a protocol and a class.
Here, the compiler appropriately selects the protocol definition because a
protocol may not declare a superclass requirement. For that, there is angle-
bracket type syntax NSObject<MyProtocol> *
.
5.6 Why might an explicit comparison to YES
, as in the code below, be dangerous?
/// ... omitted for brevity
if ([@"abcdefghijklmnopqrstuvwxyz" containsString:@"abc"] == YES) { /**/ }
/// ... omitted for brevity
- c) It may be dangerous because
BOOL
can take on more values than justYES
andNO
.
On 64-bit iOS, the representation of BOOL
was corrected to be a 1-bit boolean.
On 64-bit OS X, the representation of BOOL
is still signed char
, which means
it can take on many distinct values other than just 0
or 1
. It is dangerous
to assume that any method will return just the constants YES
or NO
.
5.7 Why might an explicit comparison to nil
be necessary instead of a cast?
/// ... omitted for brevity
- (void)doMagic:(Magic *)magic {
return (BOOL)magic;
}
/// ... omitted for brevity
- c) The explicit comparison is necessary because an implicit comparison may slice
magic
.
As before, on 64-bit OS X, the representation of BOOL
is signed char
.
Implicit pointer comparisons will always slice the pointer value to fit
a signed char
. Any pointer value that has low-bits set to 0 will show up as
NO
or false
despite being non-nil
. A check against nil should always be
explicit wherever possible.
Briefly describe a possible implementation of a bridged CoreFoundation-to-Objective-C type. What considerations must be made to ensure the bridging is "toll-free"?
Why might wrapping a tight loop in an @autoreleasepool
make looping code more
performant? In general, which part of the loop should be wrapped, the inside of
the loop body or the entire loop itself?
Will ARC manage the reference counts of Objective-C objects that are members of C structures or unions? Justify your response.
Why is the -CGColor
property of NSColor
and UIColor
marked with
NS_RETURNS_INNER_POINTER
(__attribute((objc_returns_inner_pointer))
)?
Where does the &x
"live", the stack or the heap? Justify your answer.
/// Class details omitted ...
- (void)foo {
__block int x = 0;
int *pointerToX = &x;
int (^block)() = ^{
x += 1;
return x;
};
}
/// Class details omitted ...
Given an example of a class where +alloc
is required but not necessarily -init
?
// 1) What type do I have?
// 2) Implement me to return a value of the proper type. (Invoke the callback in the appropriate location.)
void (^callback(NSInteger, void (^fp)(NSInteger)))(NSInteger);
Hey, I enjoyed working through your questions but I think got different answers on some of them. What do you think?
There are a couple of chances to handle this before a crash occurs, isn't there? For example, by implementing
-[NSObject forwardingTargetForSelector:]
.On my Mac, I can make a user-defined class with a non-pointer
isa
field. If I run this:I get this:
I believe that would be undefined behavior: https://reviews.llvm.org/D27607? The conclusion is still sound, however.