Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

23. Script Bridging Classes & Hierarchy
Written by Derek Selander

You’ve learned the essentials of working with LLDB’s Python module, as well as how to correct any errors using Python’s pdb debugging module.

In addition, you’ve explored expression’s --debug option to manually pause and explore JIT code that’s being executed in-process. Now you’ll explore the main players within the lldb Python module for a good overview of the essential classes.

You’ll be building a more complex LLDB Python script as you learn about these classes. You’ll create a regex breakpoint that only stops after the scope in which the breakpoint hit has finished executing. This is useful when exploring initialization and accessor-type methods, and you want to examine the object that’s being returned after the function executes.

In this chapter, you’ll learn how to create the functionality behind this script while learning about the major classes within the LLDB module. You’ll continue on with this script in the next chapter by exploring how to add optional arguments to tweak the script based on your debugging needs.

The essential classes

Within the lldb module, there are several important classes:

  • lldb.SBDebugger: The “bottleneck” class you’ll use to access instances of other classes inside your custom debugging script.

    There will always be one reference to an instance of this class passed in as a function parameter to your script. This class is responsible for handling input commands into LLDB, and can control where and how it displays the output.

  • lldb.SBTarget: Responsible for the executable being debugged in memory, the debug files, and the physical file for the executable resident on disk.

    In a typical debugging session, you’ll use the instance of SBDebugger to get the selected SBTarget. From there, you’ll be able to access the majority of other classes through SBTarget.

  • lldb.SBProcess: Handles memory access (reading/writing) as well as the multiple threads within the process.

  • lldb.SBThread: Manages the stack frames (SBFrames) within that particular thread, and also manages control logic for stepping.

  • lldb.SBFrame: Manages local variables (given through debugging information) as well as any registers frozen at that particular frame.

  • lldb.SBModule: Represents a particular executable. You’ve learned about modules when exploring dynamic libraries; a module can include the main executable or any dynamically loaded code (like the Foundation framework).

    You can obtain a complete list of the modules loaded into your executable using the image list command.

  • lldb.SBFunction: This represents a generic function — the code — that is loaded into memory. This class has a one-to-one relationship with the SBFrame class.

Got it? No? Don’t worry about it! Once you see how these classes interact with each other, you’ll have a better understanding of their place inside your program.

This diagram is a simplified version of how the major LLDB Python classes interact with each other.

If there’s no direct path from one class to another, you can still get to a class by accessing other variables, not shown in the diagram, that point to an instance (or all instances) of a class (many of which are not shown in the diagram).

That being said, the entry-point into the majority of these objects will be through an instance of SBDebugger, passed in as an instance variable called debugger in your scripts. From there, you’ll likely go after the SBTarget through GetSelectedTarget() to access all the other instances.

Exploring the lldb module through… LLDB

Since you’ll be incrementally building a reasonably complex script over the next two chapters, you’ll need a way to conveniently reload your LLDB script without having to stop, rerun and attach to a process. You’ll create an alias for reloading the ~/.lldbinit script while running LLDB.

Append the following to your ~/.lldbinit file:

command alias reload_script command source ~/.lldbinit

This adds a command called reload_script which reloads the ~/.lldbinit file. Now whenever you save your work, you can simply reload the updated contents without having to restart LLDB and the process it’s attached to.

In addition, this is a useful command to ensure everything inside your ~/.lldbinit file is still valid. Typically, errors in your ~/.lldbinit will go unnoticed since LLDB doesn’t have access to your stderr when it’s starting up. However, reloading while LLDB is alive and active will dump any syntax errors in your scripts right to the LLDB console.

While you’re building out this new script, you’ll create a one-time-use burner project to explore these LLDB Python APIs. To mix things up, you’ll create a tvOS project this time.

Open Xcode. Select File\New\Project… . Choose tvOS\Single View Application. Call this new project Meh (because I am out of creative names to use!). Make sure the language is set to Swift. Then save the project wherever you want.

Once the project has been created, open ViewController.swift and add a GUI breakpoint to the beginning of viewDidLoad().

Build, run and wait for the breakpoint to be triggered. Jump over to the LLDB console.

Next, type the following into LLDB:

(lldb) script lldb.debugger

You’ll get output similar to the following:

<lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x113f2f990> >

LLDB has a few easily accessible global variables that map to some of the classes described above:

  • lldb.SBDebugger -> lldb.debugger
  • lldb.SBTarget -> lldb.target
  • lldb.SBProcess -> lldb.process
  • lldb.SBThread -> lldb.thread
  • lldb.SBFrame -> lldb.frame

You’ve just explored the global variable lldb.debugger. Now it’s time to explore the other variables.

Type the following into LLDB:

(lldb) script lldb.target

You’ll get output similar to the following:

<lldb.SBTarget; proxy of <Swig Object of type 'lldb::SBTarget *' at 0x1142daae0> >

This probably doesn’t mean much to you at the moment because it’s only displaying the instance of the class, and not the context of what it does, nor what it represents.

This is why the print command might be more useful when you’re starting to explore these classes.

(lldb) script print lldb.target

This will give you some intelligible output to provide some context:

Meh

Using the print command is a useful trick when you want to get a summary of an instance, just as calling po on an object gives you an NSObject’s description method in Objective-C. If you didn’t use the print command, you’d have to hone in on properties and attributes of SBTarget to figure out the name of the target.

Note: It’s fine that you’re playing with global Python variables in one-line scripts. However, it’s important you don’t use these global variables in your actual Python scripts since you can modify the state (i.e step out of a function), and these global variables will not update until your script has finished.

The correct way to reference these instances is to start from SBDebugger, which is passed into your script function, and drill down to the appropriate variable from there.

Go through the remainder of the major global variables and print them out. Start with the following:

(lldb) script print lldb.process

You’ll get the following:

SBProcess: pid = 47294, state = stopped, threads = 7, executable = Meh

This printed out the process being run. As always, your data might differ (pid, state, thread etc…).

Next, type the following into LLDB:

(lldb) script print lldb.thread

This time you’ll get something like this:

thread #1: tid = 0x13a921, 0x000000010fc69ab0 Meh`ViewController.viewDidLoad(self=0x00007fa8c5b015f0) -> () at ViewController.swift:13, queue = ’com.apple.main-thread’, stop reason = breakpoint 1.1

This has printed out the thread that triggered the breakpoint.

Next, try the frame variable:

(lldb) script print lldb.frame

And finally, this one results in:

frame #0: 0x000000010fc69ab0 Meh`ViewController.viewDidLoad(self=0x00007fa8c5b015f0) -> () at ViewController.swift:13

This will get you the specific frame where the debugger is paused. You could, of course, access other frames in other threads. These global variables are merely convenience getters for you. I would strongly recommend using these global LLDB variables when you’re playing with and learning about these classes.

Check out http://lldb.llvm.org/python_reference/index.html to learn about which methods these classes implement.

Alternatively, you can use Python’s help function to get the docstrings for a particular class.

For example, if you were in the Xcode debugging console, and you wanted info on the active SBTarget, you could do this:

(lldb) script help(lldb.target)

Alternatively, you could go after the actual class instead of the global variable:

(lldb) script help(lldb.SBTarget)

Don’t be afraid to ask for help from the help function. I use it all the time when I’m figuring out my plan of attack through the lldb module.

Learning & finding documentation on script bridging classes

Learning this stuff isn’t easy. You’re faced with the learning curve of the LLDB Python module, as well as learning Python along the way.

The best way to go about learning these foreign APIs is to start in easy, small steps. This means attaching to a process and using the script command to explore a class or API. Once you’ve mastered how to use a certain API, it’s fair game to throw it into a custom Python script.

For example, if I stumbled across the SBTarget class and saw the global variable, lldb.target, I would jump to the URL https://lldb.llvm.org/python_reference/lldb.SBTarget-class.html and use the LLDB script command while exploring the online documentation.

Easy reading

I frequently find myself scouring the class documentation to see what the different classes can do for me with their APIs. However, doing that in the LLDB Terminal makes my eyes water. I typically jump to the online documentation because I am a sucker for basic Cascading Style Sheet(s) with more colors than just the background color and text color.

In fact, I do this so much, I often use this LLDB command to directly bring up any class I want to explore:

command regex gdocumentation ’s/(.+)/script import os; os.system("open https:" + unichr(47) + unichr(47) + "lldb.llvm.org" + unichr(47) + "python_reference" + unichr(47) + "lldb.%1-class.html")/’

