GithubHelp home page GithubHelp logo

code-review's People

Contributors

clmct avatar

Stargazers

 avatar

Watchers

 avatar

code-review's Issues

UIFont Constants

Работа с UIFont

За константы в коде плюс в карму. Подсвечу следующий момент

extension UIFont {
    public enum FontType: String {
        case semibold = "-SemiBold"
        case regular = "-Regular"
        case medium = "-Medium"
        case bold = "-Bold"
    }

    static func montserrat(_ type: FontType = .regular, size: CGFloat = UIFont.systemFontSize) -> UIFont {
        return UIFont(name: "Montserrat\(type.rawValue)", size: size)!
    }
}

// MARK: Constants
extension UIFont {
    static let smallerLabelMedium = UIFont.montserrat(.medium, size: 10)
    
    static let smallLabel = UIFont.montserrat(.regular, size: 12)
    
    static let defaultLabel = UIFont.montserrat(.regular, size: 16)
    static let defaultLabelBold = UIFont.montserrat(.bold, size: 16)
    
    static let mediumLabel = UIFont.montserrat(.regular, size: 18)
    static let mediumLabelSemibold = UIFont.montserrat(.semibold, size: 18)
    static let mediumLabelBold = UIFont.montserrat(.bold, size: 18)
    
    static let largeLabelBold = UIFont.montserrat(.bold, size: 24)
    
    static let largerLabelBold = UIFont.montserrat(.bold, size: 28)
}

Сейчас получается, что используется определенный шрифт - UIFont.montserrat. При смене шрифта придется рефакторить код. Нужна абстракция - UIFont.appFont(.bold, size: 28). В appFont уже конкретный шрифт - мы можем менять реализацию без необходимости рефакторинга. Название шрифта Montserrat можно так же вынести в константы.

UserDefaultsManager


class UserDefaultsManager {
    
    // MARK: Tokens
    func readTokens() -> (accessToken: String?, refreshToken: String?) {
        return (readAccessToken(), readRefreshToken())
    }
    
    func readAccessToken() -> String? {
        return read(forKey: UserDefaultsKeys.accessTokenKey)
    }
    
    func readRefreshToken() -> String? {
        return read(forKey: UserDefaultsKeys.refreshTokenKey)
    }
    
    func writeTokens(accessToken: String, refreshToken: String) {
        write(accessToken, forKey: UserDefaultsKeys.accessTokenKey)
        write(refreshToken, forKey: UserDefaultsKeys.refreshTokenKey)
    }
    
    func tokensExists() -> Bool {
        let tokens = readTokens()
        return tokens.accessToken != nil
            && tokens.refreshToken != nil
    }
    
    // MARK: User Id
    func readUserId() -> String? {
        return read(forKey: UserDefaultsKeys.userId)
    }
    
    // MARK: Common Methods
    func read(forKey key: String) -> String? {
        return UserDefaults.standard.string(forKey: key)
    }
    
    func write(_ value: String, forKey key: String) {
        UserDefaults.standard.set(value, forKey: key)
    }
    
    func checkValue(forKey key: String) -> Bool {
        return UserDefaults.standard.object(forKey: key) != nil
    }
}

В проекте существует UserDefaultsManager, как property wrapper. Но в тоже время он используется только в NetworkService и SceneDelegate для работы с токенами. В других частях используется UserDefaults.standard. Класс для токенов, а название общее. Это вводит в заблуждение разработчиков. Предлагаю использовать везде UserDefaultsManager с static методами read и write как property wrapper. А для работы с токенами создать UserDefaultsTokenManager, который будет взаимодействовать с UserDefaultsManager. И поместить эти утилиты в папку Utils (смотреть замечание про структуру проекта). Материал на эту темы. И еще

Неиспользуемый код (мертвый код)

Неиспользуемый код (мертвый код)

В целом проект выполнен с соблюдением code style. Автор молодец. Подсвечу один момент: заметил в классе AppDelegate неиспользуемый код.

Код из класса AppDelegate

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }

