Skip to content

Instantly share code, notes, and snippets.

@ashalkhakov
Last active August 28, 2023 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ashalkhakov/a60c42702293f22f268ca83c091df88b to your computer and use it in GitHub Desktop.
Save ashalkhakov/a60c42702293f22f268ca83c091df88b to your computer and use it in GitHub Desktop.
Objective-C basics

if type T is an NSObject descendant, then it will support these messages:

+ (T *)alloc;
- (T *)init:(T *)self;
- (void)release;
- (T *)retain;

Objects (technically, variables of type T * where T is class type) can be either owned or not. Assuming T is class type, p is a pointer to T (i.e. T *), we write OWNED(p) to mean that p is owned.

If an object is owned, then it must be released when done with it.

I'll just assume that we have a substructural type system where class types are linear. Basically, if you have a variable x, such that it is of type T, and T is a class type, then you HAVE to treat it like a resource. Once you have x, you cannot simply duplicate/alias it, or forget about it.

  • use [x release] to get rid of it.
  • use T *y = [x retain] to duplicate it. x is still owned, but y is also owned! so you'll have to get rid of both of them now.

Introduction rules for owning objects

These rules answer the question: how do the owned pointers get introduced in programs?

  1. if you create an object using a method starting with alloc, copy, new, you own it.

    ex 1:

    T *p = [[T alloc] init];

    ex 2: assume that T *p

      T *q = [p copy];

    ex 3:

    T *p = [T new];
  2. if you are passed an object and you want to keep it after the current method exits, then you need to retain it:

    -(void) method:(T *)p;
      {
        // do: `p retain` before saving to member variable
        // do NOT: `p retain` "just because".
        // do NOT: `p release`.
        // do NOT: alias `p`. you can, technically, but it will get confusing, fast.
        // kinda similar to ATS usage of `x: !T` where `T` is linear type.
      }

Elimination rule for owned objects

This rule deals with getting rid of owned pointers in programs.

If you own an object, you must release it when done.

ex 1: assume we are in dealloc method and T *p is owned.

  RELEASE(p);

ex 2: assume we are in any method, T *p is owned, and it is a local variable.

  RELEASE(p);

ex 3: assume we are in any method, T *p is owned, it is a local variable, and T *q is a member variable.

  RELEASE(q);
  q = p;

borrowing / passing owned or non-owned pointers to other methods

if you have a T *p an owned or non-owned pointer, you may pass it to other objects.

ex 1. passing p to method of another object. assume P *object where P is class type, and P has method with signature - (void)method:(T *)p.

[object method:p];

Autorelease pools

NSAutoreleasePool: a type of autorelease pools. it can be modeled as a stack of pools:

  • stack: thread-local variable of autorelease pools
  • top(stack): in-use autorelease pool where each pool can hold pointers to objects it owns.
  1. at almost every time there is a global NSAutoreleasePool in use. in command-line programs, you can always create an NSAutoreleasePool at the beginning of the program in main function. in Cocoa programs, there will always be an in-use autorelease pool.

  2. objects respond to autorelease method by adding themselves to the current NSAutoreleasePool in use.

    [p autorelease];
  3. objects in the pool are safe to use until the pool is drained.

  4. an NSAutoreleasePool can be drained by calling the drain method, which will send release messages to all objects contained in the pool, and then release the pool itself.

    [p drain];
  5. in Cocoa, the pool is drained after every NSEvent is sent. basically, every event is handled by creating a new pool, then performing the event handling, and finally by draining the pool.

  6. pools form a stack per thread, like regions from the literature. each new pool will be pushed to the top, and it will become in-use. draining the pool will pop it off the stack, reinstating its parent pool as the global one. assume NSAutoreleasePool *p is the pool in use.

      NSAutoreleasePool *p1 = [[NSAutoreleasePool alloc] init];
      // `p1` is now in use, `p` is parent of `p1`.
    // any `autorelease` method calls on objects will make those objects add themselves to `p1`.
      [p1 drain];
      // now `p` is in use, and `p1` is released as well as all objects that it owned.
    
  7. some methods will put temporary objects onto the current autorelease pool. you may want to retain those objects if want to keep them.

rules for the autorelease method:

assume T *p is owned pointer.

	[p autorelease];

or equivalently:

AUTORELEASE(p);

p can still be used (its now owned by the current autorelease pool, but not the current method).

Property declarations and their meaning

Property declarations provide a syntactic short-hand for decarling backing member variables and the corresponding getter/setter method pairs.

In interface we can use this syntactic form:

@property (<attribute-list>) T *p;

where

  • p is the name of the property being declared
  • the attribute-list is a comma-separated list of attribute names (which are Objective-C identifiers).

In implementation we need to use this syntactic form:

@synthesize p;

Or, expanded out:

@synthesize p=_p;

where _p is ivar backing this property.

@synthesize means to generate code for implementing the given property.

A property p consists of:

  • member variable _p, also called the member variable backing the property
  • method getP
  • method setP (only if property is not read-only)
  • some code dealing with _p in the dealloc method

or, in terms of Objective-C code:

{
  T *_p; // ivar
}
-(T *)getP;
{
  return _p;
}
-(void)setP:(T *)v;
{
 _p = v;
}

property attributes

  • nonatomic: makes getP non-atomic:

    {
      return _p;
    }

    NOTE: atomic properties lock self. NOTE: the default is properties that are atomic!

  • readonly: don't synthesize setP.

  • assign, retain, copy, weak, strong: these are mutually-exclusive.

  • assign: the default for primitive types. it means that the body of setP is of the form:

    {
      _p = v;
    }

if T is a class type, you can use one of the following also.

  • retain: means that setP is of the form

    {
      [_p release];
      _p = [v retain];
    }

    also, the dealloc method will release _p.

  • copy: means that setP is of the form

    {
      [_p release];
      _p = [v copy];
    }

    also, the dealloc method will release _p.

    NOTE: copy is only valid if T implements the NSCopying protocol.

  • weak: the object is non-owned, and if it gets deallocated then _p becomes nil.

    also means that the dealloc method will NOT release _p, the ivar underlying this property.

    without ARC though, our best bet is to nil-out weak references in destructors.

  • strong: the object is owned.

    NOTE: basically same as retain! but for ARC.

    NOTE: also default behavior if T is a class type.

What are weak references and how they are implemented?

References:

  1. StackOverflow

  2. Verdagon writes:

    Objective-C has a mutex-guarded global hash map, tracking all the weak references and what objects they point to.

    Specifically:

    When we make a new weak reference to an object, we add an entry to the map: The key is the address of the object, the value is the address of the weak reference itself (a double-pointer, so to speak).

    When we get rid of a weak reference to an object, we remove that entry from the map. When an object dies, it looks for all the entries for the object... From each of those entries, it gets the address of the weak reference. It uses that address to set the weak reference to nil.

Property syntax sugar

If you have T *o where T is a class type, and T has a property named p of type P, then you can do the following:

  • property subscription expression o.p
    • it is the same as writing [o p]
  • property set statement: o.p = E, where E is an expression that has type P
    • it is the same as writing [o setP (E)]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment