Open-Source LLM Driven Mobile App Development: From Requirements Breakdown to a Runnable iOS App in 6 Hours

    This article details a practical workflow for leveraging open-source large language models (LLMs) to develop an iOS messaging relay application. It covers the methodology of splitting requirements into machine-readable documents, executing a two-phase "plan-then-code" approach, managing token consumption for long-duration tasks, and validating the final build. Engineering teams exploring AI-assisted development will find actionable strategies to transform LLM generation capabilities into continuous delivery.

    Why “Plan First, Code Later” Outperforms Direct Code Generation

    When developers first attempt to build projects using LLMs, they often issue broad prompts like “write an iOS app for me.” The results are typically subpar. Although the model generates a large volume of code, it frequently suffers from structural chaos, interface mismatches, and logical conflicts. The reason is simple: the LLM lacks an overarching understanding of the project’s context and goals.

    We adopted a different strategy: have the model generate a comprehensive, executable project planning document first, and then generate code module by module based on that document. This approach offers three distinct benefits:

    • Front-loaded Requirement Constraints: By clearly defining the product form, technical boundaries, and platform limitations in the document, we prevent the model from “freestyling” during code generation.
    • Task Decomposability: The planning document naturally forms a task tree, making it easy to break down a large project into smaller, independently verifiable steps.
    • Context Reusability: The planning document itself serves as the optimal prompt. When asking the model to write code later, you only need to include the relevant section, eliminating the need to repeatedly describe the background.

    For our project—a message relay tool designed to handle the import, storage, and forwarding of chat records to a designated server—we divided the core functionality into five modules: Data Collection Layer, Local Storage Layer, Network Transmission Layer, User Configuration Interface, and Background Task Scheduler. Each module had clearly defined inputs, outputs, and boundary constraints.

    Writing a “Machine-Readable” Requirements Document

    The quality of the planning document directly determines the accuracy of the LLM’s subsequent code generation. A qualified requirements document should include at least the following:

    1. Feature List and Priorities: Clearly distinguish between core paths that must be implemented and enhancement features that can be delayed.
    2. Data Flow Description: Detail the complete flow of data from ingestion to disk storage, specifying triggers and exception handling for each step.
    3. Platform Limitations: For iOS, this includes sandbox mechanisms, background execution time limits, and privacy permission declarations. If these details are not specified upfront, the model will generate code that fails to compile.
    4. Interface Contracts: Define how modules interact, either through simple function signatures or protocol descriptions.

    An excerpt from our actual document looked like this (illustrative structure):

    1. Module: Local Storage Layer
    2. - Responsibility: Write collected chat records to a SQLite database grouped by conversation
    3. - Input: Message object (including conversation ID, sender, timestamp, content)
    4. - Output: Write success/failure status
    5. - Constraints: Database must be encrypted; file located in the Application Support directory
    6. - Dependencies: Requires importing a SQLite library; relies on the system security framework for key management

    When document details are sufficiently clear, the model’s code “hallucinations” drop significantly. In our experiments, code generated with a complete document achieved a first-pass compilation rate of about 60%, compared to less than 20% when issuing direct commands.

    Two-Phase Execution: A Closed Loop from Plan to Implementation

    The overall workflow is divided into two phases. Phase One focuses on “writing the plan,” and Phase Two focuses on “executing the plan.” Both phases use the same conversation context but require manual pace control.

    Phase 1: Plan Generation and Manual Review

    In the first conversation round, we only provide the product requirement description and ask the model to output a technical proposal document. No code is requested at this stage. The model will suggest database options, network frameworks, and module divisions. Once the proposal is ready, a manual technical feasibility review is required, focusing on:

    • Compliance with iOS platform rules (e.g., background permissions, App Store review guidelines)
    • The rationality of proposed third-party libraries and the availability of mature alternatives
    • Data encryption and privacy compliance standards
    • Error handling and logging coverage for critical paths

    Once approved, the proposal document is finalized and acts as the “master blueprint” for all subsequent generation tasks. If issues are found, ask the model to supplement or revise specific sections rather than patching things up during the code generation phase.

    Phase 2: Module-by-Module Coding and Real-Time Validation

    To kick off Phase Two, we paste the full planning document into a new chat session and explicitly instruct the model to “implement step-by-step according to the document above, stopping to wait for confirmation after completing each module.” The benefits of this approach include:

    • Independent generation context for each module, reducing the risk of “forgetfulness” in long conversations
    • Early error detection to stop losses promptly, preventing errors from cascading into subsequent code
    • Independent compilation and validation of each module for precise issue localization

    Our execution sequence was as follows:

    1. Data Model Definition (generating Swift structs and database table mappings)
    2. Database Access Layer (wrapping CRUD methods)
    3. Network Upload Service (implementing background tasks and resumable uploads)
    4. User Settings Interface (building forms with SwiftUI)
    5. Foreground Triggers (using system notifications for automated collection)

    Throughout this process, we immediately added the code generated at each step into an Xcode project for compilation. If compilation errors occurred, we fed the error messages back to the model and asked it to self-correct. This “generate-compile-feedback-fix” loop is critical for improving code quality.

    Resource Management Strategies for Long-Duration Tasks

    Continuous 6-hour autonomous development consumes considerable cloud inference resources. In practice, we found that token consumption far exceeded expectations, primarily because the model often attaches extensive explanatory text when generating code, and historical messages in long contexts continuously occupy the input window.

    To address this, we adopted four optimization techniques:

    First, control context length. When generating each module, include only the relevant section from the planning document rather than resending the entire text. For example, when writing the Network Transmission Layer, you only need the “Network Transmission Layer” section and public interface definitions; other sections can be omitted.

    Second, streamline output format. Explicitly state in the prompt, “Output code only, no explanations,” restricting the model’s output to pure code blocks. This reduces unnecessary token consumption by roughly 30%.

    Third, set an appropriate task granularity. A single request should not generate overly large files. Testing showed that having the model generate a standalone file containing 200-400 lines of code per round yielded the best results. Beyond this range, the model tends to suffer from attention loss halfway through, leading to duplicated or omitted code.

    Fourth, leverage project caching and retry mechanisms. When a drop in generation quality is detected for a single round, terminate the current task. Start a new conversation with a summary of the code generated so far, allowing the model to “continue from the breakpoint” rather than repeatedly correcting errors in a single long conversation.

    Validation and Delivery: Compiling is Just the Beginning

    Passing compilation is only the first step; true validation requires running through the complete business chain. We established a lightweight validation scheme:

    • Unit Testing for Core Logic: Write unit tests for critical methods like database CRUD and message parsing to ensure no basic logical errors exist.
    • Simulated Environment Integration: Launch the app in the simulator, manually import test chat record data, and verify the end-to-end “import-store-upload” loop.
    • Boundary Condition Testing: Focus on exception scenarios such as network reconnection, oversized message payloads, and deduplication of repeated imports.

    It is worth noting that LLMs are generally weaker at generating exception-handling code. For issues like network timeouts or database lock conflicts, the model tends to write code that appears correct but actually fails to catch errors. During manual review, it is crucial to verify that every network request has a retry mechanism and every database write is wrapped in a transaction.

    Extending from Local Development to Cloud Collaboration

    Once the single-project validation was complete, we considered a more universal engineering path. Standardizing this “plan-execute-validate” workflow allows integration with cloud development environments and CI/CD pipelines. Specifically:

    • Apply version control to planning documents, syncing them with the code repository
    • Use automated scripts to trigger LLM tasks, such as automatically generating unit tests for changed code upon every commit
    • Feed build logs and error messages back to the model to drive autonomous repairs

    In such practices, orchestrating cloud infrastructure components like Object Storage, Logging Services, and Message Queues becomes critical. Developers must correctly bind the storage calls in the model-generated code with cloud resources, ensuring consistency between local development and cloud deployment. For instance, database file upload backups should be handled uniformly by background tasks, preventing information silos caused by the model acting independently across different modules.

    Key Takeaways and Future Improvements

    This practice validated the feasibility of using LLMs for complex project development, though several areas still require improvement:

    First, automated validation during the planning phase. Currently, the quality of the requirements document still relies on manual experience. In the future, a set of review rules could be designed to allow the model to self-check document completeness and reduce omissions.

    Second, the model’s multi-file collaboration capability. Generating single files per round works best right now, but actual projects often require cross-file references. Enabling the model to accurately remember the interface signatures of other files during code generation is key to improving efficiency.

    Third, recovering and reusing development experience. After the model successfully solves a typical problem in a project, can that experience be “memorized” for subsequent tasks? This involves designing a long-term memory mechanism, which warrants further exploration.

    Regardless, the experience of producing a runnable first version in 6 hours has proven that with sound methodological guidance, open-source LLMs are fully capable of handling independent mobile application development tasks. For engineering teams, the most worthwhile direction moving forward is not waiting for natural model capability growth, but proactively designing engineering workflows optimized for human-machine collaboration, translating AI’s generation capabilities into continuous delivery power.