Почему это плохо
Код, который не используется считается плохим тоном. Его сложно читать и он может запутать программистов и коллег по цеху). Более подробно можно почитать на этом сайте

Решение
От такого кода нужно избавиться. Если нужен будет старый код, то его можно найти в истории гита. Это сделает проект более понятным и читаемым.

Именование Delegate

ChooseImageActionSheetDelegate

Именовать Delegate неправильное. Правильное и согласованное именование делает код читаемым.

protocol ChooseImageActionSheetDelegate {
    @available(iOS 14, *)
    func PHPickerDelegate() -> PHPickerViewControllerDelegate
    func imagePickerDelegate() -> (UIImagePickerControllerDelegate & UINavigationControllerDelegate)
    func presentAction(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
}

func presentAction(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) должно быть func сhooseImageActionSheet(_ viewController: сhooseImageActionSheet, _ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)

UserCardViewModelDelegate

protocol UserCardViewModelDelegate {
    func onCardTap(user: User)
}

Аналогично func onCardTap(user: User) -> func UserCardViewModel(_ viewModel, with user: User)
Так же поправить в других местах. Хорошея статья на эту тему

NetworkService

class NetworkService {
    // MARK: Properties
    private let defaults: UserDefaultsManager
    
    private var isRetrying = false
    
    // MARK: Init
    init(defaultsManager: UserDefaultsManager) {
        defaults = defaultsManager
    }
}

// MARK: Interceptor
extension NetworkService: RequestInterceptor {
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var request = urlRequest
        guard let token = defaults.readAccessToken() else {
            completion(.success(urlRequest))
            return
        }
        
        request.setValue("Bearer \(token)", forHTTPHeaderField: RequestKeys.authorization)
        completion(.success(request))
    }
    
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        guard request.retryCount < RetryPolicy.defaultRetryLimit else {
            completion(.doNotRetry)
            return
        }
        
        determineRetryAction(error, retrying: isRetrying, completion: completion)
    }
    
    private func determineRetryAction(_ error: Error, retrying: Bool, completion: @escaping (RetryResult) -> Void) {
        if retrying {
            completion(.retryWithDelay(2))
            return
        }
        
        if error.asAFError?.responseCode == 401 && !retrying {
            isRetrying = true
            requestRefresh { [weak self] isSuccess in
                isSuccess ? completion(.retryWithDelay(2)) : completion(.doNotRetry)
                self?.isRetrying = false
            }
        } else {
            completion(.doNotRetry)
        }
    }
}

// MARK: Auth
extension NetworkService {
    func signInRequest(email: String, password: String,
                       onSuccess success: @escaping () -> Void,
                       onFailure failure: @escaping (_ error: Error) -> Void) {
        AF.request(urlStrings.base + urlStrings.login,
                   method: .post,
                   parameters: [
                    RequestKeys.email: email,
                    RequestKeys.password: password
                   ],
                   encoding: JSONEncoding.default).validate().responseData { [weak self] response in
            switch response.result {
            case .success(let data):
                let decoder = JSONDecoder()
                if let token = try? decoder.decode(TokenResponse.self, from: data) {
                    self?.defaults.writeTokens(accessToken: token.accessToken,
                                               refreshToken: token.refreshToken)
                    success()
                } else {
                    failure(AuthError(code: 1002))
                }
            case .failure(let error):
                failure(AuthError(code: error.responseCode))
            }
        }
    }
...

Класс NetworkService слишком большой и содержит в себе все запросы, которые разделены логически на extensions. Большой класс считается плохим тонном (ссылка) Тяжело читать. Лучше вынести по файлам extensions. А назвать NetworkService+Auth и тд. Это облегчит читаемость и работу с кодом.

Структура проекта

Структура проекта

Структура проекта выдержана в рамках заданной архитектуры - это круто) Отметил бы пару незначительных моментов, которые усложняют работу. Особенно, когда проект станет больше.

  1. Модули экранов стоит вынести в общую папку с названием Modules (как вариант)

  2. В основном используется следующая структура