Stick this command in your ~/.lldbinit file. Make sure the above command is only on one line or else this will not work.

This command is called gdocumentation; it takes a case-sensitive query and opens up the class of interest in your web browser. For example, if I installed this command into my ~/.lldbinit file, and I was attached to a process and wanted to explore the online help documentation for SBTarget, I would type the following into LLDB:

(lldb) gdocumentation SBTarget

This will direct my web browser to the online documentation of SBTarget. Neat!

Documentation for the more serious

If you’re one of those developers who really, really needs to master LLDB’s Python module, or if you have plans to build a commercial product which interacts with LLDB, you’ll need to take a more serious approach for digging through the lldb module APIs and documentation.

Since there’s no search functionality available on http://lldb.llvm.org/python_reference/ (at the time of writing), you need a way to easily search all the classes for a particular query. A drastic but excellent suggestion is to copy the entire http://lldb.llvm.org/python_reference/ site for offline storage using a tool like http://www.httrack.com/. From there, you can search using Terminal commands.

For example, if I scraped the entire site into ~/websites/lldb on my computer and I wanted to search for all classes that had an API that pertained to SBProcess, I would type the following in Terminal:

mdfind SBProcess -onlyin ~/websites/lldb

It’s not a bad idea to also go after the LLDB mailing lists found here http://lists.llvm.org/pipermail/lldb-dev/ and grab that website for offline use. There’s are a ton of useful hints and explanations given by the authors of LLDB which are buried in the list’s archives.

One final way to search for content is to use an often overlooked feature of Google to filter queries to a particular website using the site: keyword.

For example, if I wanted to search for all occurrences of SBTarget in LLDB’s mailing archives, I could use the following query with Google:

SBTarget site:http://lists.llvm.org/pipermail/lldb-dev/

Fortunately, the next couple of chapters will guide you through most of the important classes, so the above suggestions are only meant for the crazy ones out there.

Creating the BreakAfterRegex command

It’s time to create the command you were promised you’d build at the beginning of this chapter!

How would you design a command to stop immediately after a function, print out the return value, then continue? Take a bit of happy thinking time for yourself, and try to figure out how you’d go about creating this script.

I’m serious — stop reading until you’ve given this an honest attempt. I’ll wait.

Good. What did you come up with?

When writing these types of scripts, it’s always good practice to envision what you want to achieve, and work your way back from there.

You’ll name your command script BreakAfterRegex.py. The steps the command needs to take are as follows:

  • First, use LLDB to create a regex breakpoint.
  • Next, add a breakpoint action to step-out of execution (from Chapter 6, “Thread, Frame & Stepping Around”) until the current frame has finished executing.
  • Finally, you’ll use your knowledge of registers from Section II to print out the correct register that holds the return value.

Using your favorite text editor, create BreakAfterRegex.py in your ~/lldb directory.

Once the file is created, open it and add the following:

import lldb

def __lldb_init_module(debugger, internal_dict):
  debugger.HandleCommand('command script add -f BreakAfterRegex.breakAfterRegex bar')

def breakAfterRegex(debugger, command, result, internal_dict):
  print ("yay. basic script setup with input: {}".format(command))

You should know what this is doing by now — but in case you forgot, __lldb_init_module is a callback function called by LLDB after your script has finished loading into the Python address space.

From there, it references a SBDebugger instance passed in as debugger to execute the following line of code:

command script add -f BreakAfterRegex.breakAfterRegex bar

This will add a command named bar which is implemented by breakAfterRegex within the module BreakAfterRegex (named after the file, naturally). If you gave a silly command like wootwoot instead of bar, your LLDB command would be named that instead.

Open your ~/.lldbinit file and append the following line:

command script import ~/lldb/BreakAfterRegex.py

Save the file. Open Xcode, which should still be paused on viewDidLoad(). In the LLDB console, reload the script using your newly created convenience command:

(lldb) reload_script

You’ll get a variable amount of output, as LLDB will display all the scripts it’s loading. This will reload the contents in your lldbinit file and make the bar command functional.

Let’s try out the bar command. In LLDB, type the following:

(lldb) bar UIViewController test -a -b

