You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多Target项目代码共享架构咨询:登录页Protocol实现方案探讨

Great question! Building a shared codebase for multiple targets while retaining per-target customization is a common (and smart) goal for scalable iOS projects. I’ve used this exact pattern on several apps, and here’s the most maintainable approach I’ve found:

1. Core Architecture: Protocol + Base Implementation + Target-Specific Conformances

The key is to separate shared contract/behavior from target-specific logic:

  • Use a protocol to define the mandatory methods/properties all login controllers must implement (this is your "contract").
  • Create a base view controller that provides reusable UI, common validation, and default behavior (so you don’t repeat boilerplate across targets).
  • Have each target’s login controller inherit from the base class and conform to the protocol, overriding only what’s unique to that target.

Organize your project into clear modules to keep shared code isolated and easy to reference across targets:

YourProject/
├── Shared/                # Reusable code for all targets
│   ├── Login/
│   │   ├── LoginProtocol.swift       # Defines the login controller contract
│   │   ├── BaseLoginViewController.swift  # Shared UI/behavior
│   │   ├── LoginModels.swift         # Shared data types (e.g., UserInfo, LoginError)
│   │   └── LoginValidators.swift     # Common validation logic
├── TargetA/               # First target-specific code
│   └── Login/
│       └── TargetALoginViewController.swift  # TargetA's custom login flow
└── TargetB/               # Second target-specific code
    └── Login/
        └── TargetBLoginViewController.swift  # TargetB's custom login flow
3. Step-by-Step Implementation Example

Step 1: Define the Protocol

Start with a protocol that outlines what every login controller needs to do. Use extensions to add default implementations for shared logic:

// Shared/Login/LoginProtocol.swift
import UIKit

enum LoginError: LocalizedError {
    case invalidCredentials
    case networkFailure
    
    var errorDescription: String? {
        switch self {
        case .invalidCredentials: return "Please enter a valid username and password."
        case .networkFailure: return "Could not connect to the server. Try again later."
        }
    }
}

struct UserInfo {
    let id: String
    let name: String
}

protocol LoginProtocol: AnyObject {
    // Mandatory UI components
    var usernameTextField: UITextField! { get }
    var passwordTextField: UITextField! { get }
    
    // Mandatory methods (target-specific logic)
    func handleLoginButtonTap()
    func showLoginSuccess(for user: UserInfo)
    
    // Optional method with default implementation
    func validateCredentials() -> Bool
}

extension LoginProtocol {
    // Default validation logic (targets can override this)
    func validateCredentials() -> Bool {
        guard let username = usernameTextField.text, !username.isEmpty else { return false }
        guard let password = passwordTextField.text, password.count >= 6 else { return false }
        return true
    }
    
    // Default error presentation (targets can override)
    func showLoginError(_ error: LoginError) {
        let alert = UIAlertController(title: "Login Failed", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        (self as? UIViewController)?.present(alert, animated: true)
    }
}

Step 2: Build the Base View Controller

This class handles all shared UI setup and boilerplate, so targets only need to add their unique logic:

// Shared/Login/BaseLoginViewController.swift
import UIKit

class BaseLoginViewController: UIViewController, LoginProtocol {
    // MARK: - Protocol Conformance
    var usernameTextField: UITextField!
    var passwordTextField: UITextField!
    
    // MARK: - Shared UI Components
    private let loginButton = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSharedUI()
        setupSharedConstraints()
    }
    
    // MARK: - Shared Setup
    private func setupSharedUI() {
        view.backgroundColor = .systemBackground
        
        usernameTextField = UITextField()
        usernameTextField.placeholder = "Username"
        usernameTextField.borderStyle = .roundedRect
        view.addSubview(usernameTextField)
        
        passwordTextField = UITextField()
        passwordTextField.placeholder = "Password"
        passwordTextField.borderStyle = .roundedRect
        passwordTextField.isSecureTextEntry = true
        view.addSubview(passwordTextField)
        
        loginButton.setTitle("Login", for: .normal)
        loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
        view.addSubview(loginButton)
    }
    
    private func setupSharedConstraints() {
        usernameTextField.translatesAutoresizingMaskIntoConstraints = false
        passwordTextField.translatesAutoresizingMaskIntoConstraints = false
        loginButton.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            usernameTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            usernameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            usernameTextField.widthAnchor.constraint(equalToConstant: 280),
            
            passwordTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            passwordTextField.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 20),
            passwordTextField.widthAnchor.constraint(equalToConstant: 280),
            
            loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            loginButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 30)
        ])
    }
    
    // MARK: - Shared Behavior
    @objc private func loginButtonTapped() {
        guard validateCredentials() else {
            showLoginError(.invalidCredentials)
            return
        }
        handleLoginButtonTap() // Delegates to target-specific implementation
    }
    
    // MARK: - Mandatory Protocol Methods (Must Be Overridden)
    func handleLoginButtonTap() {
        fatalError("Subclasses must implement handleLoginButtonTap()")
    }
    
    func showLoginSuccess(for user: UserInfo) {
        fatalError("Subclasses must implement showLoginSuccess(for:)")
    }
}

Step 3: Implement Target-Specific Logic

For each target, create a subclass of BaseLoginViewController and override only what’s unique:

// TargetA/Login/TargetALoginViewController.swift
import UIKit

class TargetALoginViewController: BaseLoginViewController {
    // TargetA-specific UI component
    private let appleLoginButton = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTargetSpecificUI()
    }
    
    private func setupTargetSpecificUI() {
        appleLoginButton.setTitle("Login with Apple", for: .normal)
        appleLoginButton.addTarget(self, action: #selector(appleLoginTapped), for: .touchUpInside)
        view.addSubview(appleLoginButton)
        
        appleLoginButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            appleLoginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            appleLoginButton.topAnchor.constraint(equalTo: loginButton.bottomAnchor, constant: 20)
        ])
    }
    
    // MARK: - Override Protocol Methods for TargetA Logic
    override func handleLoginButtonTap() {
        // Call TargetA's unique authentication API
        TargetAAuthService.login(username: usernameTextField.text!, password: passwordTextField.text!) { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let user):
                    self?.showLoginSuccess(for: user)
                case .failure(let error):
                    self?.showLoginError(error)
                }
            }
        }
    }
    
    override func showLoginSuccess(for user: UserInfo) {
        let alert = UIAlertController(title: "Welcome, \(user.name)!", message: "Logged in via TargetA", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Go to Home", style: .default) { _ in
            self.navigationController?.pushViewController(TargetAHomeViewController(), animated: true)
        })
        present(alert, animated: true)
    }
    
    // MARK: - Target-Specific Action
    @objc private func appleLoginTapped() {
        // Implement TargetA's Apple Sign-In flow
        TargetAAppleAuthService.startAuthentication { result in
            // Handle result
        }
    }
}
4. Advanced Optimizations
  • Dependency Injection: Pass target-specific services (like auth services) into the base controller via initializers, making shared code more testable and flexible.
  • Modularization: Package the Shared directory into a Swift Package or Framework, so you can easily reuse it across multiple projects or targets.
  • Protocol Extensions: Add more default behavior to LoginProtocol (e.g., showLoadingSpinner()) to reduce repetitive code.
  • Unit Testing: Use protocols to mock dependencies (like auth services) when testing shared logic, ensuring your base code works as expected without relying on target-specific APIs.

内容的提问来源于stack exchange,提问作者Andrea Mario Lufino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:14:03