UserCard
  UserCardView.swift
  UserCardViewModel.swift

В модуле Authentication следующая

 viewModel
  AuthViewModel.swift
  SignInViewModel.swift
  SignUpViewModel.swift
 view
  AuthView.swift
  SignInViewController.swift
  SignUpViewController.swift
 model
 AuthValidator.swift
 TokenResponse.swift

Я бы придерживался единого стиля, как в первом варианте. Со временем будет тяжело ориентироваться во втором типе структуры

  1. Опытные ребята используют Source и Resources папки. Source отвечает за код. Resources за ресурсы, такие как картинги, строки, шрифты и тд. Я бы порекомендовал использовать такой же подход. Коллеги оценят и ориентироваться в проекте станет проще и быстрее)

Рекомендую ознакомиться с Структура iOS проекта на медиум. Так же можно посмотреть Удобная структура iOS проекта на хабре. Для закрепления полезно почитать open source код - iOS-Clean-Architecture-MVVM, подчеркунть моменты чистой архитектуры

KeyboardInfo

struct KeyboardInfo {
    // MARK: Properties
    var animationCurve: UIView.AnimationOptions?
    var animationDuration: Double?
    var isLocal: Bool?
    var frameBegin: CGRect?
    var frameEnd: CGRect?
    
    // MARK: Init
    init?(_ notification: Notification) {
        guard notification.name == UIResponder.keyboardWillShowNotification ||
                notification.name == UIResponder.keyboardWillChangeFrameNotification else { return nil }
        guard let info = notification.userInfo else { return nil }

        animationCurve = info[UIWindow.keyboardAnimationCurveUserInfoKey] as? UIView.AnimationOptions
        animationDuration = info[UIWindow.keyboardAnimationDurationUserInfoKey] as? Double
        isLocal = info[UIWindow.keyboardIsLocalUserInfoKey] as? Bool
        frameBegin = info[UIWindow.keyboardFrameBeginUserInfoKey] as? CGRect
        frameEnd = info[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect
    }
}

Можно упростить следующий код

guard notification.name == UIResponder.keyboardWillShowNotification ||
                notification.name == UIResponder.keyboardWillChangeFrameNotification else { return nil }
        guard let info = notification.userInfo else { return nil }

на

 guard notification.name == UIResponder.keyboardWillShowNotification ||
            notification.name == UIResponder.keyboardWillChangeFrameNotification,
          let info = notification.userInfo else { return nil }

Это повысит читаемость кода

Image Constants

enum ImageNames {
    // MARK: Images
    static let appLogo = "AppLogo"
    static let personPlaceholder = "PersonPlaceholderImage"
    static let match = "MatchImage"
    static let welcomePeople = "WelcomePeopleImage"
    
    // MARK: Icons
    static let streamIcon = "StreamIcon"
    static let peopleIcon = "PeopleIcon"
    static let chatsIcon = "ChatsIcon"
    static let personIcon = "PersonIcon"
    static let crossIcon = "CrossIcon"
    static let heartIcon = "HeartIcon"
    static let cameraIcon = "CameraIcon"
    static let sendIcon = "SendIcon"
    static let editIcon = "EditIcon"
}

За константы названия картинок плюс. Подсвечу два момента.

  1. Лучше выносить UIImage, мы все равно вызываем UIImage. Те будет так

static let appLogo = UIImage(named: "AppLogo")

  1. Для удобной работы с локализацией и ресурсами(картинки) можно использовать R.swift (ссылка) или SwiftGen (ссылка)

Магические строки и константы


extension Date {
    static func isIncreasingDateOrder(date1: Date?, date2: Date?) -> Bool {
        guard let date1 = date1, let date2 = date2 else {
            return date1 != nil
        }
        return date1 > date2
    }
    
    static func from(string: String) -> Date? {
        let formatter = DateFormatter()
        formatter.timeZone = TimeZone(abbreviation: "UTC")
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        return formatter.date(from: string.replacingOccurrences(of: "[UTC]", with: ""))
    }
    
