iOS reverse engineering: in-depth exploration of iOS inter-process communication solutions and introduction to local sockets

In mobile application development, Inter-Process Communication (IPC) is a crucial technology for collaboration and data sharing between different applications. In the iOS ecosystem, processes and threads are basic concepts, and inter-process communication solutions provide powerful support for application function expansion and performance optimization.

1. Processes and threads: core concepts of operating systems

A process refers to an independent program instance running in the operating system. Each process has an independent memory space, data isolation and communication between different processes require specific mechanisms. In iOS, each application runs in an independent process, which ensures isolation and stability between applications.

A thread is an execution unit within a process, and a process can contain multiple threads. Threads share the process's memory space, which allows them to share data more easily. However, multi-threaded programming also brings complex issues such as thread synchronization and race conditions. In iOS, the main thread typically handles UI interactions, while background threads perform time-consuming tasks to keep the user interface responsive.

Let's discuss how the process is created from a code perspective:

1.1 Creation of processes under Linux

Under Linux, you can use system calls in C language to create new processes. Among them, fork() is a commonly used system call, used to create a new process so that the parent process and the child process can run in parallel. Here is a simple sample code that shows how to create a process under Linux using C language:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    
    
    pid_t child_pid;

    // 创建新进程
    child_pid = fork();

    if (child_pid < 0) {
    
    
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (child_pid == 0) {
    
    
        // 子进程执行的代码
        printf("This is the child process. PID: %d\n", getpid());
        // 子进程退出
        exit(0);
    } else {
    
    
        // 父进程执行的代码
        printf("This is the parent process. Child PID: %d\n", child_pid);
        // 等待子进程结束
        wait(NULL);
        printf("Child process has terminated.\n");
    }

    return 0;
}

In this example, fork() will create a new process, and the child process will start executing from where fork() returns. In the child process, we print the PID of the child process and use exit(0) to exit the child process. In the parent process, we print the PID of the child process and then use wait(NULL) to wait for the child process to end. Note that the parent process and the child process are independent in the memory space, but they share resources such as file descriptors.

1.2 Compile and run the above code in iOS

On the iOS platform, you cannot directly use fork() to create a process because iOS applications run in a sandbox environment and have some restrictions on process management and creation. On iOS, process creation and management is controlled by the system, usually initiated by the application's main thread. You can compile and run normally, but trying to call the fork() function will cause the application to crash.

1.3 The underlying principle of fork()

Insert image description here

In order to better understand inter-process communication, let us introduce what changes occur at the bottom of the operating system when fork() is called:

Copying a process: When a process calls fork(), the operating system creates a new process, called a child process. The child process is a copy of the parent process, including all memory, file descriptors and other resources of the parent process.
Copy memory space: The address space of the child process will be the same as the parent process, but the child process will get an independent copy. This is achieved through the "Copy-On-Write" mechanism, which means that the parent and child processes initially share memory, but if any process modifies the memory contents, the operating system will create a new copy of the modified part. .
Assign process ID: The operating system assigns a unique process identifier (PID) to the child process. The PID of the child process is different from the parent process, but other attributes (such as UID, GID, etc.) may remain consistent.
Handling of file descriptors: The child process will inherit the file descriptor of the parent process. File descriptors are handles used to access I/O resources such as files and sockets. Child processes can share the same file or network connection after inheriting the file descriptor.
Return value: In the parent process, the fork() function returns the PID (positive integer) of the child process. In the child process, the fork() function returns 0. If fork() fails, the return value is a negative number, indicating that the creation of the child process failed.
Continue execution: Both the parent process and the child process continue execution from the place after the fork() call. Since the child process is a copy of the parent process, they both start running from the same code location.

Among them, you can pay special attention to the concept of "copying while writing", which is often the focus of interviews at large factories. If you are interested, we will introduce it later, and you can follow this column to get the latest articles.

2. Inter-process communication solution in iOS

In iOS, data sharing and communication between different applications require the use of specific inter-process communication schemes. Here are some common iOS inter-process communication methods:

**URL Scheme:** Applications can register a customized URL Scheme, call up the target application through the URL in other applications, and transfer data. This is usually used for simple application jumps and data sharing.
App Groups: App Groups allow different apps to share the same set of container directories for storing shared data, such as preferences and files.
Keychain Sharing: Keychain is a secure container used to store sensitive data. Applications can share Keychain data under the developer account to achieve cross-application data sharing.
Notification: Apps can use the notification center to send and receive notifications to implement messaging between apps. This is suitable for one-to-many communication scenarios.
Local Socket: iOS processes can also communicate through local sockets. This is a more general method. Let’s introduce it in detail below.

3. Use Local Socket for inter-process communication

Local Socket is an inter-process communication method based on network sockets, suitable for establishing communication connections between different processes on the same device. Here is a brief example code for inter-process communication using Local Socket:

Process 1 - Send data

import Foundation

let serverURL = URL(fileURLWithPath: "/path/to/socket")
let socket = try! Socket.create(family: .unix, type: .stream, proto: .unix)
try! socket.connect(to: serverURL)

let dataToSend = "Hello, Process 2!".data(using: .utf8)!
try! socket.write(from: dataToSend)
socket.close()

Process 2 - receiving data

import Foundation

let serverURL = URL(fileURLWithPath: "/path/to/socket")
let socket = try! Socket.create(family: .unix, type: .stream, proto: .unix)
try! socket.bind(to: serverURL)
try! socket.listen(maxBacklogSize: 1)

let clientSocket = try! socket.acceptClientConnection()

var receivedData = Data()
_ = try! clientSocket.read(into: &receivedData)
let receivedString = String(data: receivedData, encoding: .utf8)
print("Received data: \(receivedString ?? "")")
clientSocket.close()

iOS inter-process communication solutions have different advantages and uses in different scenarios. We should choose appropriate communication methods based on actual needs to achieve data sharing and collaboration between applications and provide users with a better experience.

Guess you like

Origin blog.csdn.net/zh405123507/article/details/132600899