Share:
Developer tools are an essential part of an engineer's toolkit, allowing you to identify and fix bugs in your code quickly and efficiently. Tools you use daily come in many shapes and sizes, from simple print statements to complex debugging frameworks and distributed systems. Still, they all share the same goal: to make the debugging process more accessible and effective.
While it's easy to take these tools for granted, engineers need to understand how they work. By understanding the underlying mechanisms of a tool, you can use it more effectively, diagnose issues more accurately, and even build custom debugging solutions or integrate them into your workflows.
In this blog post, we'll take a deep dive into the inner workings of AppSpector’s iOS SDK, exploring the key concepts and technologies that underpin its operation. We'll discuss why it's essential to know how it's built and how this knowledge can help you become a better and more productive developer.
At a high-level AppSpector is all about data flow. Each part of the system works to deliver data from the host app to the dashboard and back. On the host app side, the only AppSpector component is the SDK - a bundle containing code that gathers and transmits data.
The core concept of the AppSpector data flow process is a session. A session is a period of time the app runs with the SDK enabled. If you configure the SDK and start it with your app, it immediately begins to gather info about the host app. At the same time, it tries to establish a connection to the backend to transfer collected data.
If it succeeds, it opens a web socket for real-time data transfer. This socket is used for incoming and outgoing traffic between SDK and the backend. Collected data is sent continuously to this socket and passes through the backend service. It then comes to the web dashboard, where you can see it in real-time.
The session ends once the app is finished running. After restart, a new session starts, and you can see it as a separate entry in your app sessions list.
Connection and data collection are designed as independent processes inside the SDK. Networks are fragile, so the SDK is prepared for the event of a connection loss and continues to save data locally so it can send it later when the connection is restored. This is why you can see session instances even if your app was offline for some time.
Sessions have a specially crafted and unique identifier the backend uses to decide if two or more connections belong to the same session. If so, it joins data received from these connections in one session on the dashboard. That's why when your app returns online, you see data inside the same session you had before the disconnect.
SDK uses SQLite DB to store data gathered while the app is offline. SQLite is a fast and reliable DB with a little CPU/memory footprint, so we used it for the SDK internal data storage.
As we mentioned above, the connection established by the SDK is two-directional. The SDK sends the data collected, and the backend sends back requests issued by a user via the dashboard. This can be SQL queries you type in SQLite monitor or refresh command in screenshot monitor.
Any user actions on the dashboard that require data to be transferred to the host app side are requests sent to the session.
Security is becoming increasingly important in today's world, where data breaches and cyber-attacks are commonplace. The need for end-to-end encryption cannot be overstated for developer tools that handle end-user data.
End-to-end encryption ensures that data remains private and secure, even if it is intercepted during transmission or storage. This is because the data is encrypted at the source and can only be decrypted by the intended recipient, making it almost impossible for any third party to access the information.
As such, end-to-end encryption is an essential security feature that should be incorporated into all developer tools that handle sensitive end-user data. Of course, we planned e2e encryption support from the beginning. Now you can opt-in to get all traffic between your computer and host app encrypted so that only you can decrypt it.
AppSpector has a separate SDK with an encryption module to establish encrypted sessions. Also, to be sure data is safe at the other end, we allow access to encrypted sessions only from the desktop app, not the web dashboard.
Let's return to data flow as a core concept of the AppSpector. A crucial part of the SDK is data collection. Modules that gather data on the host app are called monitors. Each works with a specific context, like networking or app logs.
Their implementations vary, but most commonly, they contain a code that uses ObjC runtime to inject itself into the different parts of the iOS SDK and gather data. For example, the network monitor injects hooks into more than forty methods inside the Foundation framework to get correct info about the network requests lifecycle.
Another example is SQLite monitor, which hooks symbols inside the SQLite library like sqlite3_open, sqlite3_exec, and others to get queries the host app runs. SQLite is not written in ObjC, which runtime was a rescue in network monitor, so we can't use method swizzling here. SQLite monitor instead uses dyld image loading hooks to rebind those symbols.
Interesting that some monitors, while tracking data, also get data generated by themselves or other parts of AppSpector SDK. We, of course, ignore this data, and the monitors don't send it to the backend. That's why you can't see the SDK internal DB in the SQLite monitor.
Sometimes during development, we run into issues and need a tool to get a picture of what's going on in our networking or storage. What tool do we use? A unique AppSpector SDK build that injects code into itself. It’s very handy, but you need to be attentive not to confuse a code you debug with the same code you use to debug it. Building developer tools can be so much fun!
You might think that when the code is doing several different tricks at runtime, it would influence the app's behavior, and you’d be right. That's why the code inside AppSpector monitors is written in such a way that it does not interfere with the host app code and even respects the runtime hacks, like swizzled methods the host app can use.
Of course, despite this effort, it still influences the performance of the whole app. That's why we don't recommend using AppSpector in release builds for your users. No matter how you try - debugging tools are too heavy not to be noticed in the production environment.
The world of software development and the SDKs offered by Apple are constantly evolving, with new features being introduced every year. Our focus is on keeping up with these innovations by enhancing existing monitors and introducing new ones.
Another critical consideration is platform support. As we have all witnessed, Apple is committed to reducing the effort iOS developers require to build and run their apps on macOS. At AppSpector, we share this objective and have already developed an internal alpha build of the macOS SDK. With the best debugging tool at their disposal, desktop developers will soon be able to say goodbye to their struggles.
Share:
AppSpector is remote debugging and introspection tool for iOS and Android applications. With AppSpector you can debug your app running in the same room or on another continent. You can measure app performance, view CoreData and SQLite content, logs, network requests and many more in realtime. Just like you we have been struggling for years trying to find stupid mistakes and dreaming of a better native tools, finally we decided to build them. This is the instrument that you’ve been looking for.