    func toServerFormat() -> String? {
        let formatter = DateFormatter()
        formatter.timeZone = TimeZone(abbreviation: "UTC")
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        return formatter.string(from: self)
    }
    
    func toMessageFormat() -> String {
        let formatter = DateFormatter()
        formatter.timeZone = TimeZone(abbreviation: TimeZone.current.identifier)
        formatter.dateFormat = "HH:mm • d MMMM yyyy"
        formatter.locale = Locale(identifier: "ru_RU_POSIX")
        return formatter.string(from: self)
    }
}

Строки как "en_US_POSIX" являются магическими строками (ссылка), их можно вынести в константы следующим образом. Завести структуру или енам для константы в коде. А в файле перед основным кодом определять константы. Это относится ко всем магическим цифрам и строкам в проекте.

private extension Constants {
    static let enUSPosix = "en_US_POSIX""
}

Структура файла

class UserMessageCell: MessageTableViewCell {
    
    // MARK: Public Overrided Methods
    override func setup() {
        super.setup()
        setupAvatarImageView()
        setupMessageContainer()
    }
    
    // MARK: Private Setup Methods
    private func setupAvatarImageView() {
        avatarImageView.snp.makeConstraints { make in
            make.bottom.equalTo(self).inset(Dimensions.smallInset)
            make.bottom.equalTo(messageContainer.snp.bottom)
            make.leading.equalTo(messageContainer.snp.trailing).inset(-Dimensions.smallInset)
            make.trailing.equalTo(self).inset(Dimensions.defaultInset)
            make.width.equalTo(Dimensions.mediumInset)
            make.width.equalTo(avatarImageView.snp.height)
        }
    }
    
    private func setupMessageContainer() {
        messageContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
        messageContainer.snp.makeConstraints { make in
            make.top.equalTo(self)
            make.leading.greaterThanOrEqualTo(self).inset(Dimensions.messageLeadingInset)
        }
    }
}

// MARK: Dimensions
private extension Dimensions {
    static let messageLeadingInset: CGFloat = 80
}

Принято объявлять константы в начале файла. Было бы удобнее вынести константы перед основным кодом, так как до последней строчки мало кто листает. К тому же может путаться с extensions класса, которые принято объявлять после класса. При открытии файла, хочется сразу видеть все константы.

// MARK: Dimensions
private extension Dimensions {
    static let messageLeadingInset: CGFloat = 80
}

class UserMessageCell: MessageTableViewCell {
    
    // MARK: Public Overrided Methods
    override func setup() {
        super.setup()
        setupAvatarImageView()
        setupMessageContainer()
    }
    
    // MARK: Private Setup Methods
    private func setupAvatarImageView() {
        avatarImageView.snp.makeConstraints { make in
            make.bottom.equalTo(self).inset(Dimensions.smallInset)
            make.bottom.equalTo(messageContainer.snp.bottom)
            make.leading.equalTo(messageContainer.snp.trailing).inset(-Dimensions.smallInset)
            make.trailing.equalTo(self).inset(Dimensions.defaultInset)
            make.width.equalTo(Dimensions.mediumInset)
            make.width.equalTo(avatarImageView.snp.height)
        }
    }
    
    private func setupMessageContainer() {
        messageContainer.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
        messageContainer.snp.makeConstraints { make in
            make.top.equalTo(self)
            make.leading.greaterThanOrEqualTo(self).inset(Dimensions.messageLeadingInset)
        }
    }
}

Одиночка

Антипаттерн одиночка в мире iOS разработки

class DatabaseService {
    
    // MARK: Properties
    static var shared = DatabaseService()
    
    private let context: NSManagedObjectContext
    
    // MARK: Init
    private init() {
        let container = NSPersistentContainer(name: "CacheDatabase")
        container.loadPersistentStores { _, error in
            if let error = error {
                print(String(describing: error))
            }
        }
        context = container.viewContext
    }
    
