架构¶
面试点¶
图片缓存 阅读时长统计 复杂页面架构 客户端整体架构
架构&框架¶
- 模块化
- 分层
- 解耦
- 降低代码重合度
图片缓存¶
图片通过什么方式读写,过程是怎么样的?
- 以图片的 URL 的单向 hash值作为 key.
内存设计,需要考虑哪些问题?
- 存储 size
- 淘汰策略
淘汰策略
- 队列先进先出的方式淘汰
- LRU 最近最久未使用的
#import <Foundation/Foundation.h>
@interface LRUCache : NSObject
@property (nonatomic, assign) NSInteger maxCacheSize;
- (instancetype)initWithMaxCacheSize:(NSInteger)maxCacheSize;
- (void)setObject:(id)object forKey:(id)key;
- (id)objectForKey:(id)key;
- (void)removeObjectForKey:(id)key;
- (void)removeAllObjects;
@end
@implementation LRUCache {
NSMutableArray *_keys;
NSMutableArray *_values;
NSMutableDictionary *_cache;
NSLock *_lock;
}
- (instancetype)initWithMaxCacheSize:(NSInteger)maxCacheSize {
self = [super init];
if (self) {
_maxCacheSize = maxCacheSize;
_keys = [NSMutableArray array];
_values = [NSMutableArray array];
_cache = [NSMutableDictionary dictionary];
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)setObject:(id)object forKey:(id)key {
[_lock lock];
NSInteger index = [_keys indexOfObject:key];
if (index != NSNotFound) {
[_keys removeObjectAtIndex:index];
[_values removeObjectAtIndex:index];
} else if (_keys.count >= _maxCacheSize) {
id oldestKey = _keys.firstObject;
[_keys removeObjectAtIndex:0];
[_values removeObjectAtIndex:0];
[_cache removeObjectForKey:oldestKey];
}
[_keys addObject:key];
[_values addObject:object];
[_cache setObject:object forKey:key];
[_lock unlock];
}
- (id)objectForKey:(id)key {
[_lock lock];
NSInteger index = [_keys indexOfObject:key];
id object = nil;
if (index != NSNotFound) {
object = _values[index];
[_keys removeObjectAtIndex:index];
[_values removeObjectAtIndex:index];
[_keys addObject:key];
[_values addObject:object];
} else {
object = [_cache objectForKey:key];
}
[_lock unlock];
return object;
}
- (void)removeObjectForKey:(id)key {
[_lock lock];
NSInteger index = [_keys indexOfObject:key];
if (index != NSNotFound) {
[_keys removeObjectAtIndex:index];
[_values removeObjectAtIndex:index];
}
[_cache removeObjectForKey:key];
[_lock unlock];
}
- (void)removeAllObjects {
[_lock lock];
[_keys removeAllObjects];
[_values removeAllObjects];
[_cache removeAllObjects];
[_lock unlock];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LRUCache *cache = [[LRUCache alloc] initWithMaxCacheSize:3];
// 添加缓存项
[cache setObject:@1 forKey:@"one"];
[cache setObject:@2 forKey:@"two"];
[cache setObject:@3 forKey:@"three"];
// 访问缓存项
NSLog(@"Value for 'one': %@", [cache objectForKey:@"one"]); // 输出: 1
// 添加新的缓存项(会触发移除最久未使用的项)
[cache setObject:@4 forKey:@"four"];
// 检查移除情况
if (![cache objectForKey:@"two"]) {
NSLog(@"'two' has been removed from cache"); // 输出: 'two' has been removed from cache
}
}
return 0;
}
import Foundation
class LRUCache<Key: Hashable, Value> {
private var keys: [Key] = []
private var values: [Value] = []
private var cache: [Key: Value] = [:]
private var maxCacheSize: Int
private let lock = NSLock()
init(maxCacheSize: Int) {
self.maxCacheSize = maxCacheSize
}
func set(value: Value, forKey key: Key) {
lock.lock()
defer { lock.unlock() }
if let index = keys.firstIndex(of: key) {
// If the key exists, update the value and move the key to the end
values[index] = value
keys.remove(at: index)
values.remove(at: index)
} else if keys.count >= maxCacheSize {
// If the cache is full, remove the least recently used item
let removedKey = keys.removeFirst()
values.removeFirst()
cache.removeValue(forKey: removedKey)
}
// Add the new key-value pair to the end
keys.append(key)
values.append(value)
cache[key] = value
}
func value(forKey key: Key) -> Value? {
lock.lock()
defer { lock.unlock() }
if let index = keys.firstIndex(of: key) {
// Move the accessed key to the end to mark it as recently used
let value = values[index]
keys.remove(at: index)
values.remove(at: index)
keys.append(key)
values.append(value)
return value
}
return nil
}
func removeValue(forKey key: Key) {
lock.lock()
defer { lock.unlock() }
if let index = keys.firstIndex(of: key) {
keys.remove(at: index)
values.remove(at: index)
cache.removeValue(forKey: key)
}
}
func removeAllValues() {
lock.lock()
defer { lock.unlock() }
keys.removeAll()
values.removeAll()
cache.removeAll()
}
}
-
初始化缓存:
- 使用 NSMutableArray 来存储缓存的键(_keys)和对应的值(_values)。
- 使用 NSMutableDictionary 来快速查找缓存中的对象。
- 使用 NSLock 来确保线程安全。
-
设置缓存:
- setObject:forKey: 方法将新对象插入缓存。
- 如果键已经存在,更新其值并将其移动到数组末尾(表示最近使用)。
- 如果缓存已满,移除最早的键值对(表示最久未使用)。
-
获取缓存值:
- objectForKey: 方法从缓存中获取值。
- 如果键存在,更新其位置以标记为最近使用。
-
删除缓存值:
- removeObjectForKey: 方法从缓存中删除指定键的值。
-
清空缓存:
- removeAllObjects 方法清空所有缓存。
阅读时长统计¶
为何要有不同类型的记录器,你的考虑是什么?
- 基于不同分类场景提供的关于记录的封装、适配
记录的数据会由于某种原因丢失,你是怎样处理的?
- 定时写磁盘
- 限定内存缓存条数(如10条),超过该条数,即写磁盘
关于延时上传的具体场景有哪些
- 前后台切换
- 从无网到有网的变化
- 通用轻量接口捎带
长传时机是怎样把控的?
- 立刻上传
- 延时上传
- 定时上传
MVVM¶
MVVM(Model-View-ViewModel)是一种软件架构设计模式,尤其适用于构建用户界面的应用程序。它通过分离用户界面开发与业务逻辑来简化代码,使应用程序更容易测试和维护。MVVM 模式最常用于开发平台,如 iOS、Android、和 WPF(Windows Presentation Foundation)。
MVVM 架构的主要组成部分
-
Model:
- 数据和业务逻辑的表示层。
- 负责处理数据和业务规则,但不涉及 UI 逻辑。
- 例如,网络请求、数据库访问、数据处理等都在这一层进行。
-
View:
- 用户界面层。
- 负责显示数据和处理用户交互。
- 例如,按钮、标签、表格视图等 UI 元素。
-
ViewModel:
- 连接 Model 和 View 的桥梁。
- 负责从 Model 中获取数据,并将数据转换为 View 可用的形式。
- 处理 View 的交互,并将用户的操作传递给 Model。
- 通过数据绑定(Data Binding)将 View 和 ViewModel 连接起来。
MVVM 在 iOS 中的实现示例
我们以一个简单的 iOS 应用为例,该应用展示一个用户列表,并在点击某个用户时显示详细信息。
Model
import Foundation
struct User {
let id: Int
let name: String
let email: String
}
class UserService {
func fetchUsers(completion: @escaping ([User]) -> Void) {
// 模拟网络请求
let users = [
User(id: 1, name: "John Doe", email: "john@example.com"),
User(id: 2, name: "Jane Smith", email: "jane@example.com")
]
completion(users)
}
}
ViewModel
import Foundation
class UserViewModel {
private let userService: UserService
var users: [User] = []
var reloadTableView: (() -> Void)?
init(userService: UserService) {
self.userService = userService
fetchUsers()
}
func fetchUsers() {
userService.fetchUsers { [weak self] users in
self?.users = users
self?.reloadTableView?()
}
}
func getUser(at indexPath: IndexPath) -> User {
return users[indexPath.row]
}
}
View (Controller)
import UIKit
class UserViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private var viewModel: UserViewModel!
private let tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor)
])
viewModel = UserViewModel(userService: UserService())
viewModel.reloadTableView = { [weak self] in
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
// UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
let user = viewModel.getUser(at: indexPath)
cell.textLabel?.text = user.name
cell.detailTextLabel?.text = user.email
return cell
}
// UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user = viewModel.getUser(at: indexPath)
let detailVC = UserDetailViewController(user: user)
self.navigationController?.pushViewController(detailVC, animated: true)
}
}
class UserDetailViewController: UIViewController {
private let user: User
init(user: User) {
self.user = user
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let nameLabel = UILabel()
nameLabel.text = "Name: \(user.name)"
nameLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameLabel)
let emailLabel = UILabel()
emailLabel.text = "Email: \(user.email)"
emailLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(emailLabel)
NSLayoutConstraint.activate([
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
nameLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
emailLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 20)
])
}
}
解释
-
Model:
User
结构体表示用户数据。UserService
类负责获取用户数据,这里模拟了一个网络请求。
-
ViewModel:
UserViewModel
类管理用户数据和与视图相关的逻辑。fetchUsers
方法从UserService
获取数据,并通知视图更新。
-
View:
UserViewController
类展示用户列表。- 它通过
UserViewModel
获取数据,并在用户点击时导航到详细信息页面。 UserDetailViewController
类展示用户的详细信息。
通过这种方式,数据逻辑和视图逻辑分离,方便测试和维护。如果数据源或数据逻辑发生变化,视图层的代码基本不需要修改。
客户端整体架构¶
- 独立于 app 的通用层: 时长统计,崩溃统计,网络第三方库
- 通用业务层: 自定义的 UI 控件封装
- 中间层: 协调解耦
- 业务 a, 业务 b, c, d
各个业务之间的解耦通信方式?
- openURL
- 依赖注入
理解你的需求后,我来为你设计一个更详细的客户端架构,包含独立的通用层、通用业务层、中间层以及具体业务模块。以下是该架构的层次设计:
客户端整体架构设计
-
通用层 (Common Layer)
- 描述: 最底层,提供独立于具体应用的通用功能。
-
职责:
- 公共工具类和扩展(Utility classes, Extensions)
- 网络请求库(Networking library)
- 本地存储(Local Storage, Database Access)
- 通用的UI组件(Common UI Components)
- 日志记录(Logging)
- 配置管理(Configuration Management)
-
通用业务层 (Common Business Layer)
- 描述: 提供与应用业务相关的公共服务和逻辑。
- 职责:
- 用户管理(User Management)
- 鉴权和授权(Authentication and Authorization)
- 统一错误处理(Error Handling)
- 通用数据模型(Common Data Models)
- 通用服务(Common Services)
-
中间层 (Middleware Layer)
- 描述: 连接通用业务层和具体业务模块,协调各个业务模块间的交互。
- 职责:
- 路由和导航(Routing and Navigation)
- 状态管理(State Management)
- 事件处理(Event Handling)
- 中央数据存储(Central Data Store)
-
具体业务模块 (Feature Modules)
- 描述: 具体的业务功能模块,每个模块独立开发,独立维护。
- 职责:
- 业务逻辑和数据处理(Business Logic and Data Processing)
- 特定的UI和用户交互(Specific UI and User Interaction)
- 模块内的状态管理(Module-Specific State Management)
架构图示
+---------------------------------------------------+
| 具体业务模块 (Feature Modules) |
| +-------------+ +-------------+ +-----------+|
| | 业务 A | | 业务 B | | 业务 C ||
| +-------------+ +-------------+ +-----------+|
+---------------------------------------------------+
| 中间层 (Middleware Layer) |
+---------------------------------------------------+
| 通用业务层 (Common Business Layer) |
+---------------------------------------------------+
| 通用层 (Common Layer) |
+---------------------------------------------------+
各层具体实现示例(以 Swift 语言的 iOS 应用为例)
一. 通用层 (Common Layer)
网络请求库
import Foundation
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func requestData(url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url, completionHandler: completion)
task.resume()
}
}
日志记录
import Foundation
class Logger {
static func log(_ message: String) {
#if DEBUG
print(message)
#endif
}
}
配置管理
import Foundation
class Configuration {
static let shared = Configuration()
private init() {}
var apiEndpoint: String {
return "https://api.example.com"
}
}
二. 通用业务层 (Common Business Layer)
用户管理
import Foundation
class UserManager {
static let shared = UserManager()
private var currentUser: User?
private init() {}
func login(username: String, password: String, completion: @escaping (Bool) -> Void) {
// 登录逻辑
completion(true)
}
func logout() {
currentUser = nil
}
func getCurrentUser() -> User? {
return currentUser
}
}
统一错误处理
import Foundation
class ErrorHandler {
static func handleError(_ error: Error) {
// 统一错误处理逻辑
Logger.log("Error: \(error.localizedDescription)")
}
}
三. 中间层 (Middleware Layer)
路由和导航
import UIKit
class Router {
static func navigateTo(screen: Screen, from viewController: UIViewController) {
// 导航逻辑
let destinationVC = getViewController(for: screen)
viewController.navigationController?.pushViewController(destinationVC, animated: true)
}
private static func getViewController(for screen: Screen) -> UIViewController {
// 根据 Screen 枚举返回相应的 ViewController
switch screen {
case .home:
return HomeViewController()
case .profile:
return ProfileViewController()
}
}
}
enum Screen {
case home
case profile
}
状态管理
import Foundation
class AppState {
static let shared = AppState()
var isLoggedIn: Bool = false
}
四. 具体业务模块 (Feature Modules)
业务 A - 用户资料模块
ViewModel
import Foundation
class UserProfileViewModel {
private let userManager: UserManager
var user: User? {
return userManager.getCurrentUser()
}
init(userManager: UserManager = .shared) {
self.userManager = userManager
}
func logout() {
userManager.logout()
}
}
View
import UIKit
class UserProfileViewController: UIViewController {
private var viewModel: UserProfileViewModel!
private let nameLabel = UILabel()
private let logoutButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
viewModel = UserProfileViewModel()
view.backgroundColor = .white
setupUI()
if let user = viewModel.user {
nameLabel.text = "\(user.firstName) \(user.lastName)"
}
}
private func setupUI() {
nameLabel.translatesAutoresizingMaskIntoConstraints = false
logoutButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameLabel)
view.addSubview(logoutButton)
NSLayoutConstraint.activate([
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
nameLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
logoutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
logoutButton.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 20)
])
logoutButton.setTitle("Logout", for: .normal)
logoutButton.setTitleColor(.blue, for: .normal)
logoutButton.addTarget(self, action: #selector(logout), for: .touchUpInside)
}
@objc private func logout() {
viewModel.logout()
// Navigate to login screen or handle logout
}
}
总结
这个架构设计考虑了可扩展性、模块化和可维护性,通过将不同职责分离到各自的层中,可以实现更加清晰和可维护的代码结构。
- 通用层 (Common Layer) 提供基础设施和工具支持,独立于具体业务。
- 通用业务层 (Common Business Layer) 提供通用业务逻辑和服务,供各个业务模块共享。
- 中间层 (Middleware Layer) 负责协调各个业务模块,管理应用的整体状态和导航。
- 具体业务模块 (Feature Modules) 每个业务模块独立实现自己的业务逻辑和 UI,互不干扰。
这种架构不仅适用于移动客户端开发,还可以推广到其他客户端应用程序,如桌面应用、Web应用等。
依赖注入 (Dependency Inj
通过依赖注入,将模块所需的依赖通过构造函数或属性注入的方式提供,而不是在模块内部创建依赖。这可以通过手动注入或使用依赖注入框架来实现。
import Foundation
class UserService {
func fetchUsers() -> [User] {
// 获取用户数据
return []
}
}
class UserViewModel {
private let userService: UserService
init(userService: UserService) {
self.userService = userService
}
func loadUsers() {
let users = userService.fetchUsers()
// 处理用户数据
}
}
// 使用示例
let userService = UserService()
let userViewModel = UserViewModel(userService: userService)
路由和导航 (Routing and Navigation)
使用中间层的路由器或导航器来管理视图控制器之间的导航,使视图控制器之间的跳转逻辑集中在一处,避免直接相互引用。
import UIKit
class Router {
static let shared = Router()
private init() {}
func navigate(to screen: Screen, from viewController: UIViewController) {
let destinationVC = getViewController(for: screen)
viewController.navigationController?.pushViewController(destinationVC, animated: true)
}
private func getViewController(for screen: Screen) -> UIViewController {
switch screen {
case .home:
return HomeViewController()
case .profile:
return ProfileViewController()
}
}
}
enum Screen {
case home
case profile
}
// 使用示例
Router.shared.navigate(to: .profile, from: currentViewController)
协议和接口 (Protocols and Interfaces)
使用协议定义模块间的契约,各模块通过实现这些协议进行解耦,调用者只依赖协议,而不依赖具体实现。
import Foundation
protocol UserServiceProtocol {
func fetchUsers() -> [User]
}
class UserService: UserServiceProtocol {
func fetchUsers() -> [User] {
// 获取用户数据
return []
}
}
class UserViewModel {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol) {
self.userService = userService
}
func loadUsers() {
let users = userService.fetchUsers()
// 处理用户数据
}
}
// 使用示例
let userService = UserService()
let userViewModel = UserViewModel(userService: userService)