Skip to content
Objective-C Styleguide
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Cocoa, Objective-C, and You

A Curmudgeonly Foreword

In some ways this is not your typical style guide. I find the stylistic micromanaging in most guides ultimately fruitless. There's no use fighting against years of muscle memory and personal aesthetics to ensure if () over if(). Consistency is important, but more at the practical level than the typographic.

In that spirit, this guide aims to ensure clarity, predictability, and de facto avoidance of common gotchas. Except for cases where it overlaps with those goals, I'm not going to dictate elements of personal style.

I leave you with four rules of thumb:

  1. Be consistent.
  2. Try to fit in with the code around you.
  3. Follow Apple's lead.
  4. Leave the code in better condition than you found it.


Indisputable Truths (tl;dr)

  • Use ARC. If you can't use ARC, kindly inform the client that their requirements are absurd.
  • Don't make everything public. Conceptually private properties belong in the class extension (the anonymous category) in the implementation file. (More...)
  • Use dispatch_once for singletons. And for the love of God, don't override +alloc or anything equally crazy to prevent new instances of your class. (More...)
  • Prefix everything. Preferably with three characters. Yes, even category methods. And absolutely do not co-opt any of the Apple prefixes for your own code. (More...)
  • Give Apple's guidelines on naming methods a thorough reading. If you ever think "what should I name this method?", it has the answers. (More...)
  • Four spaces for indents. No tabs. This is not the Xcode default; you should change it.
  • Use CocoaPods for external modules. If the module you want doesn't have a pod, use Git submodules (or, better, write a podspec for it). Only commit the full external library as a last resort.

Solved Problems

Don't reinvent the wheel, a'ight?

  • Managing external libraries: CocoaPods
  • Common string formatting (times and dates, addresses, file size, etc.): FormatterKit
  • Logging that is faster and more flexible than NSLog: CocoaLumberjack
  • Sensible, version-controllable build settings for new projects: xcconfigs
  • Avoiding common causes of stringly typed code: @keypath from libextobjc, and NSStringFromClass.
  • Avoiding strongly capturing self in a block (without writing tons of boilerplate): @weakify and @strongify from libextobjc
  • Safer, smaller model (de)serialization: Mantle or JSONModel.

Specific Guidance

Dot Notation

Always use dot notation when dealing with properties. Use bracket notation in all other instances.


self.view.backgroundColor = UIColor.redColor;


[[self view] setBackgroundColor:[UIColor redColor]];
[UIApplication sharedApplication].delegate
[myArray firstObject]

Rationale There is an assumption that property access will be idempotent and cheap, which is often not true of methods. That said, Apple has been moving towards property-ifying a lot of methods, regardless of their cost. Consider -[NSDictionary allValues], which generates a new array on each call — it became a property in iOS 8, presumably for better Swift support.

As of Xcode 8/the iOS 10 SDK, Objective-C has support for class properties. That means dot syntax is now the right choice for singleton access (e.g., UIApplication.sharedApplication) and a lot of other class-level getter-like things (e.g., UIColor.redColor).


Use four spaces for indenting code. Do not use tabs. This is not the Xcode default; you should change it.

Make liberal use of vertical whitespace to separate logical sections of a method. Use #pragma mark to group related methods — for instance, delegate implementations. (Though some would argue that #pragma mark is a sign that your class is trying to do too much. Consider that possibility as well.)

Rationale Multiple developers using their own indenting preferences leads to ugly code, and no one wants to have to think about what their freaking tab button should do. Readability, my friends. Readability.


Historically, Apple has recommended a 3-character prefix for all classes, functions, protocols, categories names, category methods/properties, constants, and so on. There is anecdotal evidence that, with the advent of Swift, this may be becoming passé.

So, my guidance is nuanced. Use a prefix in your application code if you prefer it. But you must use a prefix in code that is part of a library, to avoid conflicts with others.

If you decide to (or must) use a prefix, use a project-specific one, preferably 3 characters, but no less than 2. Use it everywhere. Under no circumstances should you co-opt a well-established existing prefix (e.g. NS, AF, CL).


@interface CRLMapView : MKMapView  { ... }