    // MARK: Private Methods
    private func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                context.rollback()
                fatalError(String(describing: error))
            }
        }
    }
}

Использование паттерна одиночка (singleton), в том числе для сервисов является плохим тоном и антипаттерном в iOS разработки. Хорошая статья на эту теме - Почему Singleton антипаттерн. Для решения проблемы я бы посоветовал использовать dependency injection принцип. Dependency Injection, или внедрение зависимостей, — это паттерн настройки объекта, при котором зависимости объекта задаются извне, а не создаются самим объектом. Другими словами, объекты настраиваются внешними объектами. Рекомендую ознакомится более подробно DI в iOS: Complete guide

String Constans

За константы строк в коде плюс в карму) Подсвечу следующие моменты

  1. enum можно вкладывать в enum, это дает нам удобство использование, Вместо Strings.register будет Strings.Headers.register.

  2. Отсутсвие локализации. Когда потребуется локализация, придется потратить много времени, чтобы ее добавить и перенести все строки в файлы локализации, поменять константы и тд. Советую добавлять ее сразу, даже если язык пока один. Это избавит от потенциальных проблем в будущем. (ссылка). Для удобной работы с локализацией и ресурсами(картинки) можно использовать
    R.swift (ссылка) или SwiftGen (ссылка)

enum Strings {
    // MARK: Button Titles
    static let signUp = "Зарегистрироваться"
    static let signIn = "Войти"
    static let decline = "Отказ"
    static let like = "Лайк"
    static let edit = "Редактировать"
    static let save = "Сохранить"
    static let back = "Назад"
    static let cancel = "Отмена"
    static let writeMessage = "Написать сообщение"
    static let deletePhoto = "Удалить фото"
    static let choosePhoto = "Выбрать фото"
    static let libraryActionTitle = "Библиотека"
    static let cameraActionTitle = "Камера"
    static let settingsActionTitle = "Настройки"
    static let notNow = "Не сейчас"
    static let ok = "Ок"
    
    // MARK: Headers
    static let authorization = "Авторизация"
    static let register = "Регистрация"
    static let aboutYourselfHeader = "Расскажи о себе"
    static let aboutYourselfTopicsHeader = "Укажи интересы"
    static let editProfileTopicsHeader = "Интересы"
    static let welcomeHeader = "Ты найдешь того, кто поревьюит твой код"
    static let match = "Ваши интерфейсы подошли друг к другу"
    

Архитектура

Архитектура проекта MMVM. Проект выдержан в рамках заданной концепции - это круто) Подсветил бы следующий момент - навигация в ViewController. Сейчас получается, что VC знает о сервисах и хранит ссылки на них, отвечает за навигацию. ViewController не должен отвечать за навигацию и хранить ссылки на сервисы, это нарушение SRP. Сервисы в ViewModel, а навигация в отдельной сущности (координатор). Хорошея статья Принципы SOLID, о которых должен знать каждый разработчик на тему SOLID. За навигацию в проекте должна отвечать отдельная сущность. Я бы предложил вынести эту логику в Router, Assembly (Builder) или Coordinator. Router отвечает за навигацию в приложении в Assembly за сборку модуля. Coordinator же берет на себе роль сборщика и роль навигации. Использование routing позволит переиспользовать сборку и навигацию в коде, делать код менее зависимым и гибким. Подробно о навигации: роутер и координтор.

Force Unwrap

В проекте есть Force Unwrap

В целом в проекте не используются плохие практики работы с Optional - force unwrap, код читаемый и безопасный (автор молодей). Подсвечу один момент. В классе AppDelegate и SceneDelegate есть использование force unwrap. Даже когда программист уверен, что не придет nil, глаз может замылиться и проглядеть этот момент - приложение упадет. Рекомендую обратить внимание на линтер кода, который подсветит ошибки на стадии билда. Ознакомиться можно в SwiftLint — чистота и порядок в iOS проекте на хабре.

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        
        let rootViewController = userDefaultManager.tokensExists() ?
        TabBarViewController(networkManager: networkManager) :
        WelcomeViewController(networkManager: networkManager)
        
        window!.rootViewController = NavigationControllerFactory
            .createHiddenNavBarNavigationController(rootViewController: rootViewController)
        window!.makeKeyAndVisible()
        
        return true
    }