The output in your new LLDB script will echo back the parameters you’ve supplied to it.

You’ve got the basic skeleton up and working. It’s time to write the code to create a breakpoint based upon your input. You’ll start with creating input designed solely for handling the regular expression.

Head back to BreakAfterRegex.py and find def breakAfterRegex(debugger, command, result, internal_dict):.

Remove the print statement and replace it with the following logic:

def breakAfterRegex(debugger, command, result, internal_dict):
  # 1
  target = debugger.GetSelectedTarget()
  breakpoint = target.BreakpointCreateByRegex(command)

  # 2
  if not breakpoint.IsValid() or breakpoint.num_locations == 0:
    result.AppendWarning(
      "Breakpoint isn't valid or hasn't found any hits")
  else:
    result.AppendMessage("{}".format(breakpoint))

  # 3
  breakpoint.SetScriptCallbackFunction(
    "BreakAfterRegex.breakpointHandler")

Here’s what you’re doing:

  1. Create a breakpoint using the regex input from the supplied parameter. The breakpoint object will be of type SBBreakpoint.

  2. If breakpoint creation is unsuccessful, the script will warn you it couldn’t find anything to break on. If successful, the breakpoint object is printed out.

  3. Finally, the breakpoint is set up so the function breakpointHandler is called whenever the breakpoint hits.

What’s an SBBreakpoint? Well, you can look it up through LLDB!

(lldb) script help(lldb.SBBreakpoint)

If perusing the output in the LLDB console makes your eyes water, a more convenient way to view the documentation can be found here:

https://lldb.llvm.org/python_reference/lldb.SBBreakpoint-class.html.

If you installed the gdocumentation command mentioned earlier, you can simply type the following instead:

(lldb) gdocumentation SBBreakpoint

Grabbing the first line of the help documentation indicates an SBBreakpoint class represents a logical breakpoint and its associated settings.

OK — back on the main road after that little sightseeing trip. Where were we? Oh right — you haven’t created the handler function that will be called when the breakpoint is hit. You’ll do that now.

Right below breakAfterRegex, add the following function:

def breakpointHandler(frame, bp_loc, dict):
  function_name = frame.GetFunctionName()
  print("stopped in: {}".format(function_name))
  return True

This function is called whenever any of the breakpoints you created using your new command are hit, and will then print out the function name. Notice the return of True at the end of the function. Returning True will result in your program stopping execution. Returning False, or even omitting a return statement will result in the program continuing to run after this method executes.

This is a subtle but important point. When creating callback functions for breakpoints (i.e. the breakpointHandler function you just created), you have a different method signature to implement. This consists of a SBFrame, SBBreakpointLocation, and a Python dictionary.

The SBFrame represents the frame you’ve stopped in. SBBreakpointLocation is an instance of one of your breakpoints found in SBBreakpoint. This makes sense, since you could have many hits for one breakpoint, especially if you try to break on a frequently implemented function, such as main, or if you use a well-matched regular expression.

Here’s another diagram that showcases the simplified interaction of classes when you’ve stopped on a particular function:

As you (might have?) noticed, SBFrame, and SBBreakpointLocation are your lifelines to the majority of important lldb classes while in your breakpoint callback function. Using the previous diagram, you can get to all the major class instances through SBFrame or through SBFrame’s reference to SBModule.

Remember, you should never use lldb.frame or other global variables inside your scripts since they could hold a stale state while being executed in a script, so you must traverse the variables starting with the frame, or bc_loc to get to the instance of the class you want.

If you accidentally make a typo, or don’t understand some code, simply insert a breakpoint in the script using the Python pdb module and work your way back from there. You learned about the pdb module in Chapter 22, “Debugging Script Bridging”.

This script is starting to get complicated — looks like a good time to reload and test it out. Open the Xcode console window and reload your script:

(lldb) reload_script

Go through the motions of executing some commands again to test it out:

(lldb) bar somereallylongmethodthatapplehopefullydidntwritesomewhere

You’ll get output similar to the following:

warning: Breakpoint isn't valid or hasn't found any hits

Ok, good. Time to try out an actual breakpoint. Let’s go after a rather frequently executed method.

In the LLDB console type the following:

(lldb) bar NSObject.init\]