@interface NSString (MR5Regexes) {
    -(BOOL)mr5_doesMatchRegularExpressionFromString:(NSString *)regexString;

CGPoint CRLPointMakeIntegral(CGPoint pt);

NSErrorDomain const CRLServiceErrorDomain = @"CRLServiceErrorDomain";


@interface MKMyMapView : MKMapView { ... }

@interface MapView : MKMapView { ... }

@interface NSString (Regexes)  {
    -(BOOL)doesMatchRegularExpressionFromString:(NSString *)regexString;

CGPoint CGPointMakeIntegral(CGPoint pt);

NSErrorDomain const ServiceErrorDomain = ...

Rationale This is to avoid name collisions against other third-party projects, private APIs, and future APIs. For instance, many folks added -[NSArray firstObject] through categories, and when iOS 4 exposed that method, their category implementations overrode the first-party one. In this particular case it wasn't a big problem, but you can imagine that it would be Bad if your implementation of a method differed significantly.

Also, from a readability perspective, it's useful to be able to easily distinguish third-party code.

More reading on the topic of namespacing can be found in this NSHipster article.

Properties and ivars

Use properties over ivars, almost always. There are two exceptions where ivars are acceptable:

  • The property is only accessed through pointers (e.g. integers used with the OSAtomic API, or a few things from Foundation).