Для наглядности разберем Force Unwrap на примере ниже

var someVariable: String?
var somethingElse: String = "hello"
 
func setupApp() {
    self.somethingElse = self.someVariable!
}

В данном случае someVariable равно Optional. При обращение к переменной приложение упадет Как раз для защиты от таких ошибок и создавались опционалы, а мы сводим все их усилия не нет. Для безопасного получения значения можно использовать:

Optional Binding,

func setupApp() {
    if let theThing = someVariable {
        self.somethingElse = self.someVariable!
    } else {
        print("error")
    }
}

Optional Chaining

func setupApp() {
    self.somethingElse = someClass?.createString()
}

Nil Coalescing

func setupApp() {
    self.somethingElse = someVariable ?? "error"
}

Более подробно можно ознакомиться в данной статье Три ошибки iOS-разработчика, которые могут дорого стоить

Утечка памяти и ARC

Утечка памяти и ARC

class ChooseImageActionSheet: UIAlertController {
    
    // MARK: Properties
    var delegate: ChooseImageActionSheetDelegate?
    
    // MARK: Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupActions()
    }

Так же вдругих классах

delegate объявляют как weak var delegate: ChooseImageActionSheetDelegate?
ключевое слово weak делает ссылку скобой и счетчик сильных ссылок не увеличивается в механизме ARC.
Использование сильных ссылок введет к утечкам памяти, когда два класса сильно ссылаются друг на друга. Слабые ссылки позволяют нам разорвать эту связь. Материал по этой теме Swift: ARC и управление памятью

Protocol и Extension

За использование протоколов плюс в карму, как и за кастомный NavigationController. Подсвечу один момент. В проекте встречается следующий стиль комформить протокол.

protocol NavigationControllerProtocol: UINavigationController {
    func setupCommonNavigationBarSettings()
}

class NavigationController: UINavigationController, NavigationControllerProtocol {
    func setupCommonNavigationBarSettings() {
        navigationBar.backgroundColor = Colors.transparent
        navigationBar.shadowImage = UIImage()
        navigationBar.tintColor = Colors.pink
        navigationBar.barTintColor = Colors.white
        
        navigationBar.titleTextAttributes = [
            NSAttributedString.Key.font: UIFont.mediumLabelSemibold,
            NSAttributedString.Key.foregroundColor: Colors.pink ?? UIColor.systemGray
        ]
    }
}

Комформить протоколы принято в extension, это повышает читаемость кода.

extension NavigationController: NavigationControllerProtocol {
...
}

Database Entities

public class ChatListItemLocal: NSManagedObject {
    static func updateOrCreate(fromContest domainItems: [ChatListItem],
                               context: NSManagedObjectContext) -> [ChatListItemLocal] {
        return domainItems.map { domainItem in
            return updateOrCreate(from: domainItem, context: context)
        }
    }
    
    static func updateOrCreate(from domainItem: ChatListItem,
                               context: NSManagedObjectContext) -> ChatListItemLocal {
        let request = ChatListItemLocal.fetchRequest()
        request.predicate = NSPredicate(format: "chat.id == \"\(domainItem.chat.id)\"")
        guard let localItem = try? context.fetch(request).first else {
            return create(from: domainItem, context: context)
        }
        
        localItem.update(with: domainItem, context: context)
        return localItem
    }

Удобное, но опасное решение. Сущность NSManagedObject не должна владеть context DataBase. Это нарушение SRP. Сейчас DataBase выполняет роль proxy класса в плохом смысле этого слова, он перекладывает свою ответственность на сущности CoreData. Database singleton обращается к static полям Database Entities. CoreData должна владеть context и выполнять работу с сущностями. А сейчас в

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.