
Debugging gets a lot easier when your log output is readable. NSLog uses C-style format specifiers, and since Objective-C is just C with objects bolted on, most of this table will look familiar from plain C. The classic frustration is an int that prints garbage or an object that crashes the log call — the fix is always the same: match the specifier to the type.
Format specifiers for NSLog
| Type | Example constants | NSLog specifier | Notes |
|---|---|---|---|
| char | ‘a’, ‘\n’ | %c |
A single character. |
| short int | – | %hi, %hx, %ho |
h = short; i decimal, x hex, o octal. |
| unsigned short int | – | %hu, %hx, %ho |
Same as above, with u for unsigned. |
| int | 12, -97, 0xFFE0, 0177 | %i, %x, %o |
%d is equivalent to %i. |
| unsigned int | 12u, 100U, 0xFFU | %u, %x, %o |
|
| long int | 12L, -200l, 0xfffL | %li, %lx, %lo |
One l modifier. |
| long long int | 0xe5e5e5e5LL, 500LL | %lli, %llx, %llo |
Two l modifiers. |
| unsigned long long int | 0xe5e5e5e5ULL, 120ULL | %llu, %llx, %llo |
|
| float | 12.34f, 3.1e-5f | %f, %e, %g |
%f: 6 decimal places by default; %e: scientific notation; %g: whichever reads better. |
| double | 12.34, 3.1e-5 | %f, %e, %g |
Same as float. |
| long double | 12.34l, 3.1e-5l | %Lf, %Le, %Lg |
Capital L modifier. |
| id (any object) | – | %@, %p |
%@ prints the object’s description; %p prints its pointer address. |
Putting it to work
int port = 0xFFE0;
long long big = 500LL;
float ratio = 3.1e-5f;
NSString *name = @"teliaz";
NSLog(@"decimal: %i hex: %x octal: %o", port, port, port);
NSLog(@"long long: %lli float: %g", big, ratio);
NSLog(@"object: %@ pointer: %p", name, name);
Two details worth remembering. First, %@ is the Objective-C addition: it sends description (or debugDescription under the lldb-adjacent debug formats) to the logger, which is why it works on id where %i would explode. Second, these are plain printf semantics underneath — the Apple format-specifiers reference is still the authoritative list.
If you’re writing Swift now, none of this applies: interpolation (\(value)) handles it all at compile time, which is exactly why the 2010 debugging frustration above no longer exists in new code. But for anyone maintaining an older Objective-C codebase — or reading one — this table still clears things up.