  • You've profiled and found that the objc_msgSend overhead is actually significant in your use case. This is an exceedingly rare case.

Public and protected ivars are never acceptable. Use a separate header with a subclass-only category for 'protected' properties. See Visibility.

NB Accessing properties through their getter/setter in initializers and -dealloc can be dangerous. The instance is not entirely initialized at those points, and the side-effects of the getter/setter (including any external KVO) may make bad assumptions about the object. So, there are exactly 3 places where you should access the synthesized ivar for a property: -init, -dealloc, and overridden getter/setter implementations.


@interface CRLDummy ()
@property (nonatomic, assign) BOOL internalState;

-(id)init {
    // (omitting init boilerplate...)
    _internalState = 2;

// But, in every other method except -dealloc and overridden accessors,
self.internalState = 2;


@interface CRLDummy () {
    BOOL internalState;

-(id)init {
    // (omitting init boilerplate...)
    self.someProperty = 2;

// And elsewhere,
internalState = 2;  // or self->internalState

Rationale The traditional argument for ivars is that they avoid the overhead of objc_msgSend incurred when accessing properties. However, that concern is unwarranted in 99% of cases.

Standardizing around properties provides a unified syntax for accessing member data, and adds override points for setters and getters. It also sidesteps unintentional issues that may be caused by accessing ivars directly. For instance:

  • Properties declared copy will only actually copy their values if assigned through the setter. (And the fact that this isn't the case with strong and weak is certain to be confusing to newcomers.)
  • Direct ivar access isn't KVO-compliant — observers will not be able to detect changes to properties made via the ivar.
  • Even though you may not explicitly reference self, accessing ivars in a block still implicitly captures a strong reference to self. Surprise!

Property declarations, @synthesize, etc.

In keeping with the guidance in Dot Notation, make liberal use of readonly properties with custom getters to expose cheap computed data. For instance, consider the property userLocationVisible on MKMapView.

Rationale Values like this could be implemented as a method, but the property syntax is more semantically meaningful. Also it's convenient to use dot notation.


Use nonatomic on all properties.

Rationale atomic has performance issues on ARM and is generally not all that helpful anyway, despite being the default.


Use copy for any property whose type could potentially be mutable (NSString, NSArray, NSSet, etc.).

Rationale Take the example of NSString. Since it's a subclass, you could easily be passed an NSMutableString, only to have its value change without your class noticing.


Use the newer strong instead of retain.

Rationale They are functionally identical, but strong more accurately describes the semantics under ARC.


Always specify the storage semantics for a writable property. That is, a writable property must have one of strong, weak, assign, or copy.

Rationale No one can remember what the default is.


If your property is an adjectival boolean, Apple recommends that you override the getter to give it the prefix 'is': @property (nonatomic, assign, getter=isOpaque) BOOL opaque


In service of DRY, let Xcode auto-synthesize ivars for you unless:

  • You are implementing an optional property from a protocol (these must be explicitly synthesized). For consistency's sake, give the synthesized ivar an underscore prefix, just as if it was auto-synthesized: @synthesize state = _state.

  • Your code (or your superclass — ahem NSManagedObject) is doing some runtime voodoo which calls for @dynamic.


Objective-C lacks the visibility specifiers common in other object-oriented languages. That is not an excuse to make everything public.

Everything that is an implementation detail belongs in the .m file. Basically, if there is no reason for a consumer of your class to see it, or if it may change dramatically, don't put it in the .h.

Similarly, don't expose mutable datatypes through public properties. You don't want users to be able to change data out from under your nose. Expose an immutable copy of your internal data.

For properties that should only be available to subclasses ("protected"), use a category in a separate header. For an example from Apple, see UIGestureRecognizerSubclass.h.


// CRLDummy.h
@interface CRLDummy : NSObject
@property (nonatomic, readonly) NSArray *data;

// CRLDummy.m
@interface CRLDummy ()
@property (nonatomic, assign) BOOL internalState;
@property (nonatomic, strong) NSMutableArray *mutableData;
@property (nonatomic, weak) IBOutlet UILabel *dataLabel;

@implementation CRLDummy
-(NSArray *)data {
    return [NSArray arrayWithArray:self.mutableData];


// CRLDummy.h
@interface CRLDummy : NSObject
@property (nonatomic, strong) NSMutableArray *data;
@property (nonatomic, assign) BOOL internalState;
@property (nonatomic, weak) IBOutlet UILabel *dataLabel;

Rationale Encapsulation. Ease of refactoring. Etc. etc.


A specific case of the above principle is that of -[UIViewController initWithNibName:bundle:]. It is the default initializer for view controllers, but in 99.9% of cases, the name of the nib for your VC is an implementation detail. Don't make users of your class supply it! Supply a custom -init method that does the right thing. See initWithNibName:bundle: Breaks Encapsulation.


Minimize the number of imports in your header files. Use @class and @protocol forward-declarations in headers whenever possible, and do the actual import in your .m. See UIView.h for inspiration.

Rationale Avoids polluting the namespace of consumers of your class with implementation details.

Project Organization

Keep your Xcode project in sync with the filesystem. Xcode groups should have corresponding physical folders.

Rationale Makes it far easier and less surprising to deal with the project in source code management and on the command line.

Naming Things

Objective-C has notoriously verbose names for ... most everything. Apple has a very good document describing best practices for naming things. Read it. It has the answers.

Some highlights from that document:


Do not use and in method names, unless it communicates that the method performs multiple actions.


-(id)initWithCenter:(CGPoint)center radius:(CGFloat)radius;
-(void)enqueueJob:(id)job andUpdateView:(UIView *)view;


-(id)initWithCenter:(CGPoint)center andRadius:(CGFloat)radius;
-(void)enqueueJob:(id)job updateView:(UIView *)view;


Only use get in accessor methods when they return results indirectly (via a pointer or through a callback, for instance).


-(void)getHue:(CGFloat *)h saturation:(CGFloat *)s brilliance:(CGFloat *)b;


-(void)hue:(CGFloat *)h saturation:(CGFloat *)s brilliance:(CGFloat *)b;


The One True Way to make singletons is with dispatch_once, a static instance, and (as of Xcode 8) a class property:

// CRLSingletonClass.h
@property (class, nonatomic, readonly) CRLSingletonClass *sharedInstance;

// CRLSingletonClass.m
+(CRLSingletonClass *)sharedInstance {
    static dispatch_once_t onceToken;
    static CRLSingletonClass *singletonInstance;
    dispatch_once(&onceToken, ^{
        singletonInstance = [[CRLSingletonClass alloc] init];
    return singletonInstance;

I recommend that that be the end of it — don't try to enforce that the shared instance is the only one, and please don't do anything unholy like overriding +alloc. If you're feeling particularly overprotective, consider marking the inits with __attribute__((unavailable)). But ultimately, there's very little use in writing code to babysit consumers of your class.

Apple gives little guidance on naming the accessor for a singleton. Generally sharedWhatever is appropriate when there is intended to be only one instance of the class (e.g. +[UIApplication sharedApplication]). If multiple instances could conceivably exist, something like standardWhatever or defaultWhatever is more appropriate (e.g. +[NSUserDefaults standardUserDefaults] or +[NSFileManager defaultManager]).

+initialize and +load

+initialize can be a useful place to set up data that will be shared by all instances of your class. However, there is a big caveat. If your class is subclassed, and the subclass doesn't implement +initialize, your implementation will be called more than once. So, always guard the body of +initialize with a check that self is the class you think it is:

+(void)initialize {
    if(self != [CRLExpectedClass class]) return;
    // ... real initialization code ... 

As Mike Ash points out, you should do this on every class, even if you can't imagine it will be subclassed. Under the covers, KVO creates dynamic subclasses that will trickle back to your +initialize.

Relatedly, from that Mike Ash article, avoid +load. It's a scary place.

Protocols and Delegates

Declare delegate protocols in the same header as the related class.

Always annotate the methods of your protocol with @optional and @required. No one can remember the default.

Remember to check if an adopter of your protocol responds to the selector of optional methods before invoking them.

Delegate properties should be strongly typed (id<CRLDelegate>) and weak, to avoid retain cycles.

Strict adherence to Apple's guidelines for delegate method names is important. You should be able to look at a method signature and know with 99% certainty if it is part of a delegate protocol.

Informal protocols are a relic. Don't. If you don't know what they are, don't even click that link.

Interface Stuff

IBOutlet properties should almost always be weak. There are a few rare exceptions:

  • If the outlet is a top-level object in your NIB, you need to store a strong reference to it.

  • If you will be removing the outlet from the view hierarchy (but want to reuse it later), you will need a strong reference to it in order to avoid it being released. Note that this also applies to things like layout constraints, which are removed from the hierarchy when deactivated.

Rationale The view hierarchy owns most things that you would create an outlet for. Having weak outlets avoids surprising retain cycles.


With almost no exceptions, IBOutlets (and view properties in general) should be private.

Rationale See Visibility.


Keep in mind that -viewWillAppear and -viewDidAppear will likely be called multiple times (e.g. if a modal comes and goes). You may find it valuable to set a flag on the first call to -viewWillAppear so you know if this is a new appearance or just the disappearance of a modal or popping of a navigation stack.


As of iOS 6, -viewWillUnload and -viewDidUnload are dead. Your view will never be unloaded; in a low memory situation, only its backing store will be cleared. Rejoice!


Do not leave -initWithNibName:bundle: as the preferred initializer for consumers of your view controllers. The location of the NIB for your view is an implementation detail! See the discussion in Visibility.


When subclassing UIView, be sure to implement both -initWithCoder: and -initWithFrame:, so that your view can be instantiated from both Interface Builder and code, respectively.


Use them.


@[obj1, obj2]
@{ @"first": obj1, @"second": obj2 }
@(anInteger + 3)


[NSArray arrayWithObjects:obj1, obj2, nil]
[NSDictionary dictionaryWithObjectsAndKeys:obj1, @"first", obj2, @"second", nil]
[NSNumber numberWithInt:1]
[NSNumber numberWithBool:YES]
[NSNumber numberWithInt:anInteger + 3]

Rationale Just ... look.

C Stuff

Use NS_INLINE (compiler-agnostic static inline) functions instead of function-like macros, whenever possible.

Note that the bodies of inline functions must appear in a header, so that the compiler has the code in order to inline them.


NS_INLINE void CRLMakeTheMagic(NSInteger a, NSInteger b) { ... }


#define CRLMakeTheMagic(a, b) do { ... } while(0)

Rationale Functions are typesafe. Also they don't require the gross syntax needed to make macros safe in all contexts (the do... while wrap or other constructs).


Be aware that the meaning of the const keyword is context-sensitive. Read declarations in reverse. Consider these two variables:

const NSString *var1 = @"hi"   // Nope.
NSString * const var2 = @"hi"  // Yup!

Reading in reverse, var1 is "a pointer to an NSString that is constant". That is redundant, however, as NSString is constant by nature (NSMutableString being the non-constant version). This leaves var1 assignable by anyone: exactly what you were hoping to prevent. var2, however, reads as "a constant pointer to an NSString", which is what was wanted.


Declare public constants the way Apple does — put an extern reference to the variable name in a header, and the actual values in the implementation file. Note that constant definitions like these should be outside of any @interface or @implementation blocks, since they are not an Objective-C feature.

Use constant variables over #defined macros whenever possible. Define macros only for things that will actually be used by the preprocessor (e.g. in an #if).

Name your constants with a prefix and a descriptive name.


// CRLAPIClient.h
extern NSString * const CRLAPIClientSecret;
extern NSString * const CRLAPIClientID;

// CRLAPIClient.m
NSString * const CRLAPIClientSecret = @"my-secret";
NSString * const CRLAPIClientID = @"my-client-id";


// CRLAPIClient.h
NSString * const CRLAPIClientSecret = @"my-secret";

// or ...
#define CRLAPIClientSecret @"my-secret"

Rationale Typesafety, Swift accessibility, avoidance of linker issues.


For string constants that fit into a class of existing constants ("string enums" – think things like NSNotificationCenter notification names), use NS_EXTENSIBLE_STRING_ENUM typedefs. These are available in Xcode 8+. Below are some useful existing typedefs for your own constants. If you need a custom typedef, see Enumerations and bitmasks for guidance.

NSString * typedef purpose
NSNotificationName NSNotificationCenter notification names
NSErrorDomain NSError domains
NSValueTransformerName NSValueTransformer names

Rationale Expressiveness, typesafety, and better Swift interoperability.

Enumerations and bitmasks

Use the NS_ENUM and NS_OPTIONS macros detailed here for enumerations. Generally use NSInteger as the underlying type for NS_ENUMs, and NSUInteger for NS_OPTIONS. Options should be named in the plural or with a gerund. For instance, UIViewAnimationOptions and UIViewAutoResizing. Enums that represent independent choices should be named with a singular: UIViewAnimationTransition.


typedef NS_ENUM(NSInteger, CRLAnimationCurve) { ... }
typedef NS_OPTIONS(NSUInteger, CRLAnimationOptions) { ... }


typedef enum _CRLAnimationCurve { ... } CRLAnimationCurve

Rationale These macros allow the compiler to do much better checking about your use of enumerations, improve completion in Xcode, and import properly into Swift. Also the syntax is a little more sane.


Name the individual elements of an enumeration or bitmask with a prefix of the enum's name (singularized if need be).


typedef NS_ENUM(NSInteger, CRLAnimationCurve) {

typedef NS_OPTIONS(NSUInteger, CRLAnimationOptions) {


typedef NS_ENUM(NSInteger, CRLAnimationCurve) {
    // anything not beginning with "CRLAnimationCurve"...

Rationale Consistency, predictability, and Swift compatibility.


For collections of related NSString constants ("string enums"), use a NS_EXTENSIBLE_STRING_ENUM typedef (available in Xcode 8+). Note that there are already a few existing typedefs for things like NSNotificationCenter message names. See Constants for a table of those.

For custom extensible string enums, use this approach:

// CRLEventEngine.h
// Declare the enum typedef:

// Use it for all related constants. Note that these constants can be defined be in
// multiple files, and even across multiple targets.
extern CRLEventName const CRLTapEventName;
extern CRLEventName const CRLDoubleTapEventName;

// CRLEventEngine.m
CRLEventName const CRLTapEventName = @"tap";
CRLEventName const CRLDoubleTapEventName = @"doubletap";

Rationale Consistency, better typesafety, and Swift interoperability.

Numeric Types and 64-bit Considerations

The iPhone 5s introduced the ARM64 platform and changed the underlying type of several Cocoa typedefs. CGFloat became a double, and NSInteger and NSUInteger became long. So, to ensure backward- and forward-compatibility:


Stick to the Cocoa typedefs wherever possible: the NS-prefixed integer types, CGFloat, and so on.


Consider importing tgmath.h in your prefix header. This is a C99 feature that provides type-generic wrappers around the standard math.h functions. After including this, you can write code like cos(someCGPoint.x) without warnings on all architectures. Avoid the type-specific versions like cosf and cosl.


Untrain yourself from typing the f suffix on floating-point literals. CGFloat is a double on ARM64, so you're really just wasting keystrokes by typing it. Moreover, any concerns over performance are unwarranted; the downcast on 32-bit architectures will happen at compile time.


CGPoint pt = CGPointMake(5.0, 34.2);
CGFloat newWidth = 0.25 * CGRectGetWidth(self.view.frame);


CGPoint pt = CGPointMake(5.0f, 34.2f);
CGFloat newWidth = 0.25f * CGRectGetWidth(self.view.frame);


Use the most semantically meaningful primitive type possible. For example, if a number could never conceivably be negative (e.g., an array index), use an NSUInteger.

Also, prefer Apple's specialized typedefs when they make sense. For instance, use NSTimeInterval instead of double for durations expressed in seconds.

Rationale Interoperability with Apple's APIs, type genericity, and expressiveness.

NSNumber and NSValue

NSNumber has one and only one use: to put primitives into Objective-C containers (NSArray, NSDictionary, etc.). Do not use it for properties or local variables; always prefer a primitive type when possible.

Rationale NSNumber is annoying since you can't do math on it. Also an NSNumber loses all the semantic meaning in an actual primitive type — since it can store floats, integers, unsigned integers, and booleans, it leaves no indication of what type the variable is actually intended to be.


The same goes for NSValue, which is responsible for boxing structs. Don't use it for anything other than putting structs in containers.


When declaring a local NSError variable for use as an out parameter, assign it to nil before using it.


NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];


NSError *error;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];

Rationale Local variables are initialized to garbage. If you forget to set the error variable to nil and then access it, but no error occurred, your app will probably crash. Note that this situation is mitigated by the next piece of guidance, but is still a good habit.


When using a method that has an NSError out parameter and returns BOOL or an object, check the return value rather than the nil-ness of the error to detect a problem.


NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];

if(!fileContents) {
    // An error occurred...


NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];

if(error) {
    // An error occurred...

Rationale While unlikely, there is nothing stopping the method from assigning the error parameter even in the case of success. All of Apple's documentation suggests that you rely on the return value to indicate an error. Also this approach mitigates problems if you forgot to initialize the local error variable to nil.


When attempting to identify a specific error, you must check both the domain and code of the NSError object.


NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];

if(!fileContents) {
    if([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorFileDoesNotExist) {
        // Handle file not found...


NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:@"blah.json" encoding:NSUTF8StringEncoding error:&error];

if(!fileContents) {
    if(error.code == NSURLErrorFileDoesNotExist) {
        // Handle file not found...

Rationale: Errors codes are not unique. Only the combination of a code and a domain is enough to absolutely represent an error. For instance, error code 5 could mean "bad string encoding" in the Foundation error domain, but "no network connection" in the URL error domain.


Don't hesitate to make your own custom error domain and codes if you're building something complicated. If you do so, you should use the NSErrorDomain typedef for your domain and an anonymous NSInteger-typed NS_ENUM for the codes (see Constants and Enumerations and bitmasks for more details). For example:

// CRLCombustionEngine.h
extern NSErrorDomain const CRLCombustionEngineErrorDomain;

NS_ENUM(NSInteger) {
    // ...

// CRLCombustionEngine.m
NSErrorDomain const CRLCombustionEngineErrorDomain = "CRLCombustionEngineErrorDomain";

Code Shape and "The Golden Path"

Keep the "golden path" (i.e. the main logic flow) through a function at the lowest possible level of nesting. Multiple return statements are fine. See Robots and Pencils' style guide.

This probably combats your muscle memory for -init functions, but give it a shot.


-(void)someMethod {
    if(![someOther boolValue]) return; // ... or otherwise handle the error
    //Do something important


-(void)someMethod {
    if([someOther boolValue]) {
        //Do something important

Rationale Readability. Writability (nicer to not have to worry about matching deeply nested brackets). And, semantically, this makes the main purpose of a block of code more obvious.


This guide stands on the shoulders of many other such guides.

You can’t perform that action at this time.