Every class in Objective-C hides private properties and methods behind its public interface — but with the right Runtime calls, you can expose them all for debugging or introspection..h file was simply inaccessible. Until one day, I tried to modify the placeholder text color of a UITextField. The system clearly had this capability, but it wasn’t in the public API. Later I learned that by using the Runtime, you can traverse all member variables and methods of any class — public or private. The underlying principle isn’t complicated: it comes down to two functions: class_copyIvarList and class_copyMethodList.
I once inherited a legacy project with 200+ undocumented classes — running this code on each one saved me days of guessing what properties and methods were available. It’s also the first thing I do when debugging UIKit internals: print the ivar list, find the hidden property, and use KVC to inspect it.
Why Traverse Private Properties and Methods
In iOS development, many UIKit components have properties and methods not exposed in public header files. For example, UITextField’s placeholder — the public placeholder property only changes the text content, not the color or font. But internally, it actually has a _placeholderLabel member variable, and with KVC, you can directly set its font and color.
Another common scenario is debugging and reverse engineering. When you inherit an undocumented legacy project and need to figure out what properties and methods a class has, traversing with the Runtime prints everything clearly to the console — no source code hunting required.
Core API: Getting the Private Member Variable List
The Runtime provides class_copyIvarList to get all member variables of a class, including those declared publicly in .h, privately in .m, and the underscored variables automatically generated by @property.
#import <objc/runtime.h>
- (void)printAllIvars {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"ivar: %s, type: %s", name, type);
}
free(ivars); // Must manually free
}
Printing UITextField’s member variables reveals a bunch of private variables like _placeholderLabel, _clearButton, and more. These aren’t documented anywhere, but they exist. Once you have the variable name, you can use KVC’s setValue:forKeyPath: to directly modify them.
UITextField *textField = [[UITextField alloc] init];
textField.placeholder = @"Enter username";
[textField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
[textField setValue:[UIColor lightGrayColor] forKeyPath:@"_placeholderLabel.textColor"];
Core API: Getting the Private Method List
class_copyMethodList gets all instance methods of a class, whether or not they’re declared in the .h file. As long as they’re implemented in the .m, they’ll be traversed.
- (void)printAllMethods {
unsigned int count = 0;
Method *methods = class_copyMethodList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
const char *name = sel_getName(sel);
const char *type = method_getTypeEncoding(method);
NSLog(@"method: %s, type: %s", name, type);
}
free(methods);
}
The output includes all methods implemented in .m — private methods, setters/getters, and even the auto-generated .cxx_destruct destructor method.
The Difference Between Private Properties and Private Methods
There’s a key distinction between these APIs: class_copyIvarList gets member variables, including the _xxx storage variables behind properties. class_copyPropertyList gets properties — only those declared with @property. If a class directly declares NSString *name as a plain member variable, it won’t appear in the property list, but it will appear in the member variable list.
Private methods are more complex. In Objective-C, methods are split into “declaration” and “implementation.” Even if a method isn’t declared in .h, as long as it’s implemented in .m, class_copyMethodList will find it. If a method is only declared but not implemented, it doesn’t actually exist yet, and the Runtime won’t find it.
Traversing Superclass Properties and Methods
class_copyIvarList and class_copyMethodList only return members of the current class — they don’t include the superclass. To traverse the entire inheritance chain, you need to manually loop upward with a while loop.
- (void)printAllIvarsWithSuperclass {
Class cls = [UITextField class];
while (cls && cls != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"class: %@, ivar: %s, type: %s", NSStringFromClass(cls), name, type);
}
free(ivars);
cls = [cls superclass];
}
}
Important Considerations
Memory Management: class_copyIvarList and class_copyMethodList both return C arrays. You must call free() after using them, otherwise you’ll have memory leaks.
Private API Risks: The Runtime functions discussed here are documented, but that does not make undocumented UIKit members safe to use. Depending on private ivars or selectors can break without notice and may create App Review risk. Do not hide or obfuscate private API usage to evade review. Prefer documented APIs, and limit diagnostic inspection to classes you own whenever possible.
In my own workflow, I use these Runtime traversal functions as a first step when onboarding onto any new codebase. Running class_copyIvarList on the key model classes tells me exactly what data they store — including properties the previous developer forgot to document. Running class_copyMethodList on view controllers reveals private helper methods that were added but never exposed in the header file. This saves hours of “grep through the project” detective work.
One practical pattern I’ve found useful: combine Runtime traversal with a simple logging macro that writes the output to a file. When debugging UIKit internals on a new iOS version, I run the traversal on UITextField, UIButton, and UIScrollView, then compare the output across iOS versions. This has helped me catch deprecated private properties before they caused crashes — the property disappeared from the ivar list, which meant the code that relied on it needed updating.
Summary
class_copyIvarList traverses all member variables of a class (including private ones). class_copyMethodList traverses all instance methods of a class (including undeclared ones). The former, combined with KVC, allows modification of internal properties of system components. The latter is mainly used for debugging and runtime method swizzling. Neither of these APIs is forbidden in the App Store review process. But the private properties and methods they discover — especially direct calls to private methods — require careful risk assessment.
A Real Debugging Scenario: Inventorying a Legacy Session Object
I use runtime inspection when I inherit an Objective-C module whose documentation no longer matches the code. One recent example was a session object that behaved differently in staging and production. Before changing it, I wanted a compact inventory of the state and helper methods actually compiled into that class. That is a good fit for class_copyIvarList and class_copyMethodList: the diagnostic tool observes a class my team owns, prints metadata, and leaves the object untouched.
The sample project defines DemoSession with an environment, a creation date, and an access token. It prints the business-facing summary first, then lists the ivar names, type encodings, and methods declared directly by the class. The output also exposes a useful implementation detail: ARC may add .cxx_destruct to release retained Objective-C objects. Seeing that method is normal; it is not an undocumented business method.
A Safer Ivar Inventory
Runtime metadata can reveal names that suggest sensitive data. That does not mean a diagnostic tool should read or print the corresponding values. In the runnable example, names containing words such as “token,” “password,” “secret,” or “key” are marked as redacted. The code deliberately avoids object_getIvar, KVC reads, and KVC writes.
static void PrintIvarInventory(Class cls) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
const char *name = ivar_getName(ivars[i]);
const char *type = ivar_getTypeEncoding(ivars[i]);
printf("%s : %s\n", name, type);
}
free(ivars);
}
The returned array belongs to the caller, so free is required even when the count is zero. Also remember that this function reports ivars declared by the supplied class. If a tool needs the full hierarchy, it must visit each superclass explicitly and stop at an intentional boundary such as NSObject.
Public Runtime API Is Not Permission to Use Private Apple API
class_copyIvarList and class_copyMethodList are documented Objective-C Runtime functions. However, discovering an undocumented UIKit member does not make that member safe to depend on. A private selector or ivar can change without notice, and attempting to hide its use from review is not a sound engineering strategy. For production apps, prefer documented APIs, keep diagnostics focused on classes you own, and treat App Review as a separate question from whether the compiler accepts the code.
Download and Run the Complete Project
The complete macOS command-line project is available in the dedicated GitHub repository: objc-runtime-class-inventory-demo. It requires macOS and the Xcode Command Line Tools.
git clone https://github.com/2252408699/objc-runtime-class-inventory-demo.git
cd objc-runtime-class-inventory-demo
make run
A successful run prints the normal session summary, three declared ivars, four compiled methods on the current toolchain, and a final confirmation that no values were read or modified. Method counts can vary with compiler-generated methods, so the important result is the inventory itself rather than a hard-coded number.