You’ll see something similar to the following:

SBBreakpoint: id = 3, regex = 'NSObject.init\]', locations = 2

Continue execution and use the Simulator remote to click around the tvOS Simulator to trigger the breakpoint. If you’re having trouble tripping the breakpoint, one surefire way is to navigate to the simulator’s home screen. From the Simulator, Hardware\Home (or more easily, ⌘ + Shift + H).

Cool. You’ve successfully added a command to create a regex breakpoint! That’s pretty darn neat-o.

Right now, you’ve stopped on one of NSObject’s init methods, which could be a class or an instance method. This is very likely a subclass of NSObject. You’ll manually replicate the actions you’re about to implement in the Python script using LLDB.

Using the LLDB console, finish executing this method:

(lldb) finish

Remember your register calling conventions? Since you’re working on the tvOS Simulator and this architecture is x64, you’ll want to use the RAX register. Print out the return value of NSObject’s init in LLDB.

(lldb) po $rax

Depending on where and how you were playing with the Simulator, you’ll see a different object. I received the following output:

<_CFXNotificationNameWildcardObjectRegistration: 0x61000006e8c0>

If curiosity gets the better of you, feel free to explore the properties and methods within the class you just stumbled across using the strategies discussed in Chapter 17, “Exploring and Method Swizzling Objective-C Frameworks”.

Stepping out and printing is the exact logic you’ll implement now in your custom script callback function.

Open BreakAfterRegex.py and revisit the breakpointHandler function. Modify it to look like the following:

def breakpointHandler(frame, bp_loc, dict):
  # 1
  '''The function called when the regular 
  expression breakpoint gets triggered
  '''

  # 2
  thread = frame.GetThread()
  process = thread.GetProcess()
  debugger = process.GetTarget().GetDebugger()

  # 3
  function_name = frame.GetFunctionName()

  # 4
  debugger.SetAsync(False)

  # 5
  thread.StepOut()

  # 6
  output = evaluateReturnedObject(debugger,
                                  thread, 
                                  function_name)
  if output is not None:
    print(output)

  return False

B-B-B-B-B-Breakdown time!

  1. Yep, if you’re building a full-on Python command script, you’ve got to add some docstrings. You’ll thank yourself later. Trust me.

  2. You’re climbing the hierarchical reference chain to grab the instance of SBDebugger and SBThread. Your starting point is through SBFrame.

  3. This grabs the name of the parent function. Since you’re about to step out of this current SBFrame, it’s about to get invalidated, so grab any stack references you can before the stepping-out occurs.

  4. SetAsync is an interesting function to use when tampering with control flow while scripting in a program. The debugger will run asynchronously while executing the program, so you need to tell it to synchronously wait until stepOut completes its execution before handing control back to the Python script.

    A good programmer will clean up the state to the async’s previous value, but that becomes a little complicated, as you could run into threading issues when this callback function triggers if multiple breakpoints were to hit this callback function. This is not a noticeable setting change when you’re debugging, so it’s fine to leave it off.

  5. You then step out of the method. After this line executes, you’ll no longer be in the frame you previously stopped in.

  6. You’re calling a soon-to-be implemented method evaluateReturnedObject that takes the appropriate information and generates an output message. This message will contain the frame you’ve stopped in, the return object, and the frame the breakpoint stepped out to.

You’re all done with that Python function! Now you need to implement evaluateReturnedObject. Add it below the previous function you just wrote:

def evaluateReturnedObject(debugger, thread, function_name):
  '''Grabs the reference from the return register
  and returns a string from the evaluated value.
  TODO ObjC only
  '''

  # 1
  res = lldb.SBCommandReturnObject()
  
  # 2
  interpreter = debugger.GetCommandInterpreter()
  target = debugger.GetSelectedTarget()
  frame = thread.GetSelectedFrame()
  parent_function_name = frame.GetFunctionName()

  # 3
  expression = 'expression -lobjc -O -- {}'.format(
      getRegisterString(target))


  # 4
  interpreter.HandleCommand(expression, res)

  # 5
  if res.HasResult():
    # 6
    output = '{}\nbreakpoint: '\
      '{}\nobject: {}\nstopped: {}'.format(
        '*' * 80,
        function_name,
        res.GetOutput().replace('\n', ''),
        parent_function_name)
    return output
  else:
    # 7
    return None

