The Complete Friday Q&A: Volume II
By Mike Ash, Gwynne Raskind and Chris Liscio
()
About this ebook
Related to The Complete Friday Q&A
Related ebooks
The Complete Friday Q&A: Volume III Rating: 0 out of 5 stars0 ratingsNode.js: Novice to Ninja Rating: 0 out of 5 stars0 ratingsC# Programming Illustrated Guide For Beginners & Intermediates: The Future Is Here! Learning By Doing Approach Rating: 0 out of 5 stars0 ratingsDeveloping Web Components with TypeScript: Native Web Development Using Thin Libraries Rating: 0 out of 5 stars0 ratingsJavascript Concepts: 1St Edition Rating: 0 out of 5 stars0 ratingsProgramming Problems: Advanced Algorithms Rating: 4 out of 5 stars4/5Pro C# 8 with .NET Core 3: Foundational Principles and Practices in Programming Rating: 0 out of 5 stars0 ratingsJavaScript for Gurus: Use JavaScript programming features, techniques and modules to solve everyday problems Rating: 0 out of 5 stars0 ratingsPro C# 9 with .NET 5: Foundational Principles and Practices in Programming Rating: 0 out of 5 stars0 ratingsLearn Microservices - ASP.NET Core and Docker Rating: 0 out of 5 stars0 ratingsCase Studies in GOF Creational Patterns: Case Studies in Software Architecture & Design, #2 Rating: 0 out of 5 stars0 ratingsThe Little Book of Sitecore® Tips: Volume 2 Rating: 0 out of 5 stars0 ratingsYour First Week With Node.js Rating: 0 out of 5 stars0 ratingsC# Interview Questions You'll Most Likely Be Asked Rating: 0 out of 5 stars0 ratingsProgramming Problems: A Primer for The Technical Interview Rating: 4 out of 5 stars4/5Learn ASP.NET MVC Rating: 4 out of 5 stars4/5Start-to-Finish Visual Basic 2015 Rating: 5 out of 5 stars5/5Learn ClojureScript: Functional programming for the web Rating: 0 out of 5 stars0 ratingsGet Ready for CSS Grid Layout Rating: 0 out of 5 stars0 ratingsComputer Productivity Book 2. Use AutoHotKey to Share your Personal Productivity Scripts: AutoHotKey productivity, #2 Rating: 0 out of 5 stars0 ratingsStart-to-Finish Visual C# 2015 Rating: 5 out of 5 stars5/5The KCNA Book: Kubernetes and Cloud Native Associate Rating: 0 out of 5 stars0 ratingsDesign Patterns in Swift: A Different Approach to Coding with Swift Rating: 0 out of 5 stars0 ratingsRails: Novice to Ninja: Build Your Own Ruby on Rails Website Rating: 4 out of 5 stars4/5Using Gatsby and Netlify CMS: Build Blazing Fast JAMstack Apps Using Gatsby and Netlify CMS Rating: 0 out of 5 stars0 ratingsSystemC: From the Ground Up, Second Edition Rating: 0 out of 5 stars0 ratingsC# Programming Fundamentals Rating: 0 out of 5 stars0 ratingsMootools 1.2 Beginners Guide LITE: Getting started Rating: 0 out of 5 stars0 ratingsAutomating Revit 2 Create More Flexible Scripts to Share for REVIT Productivity: Automating Revit, #2 Rating: 0 out of 5 stars0 ratingsSimply Programming C# and Visual Basic …: C# and Visual Basic Rating: 0 out of 5 stars0 ratings
Computers For You
SQL QuickStart Guide: The Simplified Beginner's Guide to Managing, Analyzing, and Manipulating Data With SQL Rating: 4 out of 5 stars4/5Deep Search: How to Explore the Internet More Effectively Rating: 5 out of 5 stars5/5CompTIA Security+ Get Certified Get Ahead: SY0-701 Study Guide Rating: 5 out of 5 stars5/5The ChatGPT Millionaire Handbook: Make Money Online With the Power of AI Technology Rating: 0 out of 5 stars0 ratingsCreating Online Courses with ChatGPT | A Step-by-Step Guide with Prompt Templates Rating: 4 out of 5 stars4/5Mastering ChatGPT: 21 Prompts Templates for Effortless Writing Rating: 5 out of 5 stars5/5How to Create Cpn Numbers the Right way: A Step by Step Guide to Creating cpn Numbers Legally Rating: 4 out of 5 stars4/5Procreate for Beginners: Introduction to Procreate for Drawing and Illustrating on the iPad Rating: 0 out of 5 stars0 ratingsGrokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5The Professional Voiceover Handbook: Voiceover training, #1 Rating: 5 out of 5 stars5/5The Insider's Guide to Technical Writing Rating: 0 out of 5 stars0 ratingsCompTIA Security+ Practice Questions Rating: 2 out of 5 stars2/5Remote/WebCam Notarization : Basic Understanding Rating: 3 out of 5 stars3/5Dark Aeon: Transhumanism and the War Against Humanity Rating: 5 out of 5 stars5/5Artificial Intelligence: The Complete Beginner’s Guide to the Future of A.I. Rating: 4 out of 5 stars4/5Ultimate Guide to Mastering Command Blocks!: Minecraft Keys to Unlocking Secret Commands Rating: 5 out of 5 stars5/5Mindhacker: 60 Tips, Tricks, and Games to Take Your Mind to the Next Level Rating: 4 out of 5 stars4/5Everybody Lies: Big Data, New Data, and What the Internet Can Tell Us About Who We Really Are Rating: 4 out of 5 stars4/5Standard Deviations: Flawed Assumptions, Tortured Data, and Other Ways to Lie with Statistics Rating: 4 out of 5 stars4/5Elon Musk Rating: 4 out of 5 stars4/5Network+ Study Guide & Practice Exams Rating: 4 out of 5 stars4/5What Video Games Have to Teach Us About Learning and Literacy. Second Edition Rating: 4 out of 5 stars4/5
Reviews for The Complete Friday Q&A
0 ratings0 reviews
Book preview
The Complete Friday Q&A - Mike Ash
Index
About The Complete Friday Q&A: Volume II
Friday Q&A is a biweekly series on Mac programming. It can be found online at https://mikeash.com/pyblog/. Volume II is a full archive of all posts from November 2010 to November 2012.
The author gratefully acknowledges all of the topic and comment contributions to Friday Q&A from its readers.
The Complete Friday Q&A: Volume II Copyright © 2010-2017 by Michael Ash
Mike Ash
mike@mikeash.com
https://mikeash.com/
Introduction
It's been a long time since the release of The Complete Friday Q&A: Volume I, and it's well past time for the next collection in the set. We have a wide variety of great topics for you, from multithreading to disassemblers to the Fast Fourier Transform. The Let's Build series, my personal favorite, showcases the inner workings of everything from reference counting to NSDictionary to tagged pointers.
Volume II was originally intended to contain all articles through 2016. As I worked on the book, I realized that it was much too big. Although it's primarily an e-book, I like having a print edition available, and finding a print-on-demand service for such a large book proved to be impossible. I solved the problem by splitting the content into Volumes II and III to be released simultaneously. This is the first half of what was going to be that one gigantic book.
One big change in Volume II is that Friday Q&A is no longer a solo effort. Guest authors Gwynne Raskind and Chris Liscio also have articles in this collection. Gwynne and Chris are extremely smart people who were able to write about topics I could not personally cover, and I'm extremely pleased to be able to present their articles along my own. Their articles are indicated by bylines under the article title. Articles without bylines are mine.
I hope you enjoy the unusual and occasionally absurd programming content collected here. As always, if you have an idea for a topic that you'd like to see covered in Friday Q&A, send it in!
Acknowledgements
Special thanks go to Chris Liscio and Gwynne Raskind, who contributed articles included in this book. Letting someone write for your blog is like letting them stay in your house and borrow your car, and they lived up to the trust I placed in them and then some.
I would like to thank my reviewers, whose valuable input dramatically improved this book. They are: Harry Jordan, Steven Vandeweghe, Matthias Neeracher, Phil Holland, Matthias Neeracher, Alex Blewitt, Landon Fuller, Joshua Pokotilow, and Cédric Luthi.
I would also like to thank everyone who contributed the topic ideas used throughout this book. Their names can be found at the beginning of each chapter.
Finally, I would like to thank everyone who has commented on one of my posts, e-mailed about Friday Q&A, or merely read it. No matter what your contribution, it is appreciated.
Dedication
The Complete Friday Q&A Volumes II and III are dedicated to the memory of my friend and fellow glider club member Steve Zaboji. Steve was killed in a plane crash the same afternoon that I received the proofs of these books. He was a central part of the club and will be deeply missed.
Friday Q&A 2010-11-06:
Creating Classes at Runtime in Objective-C
Friday Q&A is back! I had some very important slacking to take care of for the past couple of months, but now I'm ready to resume business as usual. For this return to Friday Q&A, I'm going to talk about how to create Objective-C classes at runtime, a topic suggested by Kevin Avila. This topic is meaty enough that this will be a two-parter; today's post will talk about the basics of how to create classes at runtime, and then the next one will discuss uses for such classes and how to take advantage of them.
MAObjCRuntime
One of the things I did during my off time was build
MAObjCRuntime
, a nice OO wrapper around a lot of common runtime functionality, including everything that I'm going to talk about today. For my discussion today I will not involve
MAObjCRuntime
, so that you can see how to use the runtime directly.
If you decide to use these techniques on your own, I'd recommend using
MAObjCRuntime
instead, as it makes life considerably easier.
What and Why
What exactly does it mean to create a class at runtime? If you've done any Objective-C at all, you know what it means to create a class. You create an
@interface
block, an
@implementation
block, add instance variables and methods, and you have a class that you can use.
Creating a class at runtime gives you the same result. The difference is that you write code which calls into the runtime to create class structures in memory directly, rather than writing classes to be interpreted by the compiler. You can add methods and instance variables just as you would normally.
Why would you do such a thing? It's often handy to create new classes at runtime to override functionality in arbitrary classes. For example,
MAZeroingWeakRef
does this in order to catch memory management events in order to implement zeroing weak references.
Creating a Class
The act of creating a class is accomplished using the
objc_allocateClassPair
function in
objc/runtime.h
. You pass it a superclass, a name, and a size for per-class storage (generally best left at
0
), and it returns a class to you:
Class
mySubclass
=
objc_allocateClassPair
([
NSObject
class
],
MySubclass
,
0
);
And that's it! Of course this is pretty boring, since the new class doesn't do anything yet. I'll cover how to actually put things into it shortly.
An aside: why is it called "allocate class pair"? As you probably already know, all Objective-C classes are also Objective-C objects. You can put them in variables, send them messages, add them to arrays, etc. just like you would with any other object. All objects have a class, and the class of a class is called the metaclass. Each class has a unique metaclass, and thus the pair:
objc_allocateClassPair
allocates both the class and the metaclass together.
A full discussion of what the metaclass is and how it works is beyond the scope of this post, but Greg Parker has a good discussion of metaclasses if you're interested in reading more.
Adding Methods
You know how to create a class, but it won't do anything interesting unless you actually put things in it.
Methods are the most obvious things to add to a newly created class. You add methods to a class using the
class_addMethod
function in
objc/runtime.h
. This function takes four parameters.
The first two parameters are the class you want to manipulate, and the selector of the method that you want to add. Both of these should be pretty obvious.
The next parameter is an
IMP
. This type is a special Objective-C
typedef
for a function pointer. It's defined as:
typedef
id
(
*
IMP
)(
id
,
SEL
,
...);
Objective-C methods take two implicit parameters,
self
and
_cmd
, which are the first two parameters listed here. The other parameters are not listed, and are up to you.
To create the
IMP
that you pass to this function, implement a function that takes
id
self
and
SEL
_cmd
as its first two parameters. The rest of the parameters are the parameters that the method will take, and the return type is the method return type.
For example, let's say you wanted to write an
IMP
with this signature:
-
(
NSUInteger
)
countOfObject:
(
id
)
obj
;
You'd write the function like this:
static
NSUInteger
CountOfObject
(
id
self
,
SEL
_cmd
,
id
obj
)
Unfortunately, the type of this function doesn't match the
IMP
typedef, so you have to cast it when passing it to
class_addMethod
.
The last parameter is a type encoding string which describes the type signature of the method. This is the string that the runtime uses to generate the
NSMethodSignature
that's returned from
methodSignatureForSelector:
, among other uses.
The best way to generate this type encoding string is to retrieve it from an existing class which has a method with the same signature. This way you can just trust the compiler to get it right and don't have to worry about the details of how these strings are put together. For example, the method above has the same signature as
-[NSArray
indexOfObject:]
, so you can retrieve that type encoding string:
Method
indexOfObject
=
class_getInstanceMethod
([
NSArray
class
],
@selector
(
indexOfObject:
));
const
char
*
types
=
method_getTypeEncoding
(
indexOfObject
);
If no existing class has a matching method, consider writing a small dummy class that does, and then querying it.
If you absolutely must build your own type encoding string (not recommended), then you can do it using the
@encode
directive to generate strings for the individual components, then combine them. Compiler-generated strings also have numeric stack offset information embedded in them, which means that your string won't completely match its output, but it's often good enough.
The components of a method's type encoding string are simply the
@encode
representation of the return type, followed by the argument types, including the two implicit parameters at the beginning:
NSString
*
typesNS
=
[
NSString
stringWithFormat:
@%s%s%s%s
,
@encode
(
NSUInteger
),
@encode
(
id
),
@encode
(
SEL
),
@encode
(
id
)];
const
char
*
typesC
=
[
typesNS
UTF8String
];
But again, avoid this if it's at all possible.
Here's a full example of adding a
description
method to a newly created class:
static
NSString
*
Description
(
id
self
,
SEL
_cmd
)
{
return
[
NSString
stringWithFormat:
@<%@ %p: foo=%@>
,
[
self
class
],
self
,
[
self
foo
]];
}
// add Description to mySubclass
// grab NSObject's description signature so we can borrow it
Method
description
=
class_getInstanceMethod
([
NSObject
class
],
@selector
(
description
));
const
char
*
types
=
method_getTypeEncoding
(
description
);
// now add
class_addMethod
(
mySubclass
,
@selector
(
description
),
(
IMP
)
Description
,
types
);
A bit verbose, but not too difficult at all.
Adding Instance Variables
You can add instance variables to a class using the
class_addIvar
method.
The first two parameters to this function are the class to manipulate and the name of the instance variable you want to add. Both are straightforward.
The next parameter is the size of the instance variable. If you're using a plain C type as the instance variable, then you can simply use
sizeof
to get the size.
Next is the alignment of the instance variable. This indicates how the instance variable's storage needs to be aligned in memory, potentially with padding in between it and the end of the previous instance variable. A trick to this parameter is that it's the log2 of the alignment rather than the alignment itself. Passing
1
means aligning it to a 2-byte boundary, passing
4
means 16-byte alignment, etc. Since most types want to be aligned to their size, you can simply use
rint(log2(sizeof(type)))
to generate the value of this parameter.
The last parameter is a type encoding string for the parameter. This can be generated using the
@encode
directive and giving it the type of the variable that you're adding.
Here's a full example of adding an
id
instance variable:
class_addIvar
(
mySubclass
,
foo
,
sizeof
(
id
),
rint
(
log2
(
sizeof
(
id
))),
@encode
(
id
));
Accessing Added Instance Variables
Accessing this newly-added variable is not as easy as it normally would be. You can't just write
foo
in your code, because the compiler has no idea that this thing even exists.
The runtime provides two functions for accessing instance variables:
object_setInstanceVariable
and
object_getInstanceVariable
. They take an object and a name, and either a value to set, or a place to put the current value. Here's an example of getting and setting the
foo
variable constructed above:
id
currentValue
;
object_getInstanceVariable
(
obj
,
foo
,
&
currentValue
);
// it will be replaced, so autorelease
[
currentValue
autorelease
];
id
newValue
=
...;
[
newValue
retain
];
// runtime won't retain for us
object_setInstanceVariable
(
obj
,
foo
,
newValue
);
Another way is to simply use key-value coding to read and write the instance variable. As long as you don't have a method with the same name, it will directly access the variable's contents. It will also do proper memory management on object-type variables. As a potential downside, it will box primitive values in
NSValue
or
NSNumber
objects, which could add complication.
With either technique, don't forget to add a
dealloc
method to release your object instance variables.
If you need per-instance storage, consider using the associated object API (
objc_setAssociatedObject
and
objc_getAssociatedObject
) instead of instance variables. It takes care of memory management for you.
Adding Protocols
You can add a protocol to a class using
class_addProtocol
. This is not usually very useful, so I won't go into how to use it. Keep in mind that this function only declares the class as conforming to the protocol in question, but it doesn't actually add any code. If you want the class to actually implement the methods in a protocol, you have to implement and add those methods yourself.
Adding Properties
Although there are plenty of functions for querying the properties of a class, Apple apparently forgot to provide any way to add a property to a class. Fortunately, like protocols, it's not usually very useful to add a property to a class at runtime, so this is not a big loss.
Registering the Class
After you're done setting up the class, you have to register it before you can use it. You do this with the
objc_registerClassPair
function:
objc_registerClassPair
(
mySubclass
);
It's now ready to use.
Note that you must register a class before you use it, and you can't add any instance variables to a class after you register it. You can add methods to a class after registration, however.
Using the Class
Once you've registered the class, you can message it just like you would any other class:
id
myInstance
=
[[
mySubclass
alloc
]
init
];
NSLog
(
@%@
,
myInstance
);
You can access the class using
NSClassFromString
as well, and in general it behaves just like any other class at this point.
Friday Q&A 2010-11-19:
Creating Classes at Runtime for Fun and Profit
It's time for another edition of Friday Q&A. In the last Friday Q&A, I discussed how to create classes at runtime in Objective-C. Today, I'm going to discuss how to actually make practical use of this technique.
MAObjCRuntime
In my last article, I avoided making use of
MAObjCRuntime
, my object-oriented wrapper around the Objective-C runtime, in order to show how to use the Objective-C runtime directly. Since I've already shown how to call the runtime directly, I'll be making use of
MAObjCRuntime
today in order to make the code simpler to read and write. If you're interested in doing things the old-fashioned way, the
MAObjCRuntime
calls translate pretty directly to runtime calls, it's just more verbose and less convenient.
While I think the calls will be pretty obvious, you may want to read
MAObjCRuntime
's readme to see how it works before continuing.
Conditional Subclassing
A common problem in Mac and iOS development is supporting multiple OS versions simultaneously. Often it's necessary to use a class that exists on newer versions, but avoid using it on older versions where it's not available. Normally this can be done by using calls like
NSClassFromString
. However, sometimes it's necessary to subclass one of these classes, and the normal technique of writing
@interface
YourClass
:
ThisMightNotExist
doesn't work too well.
(It is possible to use the normal syntax by using weak linking to pull in the class which may not exist. Unfortunately, weak linking of Objective-C classes is not very mature yet, and you have to wait for it to work all the way back to the earliest OS version that you support.)
By creating the subclass at runtime only if the superclass exists, you can make code that works properly in both cases without too much effort.
As an example, I'll show how to create a subclass of a hypothetical
NSFoo
class which might not exist at runtime. The subclass will override a single method from its superclass,
-bar
, to do nothing.
First, write the function that implements the
-bar
method. As you'll recall from last time, you just write a C function that takes
id
self
and
SEL
_cmd
as its first two parameters, with the other parameters and the return type as usual. Here's the simple do-nothing function:
static
void
BarOverride
(
id
self
,
SEL
_cmd
)
{
}
Now, a function to create the subclass. The first thing it does is use
rt_createSubclassNamed:
to make the class.
static
Class
CreateMyFoo
(
void
)
{
Class
NSFoo
=
NSClassFromString
(
@NSFoo
);
if
(
!
NSFoo
)
return
nil
;
Class
myFoo
=
[
NSFoo
rt_createSubclassNamed:
@MyFoo
];
Now to add the new
-bar
method. We first get the method from the superclass so I can borrow its type signature string, then create a new
RTMethod
and add it to the subclass:
SEL
sel
=
@selector
(
bar
);
RTMethod
*
nsBar
=
[
NSFoo
rt_methodForSelector:
sel
];
RTMethod
*
myBar
=
[
RTMethod
methodWithSelector:
sel
implementation:
(
IMP
)
BarOverride
signature:
[
nsBar
signature
]];
[
myFoo
rt_addMethod:
myBar
];
That's it! Now just return the newly created class:
return
myFoo
;
}
To complete the code, we just need a simple wrapper function that creates the above class just once and stores it, then returns it on demand. (I'm using GCD for thread safety, which may defeat the purpose of compatibility with older OSes, but it can easily be replaced with another method.)
static
Class
MyFoo
(
void
)
{
static
Class
c
=
nil
;
static
dispatch_once_t
pred
;
dispatch_once
(
&
pred
,
^
{
c
=
CreateMyFoo
();
});
return
c
;
}
Because
CreateMyFoo
returns
nil
if
NSFoo
doesn't exist,
MyFoo
will also return
nil
in that case. All the caller has to do to use
MyFoo
is something like this:
Class
myFoo
=
MyFoo
();
if
(
myFoo
)
{
id
foo
=
[[
myFoo
alloc
]
init
];
// ...
}
Calling
super
It's very common to call
super
in an overridden method. Unfortunately, the
super
keyword isn't valid in a C function, even if you're using it to implement an Objective-C method.
It's still possible, and not too hard, but you have to be a little more roundabout. You need to manually retrieve the method pointer from the superclass, then directly call it. Here's what
BarOverride
would look like just calling through to
super
:
static
void
BarOverride
(
id
self
,
SEL
_cmd
)
{
Class
superclass
=
NSClassFromString
(
@NSFoo
);
void
(
*
superIMP
)(
id
,
SEL
)
=
(
void
*
)[
superclass
instanceMethodForSelector:
@selector
(
bar
)];
superIMP
(
self
,
_cmd
);
}
Simply calling through to
super
is not all that useful, but you can add your own code before and after the call to augment it.
Example Subclass
Here's a more realistic example that subclasses the hypothetical
NSMysteryView
to add custom drawing and event handling:
static
DrawRect
(
id
self
,
SEL
_cmd
,
NSRect
rect
)
{
// draw the original stuff, but on top of a black background
[[
NSColor
blackColor
]
setFill
];
NSRectFill
(
rect
);
// black background is down, now draw the original
Class
superclass
=
NSClassFromString
(
@NSMysteryView
);
void
(
*
superIMP
)(
id
,
SEL
,
NSRect
)
=
[
superclass
instanceMethodForSelector:
@selector
(
drawRect:
)];
superIMP
(
self
,
_cmd
,
rect
);
}
static
MouseUp
(
id
self
,
SEL
_cmd
,
NSEvent
*
event
)
{
// beep if the mouse is clicked in this view
NSBeep
();
}
// encapsulate code needed to override an existing method
static
void
Override
(
Class
c
,
SEL
sel
,
void
*
fptr
)
{
RTMethod
*
superMethod
=
[[
c
superclass
]
rt_methodForSelector:
sel
];
RTMethod
*
newMethod
=
[
RTMethod
methodWithSelector:
sel
implementation:
fptr
signature:
[
superMethod
signature
]];
[
c
rt_addMethod:
newMethod
];
}
static
Class
CreateMyMysteryView
(
void
)
{
Class
NSMysteryView
=
NSClassFromString
(
@NSMysteryView
);
if
(
!
NSMysteryView
)
return
nil
;
Class
c
=
[
NSMysteryView
rt_createSubclassNamed:
@MyMysteryView
];
Override
(
c
,
@selector
(
drawRect:
),
DrawRect
);
Override
(
c
,
@selector
(
mouseUp:
),
MouseUp
);
return
c
;
}
Intercepting Calls with Dynamic Subclassing
Sometimes you need to intercept a call to an arbitrary object without knowing in advance what it will be. By creating a subclass of the target's class at runtime, and then swizzling the class of the target object, you can accomplish this interception. Cocoa's Key-Value Observing does this, as does my own
MAZeroingWeakRef
. Here, I'll walk through how you can do this for yourself. For a simple example, I'll show a sort of light version of what
MAZeroingWeakRef
does by making code that simply observes when an object is deallocated by overriding its
dealloc
method.
A quick note: none of the code that I'm going to show is thread safe, just to keep it simple. Since this is the sort of low-level code that you might want to use from multiple threads (especially since
dealloc
could happen on other threads), keep in mind that a more practical version would want to lock access to all of the shared data.
When the object is about to be destroyed, it will post a notification. Of course we'll want a constant to hold the notification name:
NSString
*
MyObjectWillDeallocateNotification
=
@MyObjectWillDeallocateNotification
;
Now, we only want to create one subclass of any given class. If two objects of the same class are passed in to the system, they should both be swizzled to the same dynamic subclass, rather than creating two identical dynamic subclasses. To accomplish this, we'll keep a dictionary that maps from classes to subclasses. It's also useful to keep a set of the already-created subclasses:
static
NSMutableDictionary
*
gSubclassesDict
;
static
NSMutableSet
*
gSubclasses
;
Now, a small function to query the dictionary and either return what it contains, or call a function to create the subclass, insert it into the dictionary, and return that:
static
Class
GetSubclassForClass
(
Class
c
)
{
Class
subclass
=
[
gSubclassesDict
objectForKey:
c
];
if
(
!
subclass
)
{
subclass
=
CreateSubclassForClass
(
c
);
[
gSubclassesDict
setObject:
subclass
forKey:
c
];
[
gSubclasses
addObject:
subclass
];
}
return
subclass
;
}
Before we can write
CreateSubclassForClass
, we need to write a
dealloc
override. All this override needs to do is post the notification and then call
[super
dealloc]
. However, calling
super
is complicated because not only is this a dynamically allocated class, but one whose superclass isn't known at compile time. Thus we need to perform a search at runtime to find the correct superclass.
It is not correct to simply use
[self
superclass]
in this case. Although our dynamic subclass is probably the very last class to be set, and so
[self
superclass]
would probably return the correct answer, it's not guaranteed. Some other piece of code (like KVO) could have pulled the same dynamic subclassing trick that we're pulling after we did it, which means their class would be at the bottom instead of our. To be truly robust, we have to search in a loop until we come across a class that has an entry in
gSubclassesDict
, and then that is the one where we need to send our
dealloc
.
Here's what the full
dealloc
override function looks like:
static
void
Dealloc
(
id
self
,
SEL
_cmd
)
{
[[
NSNotificationCenter
defaultCenter
]
postNotificationName:
MyObjectWillDeallocateNotification
object:
self
];
Class
c
=
[
self
rt_class
];
while
(
c
&&
!
[
gSubclassesDict
objectForKey:
c
])
c
=
[
c
superclass
];
// if it wasn't found, something went horribly wrong
assert
(
c
);
void
(
*
superIMP
)(
id
,
SEL
)
=
[
c
instanceMethodForSelector:
@selector
(
dealloc
)];
superIMP
(
self
,
_cmd
);
}
Now we're ready to write
CreateSubclassForClass
. There's nothing complex here, this looks just like the other subclass-creation code we've written:
static
Class
CreateSubclassForClass
(
Class
c
)
{
// give the subclass a sensible name
NSString
*
name
=
[
NSString
stringWithFormat:
@%@_MyDeallocNotifying
,
[
class
name
]];
Class
subclass
=
[
c
rt_createSubclassNamed:
name
];
// use the Override function from above
Override
(
c
,
@selector
(
dealloc
),
Dealloc
);
return
subclass
;
}
Finally, a function to transform an object to post this notification. We start out by checking to see if it's already been added by seeing if it has one of our subclasses in its hierarchy:
void
MakeObjectPostDeallocNotification
(
id
obj
)
{
Class
c
=
[
obj
rt_class
];
while
(
c
&&
!
[
gSubclasses
containsObject:
c
])
c
=
[
c
superclass
];
// if we found one, then nothing else to do
if
(
c
)
return
;
// not yet set, grab the subclass
c
=
GetSubclassForClass
([
obj
rt_class
]);
// set the class of the object to the subclass
[
obj
rt_setClass:
c
];
}
That's it! You can now call
MakeObjectPostDeallocNotification(obj)
and then use
NSNotificationCenter
to listen for the notification.
Caveats
There are some gotchas to this technique. From easiest to hardest:
Archiving: if a targeted object is archived, the archiver will record the custom subclass. When unarchiving, it will try to instantiate that custom subclass. Since these classes are created dynamically, that class may not exist yet, preventing the unarchiver from instantiating the object. To fix this, you can override
classForCoder
in the subclass to return the superclass, so that the archiver records the correct class.
KVO subclasses: as I mentioned previously, KVO makes use of this same technique. Unfortunately, Apple's code does not tolerate having subclasses created beneath its own custom classes. To work around this, you can subclass the real
class and then insert your new subclass into the class hierarchy between the KVO class and the real
class.
MAZeroingWeakRef
shows how to do this, just search for KVO
.
CoreFoundation bridged classes: due to how toll-free bridging is implemented, you cannot subclass the bridged classes, nor can you reliably intercept messages to them in other ways. Depending on exactly what you're doing, you may be able to use some really nasty hacks, but in general your best bet is to simply not try to mess with bridged classes.
Conclusion
Creating classes at runtime is a powerful technique. Today I've shown how you can use it to create subclasses of classes that you can't reference at compile time, and to dynamically create subclasses of arbitrary classes in order to intercept calls to them. This is the sort of thing you can use to blow a very large hole in your foot, but it can also let you do things that simply can't be done otherwise.
Friday Q&A 2010-12-03:
Accessors, Memory Management, and Thread Safety
Related Articles
Custom Object Allocators in Objective-C
A Tour of OSAtomic
The Inner Life of Zombies
Let's Build Reference Counting
Automatic Reference Counting
Fork Safety
Ring Buffers and Mirrored Memory: Part I
Ring Buffers and Mirrored Memory: Part II
Nib Memory Management
It's once again time for a brand new edition of Friday Q&A. This week, I'm going to talk about accessors, and how to properly deal with memory management and thread safety when creating them, a topic suggested by Daniel Jalkut.
More Complicated Than You Might Think
Cocoa's reference counting memory management usually works pretty well, and for the most part accomplishes its goal of making memory management decisions a local, rather than global, affair.
There's a big exception. Spot the bug in this code:
NSMutableDictionary
*
dict
=
...;
id
obj
=
[
dict
objectForKey:
@foo
];
[
dict
removeObjectForKey:
@foo
];
[
obj
something
];
This bug isn't too hard to find. By removing
obj
from
dict
, you've potentially destroyed the object, which then causes your program to crash on the last line.
Of course, this bug isn't always this easy to find. The removal may be buried several method calls deep, and the object may sometimes be retained elsewhere, hiding the bug and making your program crash only inconsistently.
The same problem can happen with a regular object and accessors:
id
obj
=
[
otherObj
foo
];
[
otherObj
setFoo:
newFoo
];
[
obj
something
];
// crash
This is not necessarily a problem. It all depends on how exactly
-foo
and
-setFoo:
are implemented.
Basic Accessors
The most basic accessors look something like this:
-
(
void
)
setFoo:
(
id
)
newFoo
{
[
newFoo
retain
];
[
_foo
release
];
_foo
=
newFoo
;
}
-
(
id
)
foo
{
return
_foo
;
}
With these basic accessors, the example is buggy and must be avoided.
This does not mean that these basic accessors are wrong. It does mean that you need to be more careful when writing code that uses them:
id
obj
=
[[
otherObj
foo
]
retain
];
[
otherObj
setFoo:
newFoo
];
[
obj
something
];
// safe
[
obj
release
];
Since most code doesn't set new values before using the old ones, most code won't need to worry about the problem at all. However, since you do sometimes write code like this, you do need to keep this problem in mind, and code accordingly.
Autoreleasing Accessors
Rather than make callers retain and release temporary objects, another approach is to modify the accessors to use
autorelease
. You can do this in the setter:
-
(
void
)
setFoo:
(
id
)
newFoo
{
[
_foo
autorelease
];
_foo
=
[
newFoo
retain
];
}
However, this still leaves you vulnerable to a crash if there's a temporary autorelease pool set up when the setter is called. It's better to do it in the getter instead:
-
(
id
)
foo
{
return
[[
_foo
retain
]
autorelease
];
}
This
retain
/
autorelease
combo keeps the object alive even after the setter releases it.
This solves the problem, but there are a couple of downsides. The obvious one is that it's less efficient. Using
autorelease
is a bit slower than
release
, and it keeps the target object alive longer, which could lead to more memory usage. This isn't usually very important, but it's something to keep in mind.
More importantly, pervasive use of
autorelease
can make it harder to track down memory management errors. If you over-
release
an object, you want to crash as soon as possible. Using
autorelease
can often cause the crash to be in
NSAutoreleasePool
code, making it considerably harder to track down. Using zombies and Instruments will help a lot, but it can still make your job harder.
Thread Safe Accessors
When writing thread safe accessors, the
autorelease
getter becomes mandatory. Another thread could call the setter after it returns, and the
retain
/
autorelease
dance is the only way to ensure that this does not destroy the previous object in the middle of being used.
Thread safe accessors, like any access to shared data, need to use a lock. You can use
@synchronized(self)
, an instance of
NSLock
, or whatever other locking mechanism you prefer.
(Lockless access to shared data is, of course, possible, but far too tricky and difficult to cover here. Better to skip it and use locks.)
By locking all shared access, and by using the
autorelease
version of the getter, you end up with thread safe accessors:
-
(
void
)
setFoo:
(
id
)
newFoo
{
[
newFoo
retain
];
@synchronized
(
self
)
{
[
_foo
release
];
_foo
=
newFoo
;
}
}
-
(
id
)
foo
{
id
returnFoo
;
@synchronized
(
self
)
{
returnFoo
=
[
_foo
retain
];
}
return
[
returnFoo
autorelease
];
}
The locking adds a performance penalty and increases the complexity of the code, so you should only use this style when you need it.
Do You Need Thread Safe Accessors?
In almost every case, the answer to this question is no
. Accessors are usually not the right place to worry about thread safety.
The problem is that thread safety isn't a composable attribute. If you take a bunch of thread safe components and bundle them together, the result may not be thread safe.
For a simple example, imagine a
Person
class with properties for the first and last name of the person it represents. One thread does this:
[
person
setFirstName:
@John
];
[
person
setLastName:
@Doe
];
While another thread does this:
NSString
*
f
=
[
person
firstName
];
NSString
*
l
=
[
person
lastName
];
NSString
*
fullName
=
[
NSString
stringWithFormat:
@%@ %@
,
f
,
l
];
Thread safe accessors will keep this code from crashing, but it does not protect against an inconsistent result! If the previous name was Bob Smith
, this code could easily result in a
fullName
of John Smith
, which doesn't correspond to either person.
In order to make this code safe, thread safety needs to be applied at a higher level. For example, you might have a
PersonDatabase
object which could be locked and unlocked for any manipulations. You might decree that all
Person
access go through a single serial Grand Central Dispatch queue. You might just make all
Person
access happen on the main thread.
No matter which solution you pick, once you've picked it, the thread safe accessors are no longer necessary. The larger-scale thread safety takes care of problems, and all the thread safe accessors do is make your code unnecessarily slow and complex.
There are cases where a thread safe accessor is useful. You may have a single property that's accessed from many threads and which won't experience problems being inconsistent with other properties, in which case you'd want a thread safe accessor. However, 99% of the time there is no point in making them.
Properties and
nonatomic
Given the above, it's extremely puzzling that Apple has made
@property
declarations default to
atomic
. Most of the time it's pointless, and it can give the mistaken impression that the programmer doesn't have to worry about thread safety anymore, because the
@property
handles it all.
The
@property
and
@synthesize
constructs can be a good way to generate accessors without writing code, just be aware that the default
atomic
behavior is not all that useful, and doesn't mean you can forget about thread safety.
Garbage Collection
[Objective-C garbage collection is no longer supported by Apple, so this section is highly obselete. Consider it to be a little piece of history.]
If you're using garbage collection, this whole question becomes vastly simpler. Here's what a correct, thread safe accessor pair looks like in garbage collected code:
-
(
void
)
setFoo:
(
id
)
newFoo
{
_foo
=
newFoo
;
}
-
(
id
)
foo
{
return
_foo
;
}
The lack of explicit memory management calls really simplifies accessors here.
Conclusion
Writing accessors is easy, but there are some subtleties. The vast majority of the time, a plain
retain
/
release
setter and a
return
_ivar
getter is all you need. However, depending on your situation and your individual taste, you may want to put
retain
/
autorelease
into the getter in order to simplify the code that calls it.
When it comes to thread safety, accessors are usually the wrong place to worry about it. While there are legitimate cases where it's useful, most of the time you should back up and take on the problem of thread safety at a higher level of your code. And don't let the
atomic
@property
keyword fool you: it doesn't do anything special in this regard.
If you're coding exclusively for garbage collection, you can pretty much forget about this whole business and write much simpler code.
Friday Q&A 2010-12-17:
Custom Object Allocators in Objective-C
Related Articles
Accessors, Memory Management, and Thread Safety
The Inner Life of Zombies
Let's Build Reference Counting
Automatic Reference Counting
Ring Buffers and Mirrored Memory: Part I
Ring Buffers and Mirrored Memory: Part II
Nib Memory Management
Merry holidays, happy winter, and a joyous Friday Q&A to you all. Camille Troillard suggested that I discuss how to create custom object memory allocators in Objective-C, and today I'm going to walk through how to accomplish this and why you might want to.
What It Means
As anyone who uses Objective-C knows, you allocate an instance of a class by writing
[MyClass
alloc]
. Creating a custom allocator simply means that replace the standard allocator so that
[MyClass
alloc]
calls into your own code instead.
An Objective-C object is just a chunk of memory with the right size, and with the first pointer-sized chunk set to point at the object's class. A custom allocator thus needs to return a pointer to a properly-sized chunk of memory, with the class filled out appropriately.
Why It's Useful
By far the largest reason to write a custom allocator is for performance. The standard allocator makes tradeoffs which may not be appropriate for your particular case. It also has to work with every class in every situation, whereas your custom allocator only needs to work with your class and the situations it's used in.
Another reason is overhead. The standard allocator requires a certain amount of extra storage for each allocation for various reasons. This can be particularly expensive for very small objects allocated in very large numbers. A custom allocator can cut down on this overhead substantially by tailoring it to the needs of the class it's written for.
A Basic Custom Allocator
The
+alloc
method actually just calls through to
+allocWithZone:
. Although memory zones are pretty much just a historical curiosity at this point, they remain in the API. Thus the method to override is
+allocWithZone:
+
(
id
)
allocWithZone:
(
NSZone
*
)
zone
{
For a simple allocator example, I'll just call
calloc
. This will have roughly zero advantages over the standard allocator, but shows how it can be done. (I'm using
calloc
instead of
malloc
because Objective-C code assumes that instance variables are zeroed out.)
In order to call
calloc
, you need to know how much memory to allocate. Fortunately, the Objective-C runtime makes it easy. The
class_getInstanceSize
function will tell you exactly this:
id
obj
=
calloc
(
class_getInstanceSize
(
self
),
1
);
Next, you need to set the isa of this newly-allocated object. The isa is found right at the beginning of the object, and a bit of judicious casting lets you set it easily:
*
(
Class
*
)
obj
=
self
;
You can now return the newly created object:
return
obj
;
}
We're not done yet. We also have to override
-dealloc
to call
free
:
-
(
void
)
dealloc
{
free
(
self
);
Normally this would be all. However, the compiler has a warning for
-dealloc
methods that don't call through to
super
. In order to shut up this warning, I insert a dummy call after a
return
statement which prevents it from executing:
return
;
[
super
dealloc
];
// shut up compiler
}
Your custom allocator is all ready to go.
Gotchas
As with most things at this level, there are a few things to watch out for.
First, don't do this unless you subclass
NSObject
directly. The
-dealloc
method covers both destroying the object itself, and freeing resources it holds.
-[NSObject
dealloc]
just destroys the object (mostly) so it's safe not to call it. It's not safe to do this for any other class, though. For example, if you tried this with an
NSView
subclass, you'd end up leaking a whole bunch of internal state.
Second, the mostly
from above means there are some things that
NSObject
does that you need to think about. One is removing associated objects. If your objects may have associated objects, or you think there's even a chance that it might, then you need to make sure they're removed. This can be done by calling
objc_removeAssociatedObjects(self)
. The other is calling destructors for C++ objects in instance variables. Your best bet here is to just avoid having C++ objects as instance variables. If you must have them, look into the possibility of calling or imitating the private runtime function
objc_destructInstance
, which takes care of both C++ destructors and associated objects.
Third, memory debugging tools like ObjectAlloc and zombies won't work on objects with a custom allocator. For this reason, I recommend that you have a memory debugging preprocessor define which makes your objects use the standard allocator instead of your custom allocator, so that you can flip the switch and use these tools if need be.
Caching Objects
For a realistic example, I'll write an allocator that places destroyed objects in a cache so that they can be quickly reused. This sort of thing is useful for classes which are allocated and destroyed so frequently that the standard allocator is too slow.
In order to reach maximum speed, I'll make a few assumptions about how this class works and is used:
It is never subclassed, or if it is, subclasses never add instance variables. (This allows it to put all instances in the same cache.)
Its initializer methods can deal with a dirty
object; i.e. the instance variables don't need to be zeroed out. (This saves time zeroing out each instance when pulling it out of the cache.)
It is only ever allocated and destroyed from the same thread. (This makes it unnecessary to create a thread-safe cache.)
I'll ignore just how the cache works for now, and just assume it presents a simple interface of two functions:
AddObjectToCache
and
GetObjectFromCache
. The
+allocWithZone:
override then looks like this:
+
(
id
)
allocWithZone:
(
NSZone
*
)
zone
{
id
obj
=
GetObjectFromCache
();
if
(
obj
)
*
(
Class
*
)
obj
=
self
;
else
obj
=
[
super
allocWithZone:
zone
];
return
obj
;
}
The
-dealloc
override simply returns the object to the cache:
-
(
void
)
dealloc
{
// release any ivars here
AddObjectToCache
(
self
);
// shut up the compiler
return
;
[
super
dealloc
];
}
The cache itself is just a linked list, using the
isa
slot of each object to point to the next entry in the list. The list head is a global variable:
static
id
gCacheListHead
;
Next, I want a couple of helper functions for accessing the
next
pointer of each list item:
static
id
GetNext
(
id
cachedObj
)
{
return
*
(
id
*
)
cachedObj
;
}
static
void
SetNext
(
id
cachedObj
,
id
next
)
{
*
(
id
*
)
cachedObj
=
next
;
}
With these helpers, the two main cache functions are easy to write:
static
id
GetObjectFromCache
(
void
)
{
id
obj
=
gCacheListHead
;
if
(
obj
)
gCacheListHead
=
GetNext
(
obj
);
return
obj
;
}
static
void
AddObjectToCache
(
id
obj
)
{
SetNext
(
obj
,
gCacheListHead
);
gCacheListHead
=
obj
;
}
With this system in place, objects are initially allocated normally, but then go into the cache when destroyed. Once the cache has objects, new objects come out of it, which is much faster than allocating new memory.
Custom Block Allocator
Caching objects can be a big speed boost, but the initial allocations are not accelerated, and you still have the space overhead of all of those small allocations. By allocating a large block of memory and chopping it up into chunks, it's possible to speed up the initial allocations and vastly decrease the per-object overhead. To do this, I'll use the same object cache scheme as above, but with a modification to the
+allocWithZone:
implementation:
+
(
id
)
allocWithZone:
(
NSZone
*
)
zone
{
id
obj
=
GetObjectFromCache
();
if
(
!
obj
)
{
AllocateNewBlockAndCache
(
self
);
obj
=
GetObjectFromCache
();
}
*
(
Class
*
)
obj
=
self
;
return
obj
;
}
All of the interesting stuff will then happen in
AllocateNewBlockAndCache
. The first thing this function will do is allocate a large block of memory. I chose
4096
for the block size as it matches the page size used by OS X and is a convenient number to work with:
static
void
AllocateNewBlockAndCache
(
Class
class
)
{
static
size_t
kBlockSize
=
4096
;
char
*
newBlock
=
malloc
(
kBlockSize
);
Once it has this block, it needs to chop it into pieces and add each piece to the cache. To do this, it will walk through the block using
class_getInstanceSize
to mark off each instance-sized section, and then use
AddObjectToCache
to get each section into the cache:
int
instanceSize
=
class_getInstanceSize
(
class
);
int
instanceCount
=
kBlockSize
/
instanceSize
;
while
(
instanceCount
--
>
0
)
{
AddObjectToCache
((
id
)
newBlock
);
newBlock
+=
instanceSize
;
}
}
That's all there is to it. The object caching mechanism takes care of recycling old objects so that they can be used again.
Conclusion
Writing a custom object allocator in Objective-C is relatively simple. The hard part is the allocator itself, which is largely up to you. Once you have the allocator, you can plug it into your Objective-C class by:
Overriding
+allocWithZone:
to call your custom allocator, set the
isa
of the block to
self
, and optionally zero out the rest of the memory.
Overriding
-dealloc
to call your custom allocator, and do not call through to
super
.
Calling
objc_removeAssociatedObjects
in
-dealloc
if there's a chance of your object containing associated objects.
Only subclassing
NSObject
directly, and not subclassing any subclass of
NSObject
.
In addition to a full-blown custom allocator, techniques like object caching can give you a speed boost with less complexity.
Friday Q&A 2010-12-31:
C Macro Tips and Tricks
Related Articles
Compile-Time Tips and Tricks
Namespaced Constants and Functions
Things You Never Wanted To Know About C
The year is almost over, but there's time for one last Friday Q&A before 2011 comes around. For today's post, fellow Amoeba Dan Wineman suggested that I discuss tricks for writing macros in C.
Preprocessor vs Compiler
To properly understand C macros, you must understand how a C program is compiled. In particular, you must understand the different things that happen in the preprocessor and in the compiler.
The preprocessor runs first, as the name implies. It performs some simple textual manipulations, such as:
Stripping comments.
Resolving
#include
directives and replacing them with the contents of the included file.
Evaluating
#if
and
#ifdef
directives.
Evaluating
#define
s.
Expading the macros found in the rest of the code according to those
#define
s.
It is, of course, these last two which are most relevant to today's discussion.
Note that the preprocessor largely has no understanding of the text that it processes. There are some exceptions to this. For example, it knows that this is a string, and so does not expand the macro inside it:
#define SOMETHING hello
char
*
str
=
SOMETHING, world!
// nope
And it can count parentheses, so it knows that this comma does not result in two arguments to the macro:
#define ONEARG(x) NSLog x
ONEARG
((
@hello, %@
,
@world
));
But in general, it does not have any concept of what it processes. For example, you can't use
#if
to check whether a type is defined or not:
// makes no sense
#ifndef MyInteger
typedef
int
MyInteger
#endif
The
#ifndef
always comes out true even if the
MyInteger
type is already defined. Type definitions are evaluated as part of the compilation phase, which hasn't even happened yet.
Likewise, there is no need for the contents of a
#define
to be syntactically correct on their own. It is completely legal, although a poor idea, to create macros like this:
#define STARTLOG NSLog(
#define ENDLOG , @testing
);
STARTLOG
@just %@
ENDLOG
The preprocessor just blindly replaces
STARTLOG
and
ENDLOG
with their definitions. By the time the compiler comes along to try to make sense of this code, it actually does make sense, and so it compiles as valid code.
A Word of Warning
C macros are at the same time too powerful and not powerful enough. Their somewhat ad-hoc nature makes them dangerous, so treat them with care.
The C preprocessor is nearly Turing-complete. With a simple driver, you can compute any computable function using the preprocessor. However, the contortions required to do this are so bizarre and difficult that they make Turing-complete C++ templates look simple by comparison.
While powerful, macros are also very simple. Since macro expansion is a simple textual process, there are pitfalls. For example, operator precedence can be dangerous:
#define ADD(x, y) x+y
// produces 14, not 20
ADD
(
2
,
3
)
*
4
;
#define MULT(x, y) x*y
// produces 14, not 20
MULT
(
2
+
3
,
4
);
Be very careful to parenthesize everything that could possibly need it, considering both any possible arguments passed to the macro, and any possible context that the macro could be used in.
Evaluating a macro argument multiple times can also lead to unexpected results:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int
a