Here’s what that does:

  1. You first instantiate a new SBCommandReturnObject. You’ve seen this class already in your primary functions as the result parameter. However, you’re creating your own here because you’ll use this instance to evaluate and modify an expression. A typical po "something" will produce output, including two newlines, straight to the console. You need to grab this output before it goes to the console and remove those newlines… because you’re fancy like that. In Chapter 25, “Script Bridging with SBValue & Language Contexts”, you’ll explore a cleaner alternative to evaluating code and obtaining output, but for now you’ll make do with your existing knowledge of the SBCommandReturnObject class.

  2. You grab a few variables for use later on.

  3. Here you create the expression to be executed that prints out the return value. The getRegisterString is yet another unimplemented function you’ll implement in just a moment — I promise this will be the last time I do that to you! This function will return the syntax needed to access the register which holds the return value.

    This is required because you can’t know if this script is running on a watchOS, iOS, tvOS, or macOS device, so you’ll need to augment the register name depending upon the architecture. Remember, you also need to use the Objective-C context, since Swift hides the registers from you!

  4. Finally, you execute the expression through the debugger’s command interpreter, SBCommandInterpreter. This class interprets your commands but allows you to control where the output goes, instead of immediately piping it to stderr or stdout.

  5. Once HandleCommand has executed, the output of the expression should now reside in the SBCommandReturnObject instance. However, it’s good practice to ensure the return object actually has any output to give to you.

  6. If everything worked correctly, you format the old, stepped-out function along with the object and currently stopped function into a string and return that.

  7. However, if there was no input to print from the SBCommandReturnObject, you return None.

One more method, and then you’re (sort of) done! Implement getRegisterString at the bottom of your Python script:

def getRegisterString(target):
  triple_name = target.GetTriple()
  if "x86_64" in triple_name:
    return "$rax"
  elif "i386" in triple_name: 
    return "$eax"
  elif "arm64" in triple_name:
    return "$x0"
  elif "arm" in triple_name:
    return "$r0"
  raise Exception('Unknown hardware. Womp womp')

You’re using the SBTarget instance to call GetTriple, which returns a description of the hardware the executable is designed to run on. Next, you determine which syntax you need to access the register responsible for the return value based on your architecture. If it’s an unknown architecture, then raise an exception.

You’ve done it! Save your work, jump back to Xcode and reload the script with your trusty reload_script command in the LLDB command line.

Next, before you get started with the full-blown command, remove all previous breakpoints like so:

(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n] Y
All breakpoints removed. (1 breakpoint)

It’s time to take this beauty for a spin!

Type the following into LLDB:

(lldb) bar NSObject.init\]

This time your script will execute your completed command’s script when it hits the breakpoint.

Do whatever you need to do through the tvOS Simulator to trigger the init breakpoint; closing the application will work (⌘ + Shift + H), as will bringing up the Apple TV Remote (found in the Hardware menu) and tapping on the remote.

Once hit, you’ll get some beautiful output which showcases the method you’ve stopped on (in this case -[NSObject init]), the object that is being created, and the calling method as well.

Since you’ve created a breakpoint on a frequently-called method, you’ll soon hit the same breakpoint again.

This is a fun tool to have at your disposal. You could, for instance, create a well-crafted regex breakpoint to trigger each time an NSURL is created within any application… owned by you or not. For example, you could try:

(lldb) bar NSURL(\(\w+\))?\ init

The “weird” syntax is needed because a lot of the initialization methods for NSURL are in categories. Alternatively, you could use this script on a problematic getter method of a Core Data object that is returning unusual values.

Where to go from here?

You’ve begun your quest to create Python LLDB scripts of real-world complexity. In the next chapter, you’ll take this script even further and add some cool options to customize this script.

But for now, have fun and play around with this bar script! Attach LLDB to some applications running in the simulator and play around with the command. Try the already mentioned NSURL initialization (or NSURLRequest initialization) breakpoints.

Once you get bored of that, see what objects are using Core Data by inspecting the return value of -[NSManagedObject valueForKey:] or check out all the items that are being created from a nib or storyboard by breaking on an initWithCoder: method.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.