I want to refactor my code to apply clean approach.
I have a class user
class User {
let name: String?
let id: String
var isOnline: Bool
var mesaageHistory = [Message]()
init(name: String, id: String, isOnlineStatus: Bool) {
self.name = name
self.id = id
self.isOnline = isOnlineStatus
}
}
Then I'm using fabric pattern to create list of my users.
protocol CreateUserProtocol: class {
func sortArray(inputArr: [User])
}
class CreateUserEntity: CreateUserProtocol {
static let shared = CreateUserEntity()
var users = [User]()
func sortArray(inputArr: [User]){
var applySortingByDate: Bool = false
for user in inputArr {
if !user.mesaageHistory.isEmpty {
applySortingByDate = true
}
}
if applySortingByDate {
inputArr.sorted(by: { (first, second) -> Bool in
(first.mesaageHistory.last?.messageTime)! < (second.mesaageHistory.last?.messageTime)!
})
} else {
inputArr.sorted(by: { (first, second) -> Bool in
first.name! < second.name!
})
}
}
}
One controller is responsible for appending new users, while another is used to retrieve them and bind them to tableView. Everything is working fine, but I think my solution is not good enough for scaling.
Moreover in one of my VC I use to sort my Users to online and offline. I think, that I shouldn't do that in my VC and to put this logic into my CreateUserEntity
var onlineUsersData = [User]()
var offlineUsersData = [User]()
private func classifyUsers() {
for user in CreateUserEntity.shared.users {
print("is user online: \(user.isOnline)")
print(CreateUserEntity.shared.users.count)
if user.isOnline == true && !onlineUsersData.contains(user) {
onlineUsersData.append(user)
}
if user.isOnline == false && !offlineUsersData.contains(user) {
offlineUsersData.append(user)
}
}
}
I'd like to rewrite it in proper way, what can you recommend me?
From my opinion try to use firstly struct instead of the class.
Example:
struct User {
let name: String
}
Then you should figure out where you will be storing these users? Now they are in the memory. So we should define where and how we will be storing them.
So probably for this case we can consider NSUserDefaults as core for the class that will store users.After that we should create facade to this class to manage our users.
protocol UserStoreFacade {
func fetch(name withName:String) -> User
func create(name withName:String) -> User
func save(user:User)
func update(name newName:String) -> User
func delete(name withName:String)
}
And UserStore that is used to manage user.
class UserStore: UserStoreFacade {
let defaults = UserDefaults(suiteName: "User")
func fetch(name withName:String) -> User {
let encodeData = defaults?.dictionary(forKey: withName)
return User(dictionary: encodeData as! Dictionary<String, AnyObject>)
}
func create(name withName: String) -> User {
return User(name: withName)
}
func save(user: User) {
defaults?.set(user.encode(), forKey: user.name)
}
func update(name newName:String) -> User {
return User(name: newName)
}
func delete(name withName:String) {
defaults?.removeObject(forKey: withName)
}
}
It is quite primitive but still showing case how it is possible to accomplish.
Related
Creating an example for a struct is very easy and straightforward. For example,
import Foundation
struct User: Identifiable, Codable {
let id: UUID
let isActive: Bool
let name: String
let age: Int
let company: String
static let example = User(id: UUID(), isActive: true, name: "Rick Owens", age: 35, company: "Rick Owens Inc.")
}
Now, how can I create an example if I made this an entity in core data? I can't just put let example = CachedUser(id: UUID(), ...) like I did with the struct. I want this example to be part of my core data automatically without having to manually create it by using forms, buttons, etc... Thanks in advance!
You can simply check if your default user exists in database. If it does not then you need to create one and save it. Something like the following would work if you have synchronous operations:
class CachedUser {
static var example: CachedUser = {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
if let existingUser = Database.fetchUser(id: exampleUUID) {
return existingUser
} else {
let newUser = CachedUser()
// TODO: apply example values to user
Database.saveUser(newUser)
return newUser
}
}()
}
This will lazily return existing or generate a new user for you. This user will then be persistent in your database.
The code will only be executed once per session, first time you call CachedUser.example.
If you have your database setup asynchronous then with closures it should look something like this:
class User {
static private(set) var example: User!
static func prepareExampleUser(_ completion: () -> Void) {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
Database.fetchUser(id: exampleUUID) { user in
if let user = user {
example = user
completion()
} else {
let newUser = User()
newUser.id = exampleUUID
// TODO: apply example values to user
Database.saveUser(newUser) {
example = newUser
completion()
}
}
}
}
But in this case it makes sense to warmup your application before you show screens that require this user to be present. You can for instance have a loading screen when your app first starts and continue to next screen once this method has finished...
// Loading screen enters
self.startLoading()
User.prepareExampleUser {
self.navigateToNextScreen()
self.stopLoading()
}
In both cases you now hold a static property to your example entry such as User.example which can be used anywhere.
But in both cases you may stumble to issue if user (if able to) deletes this entry from database. You would need to handle that case. Either prevent that action or create a new example user once the old one is deleted.
To access this manager put
let mgr = CachedUserPersistenceManager()
In a ViewModel or a View
/// Manager for the Item entity
class CachedUserPersistenceManager: PersistenceManager<CachedUser>{
let sampleUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
init(isTest: Bool = false) {
super.init(entityType: CachedUser.self, isTest: isTest)
//Preloads the user
preloadSample()
}
///Preloads a sample object to the context
func preloadSample(){
let list = retrieveObjects(sortDescriptors: nil, predicate: NSPredicate(format: "%K == %#", #keyPath(CachedUser.uuid), sampleUUID as CVarArg)
)
if list.isEmpty{
let sampleItem = createObject()
sampleItem.uuid = sampleUUID
save()
}
}
override func addSample() -> CachedUser {
let new = super.addSample() as CachedUser
//add any sample code
return new
}
override func createObject() -> CachedUser {
super.createObject()!
}
override func updateObject(object: CachedUser) -> Bool {
//Replace the uuid if needed
if object.uuid == sampleUUID{
object.uuid = UUID()
}
return super.updateObject(object: object)
}
}
The generic classes that are a part of this code are below. You don't need them per say it just makes some of the code reusable through the app.
//Manager for any Entity
class PersistenceManager<T : NSManagedObject>{
let serviceSD: CoreDataPersistenceService<T>
internal init(entityType: T.Type, isTest: Bool = false) {
self.serviceSD = CoreDataPersistenceService(isTest: isTest, entityType: entityType)
}
//MARK: convenience
func addSample() -> T {
let newItem = createObject()
return newItem!
}
//MARK: Persistence Service Methods
func createObject() -> T? {
let result = serviceSD.createObject()
return result
}
func updateObject(object: T) -> Bool {
return serviceSD.updateObject(object: object)
}
func deleteObject(object: T) -> Bool {
return serviceSD.deleteObject(object: object)
}
func deleteAllObjects(entityName: String, isConfirmed: Bool) -> Bool {
return serviceSD.deleteAllObjects(isConfirmed: isConfirmed)
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> [T]{
return serviceSD.retrieveObjects(sortDescriptors: sortDescriptors, predicate: predicate)
}
func retrieveObject(id: String) -> T? {
return serviceSD.retrieveObject(sortDescriptors: nil, id: id).first
}
func resetChanges() {
serviceSD.resetChanges()
}
func save() {
_ = serviceSD.save()
}
}
//Service for Any Entity
class CoreDataPersistenceService<T: NSManagedObject>: NSObject {
var persistenceController: PersistenceController
let entityType: T.Type
required init(isTest: Bool = false, entityType: T.Type) {
if isTest{
self.persistenceController = PersistenceController.preview
}else{
self.persistenceController = PersistenceController.previewAware
}
self.entityType = entityType
super.init()
}
//MARK: CRUD methods
func createObject() -> T? {
let result = entityType.init(context: persistenceController.container.viewContext)
return result
}
func updateObject(object: T) -> Bool {
var result = false
result = save()
return result
}
func deleteObject(object: T) -> Bool {
var result = false
persistenceController.container.viewContext.delete(object)
result = save()
return result
}
func deleteAllObjects(isConfirmed: Bool) -> Bool {
var result = false
//Locked in so only the Generic "Item" can be deleted like this
if entityType == Item.self && isConfirmed == true{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: entityType.fetchRequest())
do {
try persistenceController.container.persistentStoreCoordinator.execute(deleteRequest, with: persistenceController.container.viewContext)
} catch {
print(error)
result = false
}
}
return result
}
func resetChanges() {
persistenceController.container.viewContext.rollback()
_ = save()
}
func save() -> Bool {
var result = false
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
result = true
}else{
result = false
}
} catch {
print(error)
}
return result
}
func retrieveObject(sortDescriptors: [NSSortDescriptor]? = nil, id: String) -> [T]{
return retrieveObjects(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "id == %#", id))
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) -> [T]
{
let request = entityType.fetchRequest()
if let sortDescriptor = sortDescriptors
{
request.sortDescriptors = sortDescriptor
}
if let predicate = predicate
{
request.predicate = predicate
}
do
{
let results = try persistenceController.container.viewContext.fetch(request)
return results as! [T]
}
catch
{
print(error)
return []
}
}
}
The previewAware variable that is mentioned goes with the Apple standard code in the PersistenceController
It automatically give you the preview container so you don't have to worry about adapting your code for samples in Canvas. Just add the below code to the PersistenceController
static var previewAware : PersistenceController{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return PersistenceController.preview
}else{
return PersistenceController.shared
}
}
I try to refactor my code to be clean and I wonder if I go into good direction.
I use Model View Presenter pattern but I also use Service class when I want to make connection with database.
protocol NewWorkoutServiceType {
func checkNewWorkout(workoutName: String) -> Bool
func saveNewWorkout(exercises: [ExerciseCellInNewWorkout], workoutName: String, workoutDate: String, completion: (WorkoutModelInfo) -> Void)
}
class NewWorkoutService: NewWorkoutServiceType {
let newWorkoutDBHandler: SaveNewWorkoutProtocol
let checkWorkoutDBHandler: CheckIfWorkoutExistsProtocol
init(newWorkoutDBHandler: SaveNewWorkoutProtocol, checkWorkoutDBHandler: CheckIfWorkoutExistsProtocol) {
self.newWorkoutDBHandler = newWorkoutDBHandler
self.checkWorkoutDBHandler = checkWorkoutDBHandler
}
func checkNewWorkout(workoutName: String) -> Bool {
return checkWorkoutDBHandler.checkIfWorkoutExists(workoutToCheck: workoutName)
}
func saveNewWorkout(exercises: [ExerciseCellInNewWorkout], workoutName: String, workoutDate: String, completion: (WorkoutModelInfo) -> Void) {
let savedWorkout = newWorkoutDBHandler.saveNewWorkout(exercises: exercises, workoutName: workoutName, workoutDate: workoutDate)
completion(savedWorkout)
}
}
It is my service class, I use protocol to make it loosely coupled. I have read about SOLID and SRP and I have decided to break my code on small pieces with one responsibility.
protocol SaveNewWorkoutProtocol {
func saveNewWorkout(exercises: [ExerciseCellInNewWorkout], workoutName: String, workoutDate: String) -> WorkoutModelInfo
}
protocol CheckIfWorkoutExistsProtocol {
func checkIfWorkoutExists(workoutToCheck: String) -> Bool
}
class CheckWorkoutDBHandler: CheckIfWorkoutExistsProtocol {
func checkIfWorkoutExists(workoutToCheck: String) -> Bool {
let workoutModel = RealmService.shared.realm.object(ofType: WorkoutModelInfo.self, forPrimaryKey: workoutToCheck)
if workoutModel == nil {
return false
} else {
return true
}
}
}
class NewWorkoutDBHandler: SaveNewWorkoutProtocol {
func saveNewWorkout(exercises: [ExerciseCellInNewWorkout], workoutName: String, workoutDate: String) -> WorkoutModelInfo {
var newExerciseModel = [ExerciseModel]()
for section in 0..<exercises.count {
newExerciseModel.append(ExerciseModel(number: (section + 1),
exercise:
exercises[section].workoutModel.exercise,
exerciseSet:
Array(exercises[section].workoutModel.exerciseSet)))
}
let workoutModel = WorkoutModelInfo(workoutName: workoutName, workoutDate: workoutDate, exercises: newExerciseModel)
RealmService.shared.addModified(workoutModel)
return workoutModel
}
}
class NewWorkoutPresenter {
private let newWorkoutService: NewWorkoutServiceType
weak var newWorkoutPresenterDelegate: NewWorkoutPresenterDelegate?
init(newWorkoutPresenterDelegate: NewWorkoutPresenterDelegate, newWorkoutService:
NewWorkoutServiceType) {
self.newWorkoutPresenterDelegate = newWorkoutPresenterDelegate
self.newWorkoutService = newWorkoutService
}
}
And I think that every think okay, but later when I initialize presenter in VC it is so big and look dirty. So maybe better option is not divide my service class on smaller pieces (separated classes for checking if workout exists and saving workout).
class ViewController: UIViewController {
private lazy var presenter = NewWorkoutPresenter(newWorkoutPresenterDelegate: self, newWorkoutService: NewWorkoutService(newWorkoutDBHandler: NewWorkoutDBHandler(), checkWorkoutDBHandler: CheckWorkoutDBHandler()))
}
I will be greatful for your advice!
Here's a User model class. This model will be container for data while registering new user, logging an already registered user and displaying profile.
struct User {
typealias message = (Bool,String)
var name: String?
var username: String
var password: String
var image: String?
func isValidForLogin() -> message {
let emailMessage = isValidEmail(testStr: username)
let passwordMessage = isValidPassowrd(testStr: password)
if emailMessage.0 && passwordMessage.0 {
return (true,"Valid")
}
if !emailMessage.0{
return (emailMessage.0, emailMessage.1)
}else{
return (passwordMessage.0, passwordMessage.1)
}
}
func isValidForRegister() -> message {
if let name = self.name{
let nameMessage = isValidName(testStr: name)
let emailMessage = isValidEmail(testStr: username)
let passwordMessage = isValidPassowrd(testStr: password)
if emailMessage.0 && passwordMessage.0 && nameMessage.0{
return (true,"Valid")
}
if !emailMessage.0{
return (emailMessage.0, emailMessage.1)
}else if !passwordMessage.0{
return (passwordMessage.0, passwordMessage.1)
}else{
return (nameMessage.0, nameMessage.1)
}
}
return (false, "Name " + Constants.emptyField)
}
private func isValidName(testStr: String) -> message{
if testStr.isEmpty{
return (false, "Name " + Constants.emptyField )
}
return (true, "Valid")
}
private func isValidPassowrd(testStr: String) -> (Bool, String) {
if testStr.isEmpty{
return (false, "Password " + Constants.emptyField )
}
if testStr.count > 6{
return (true, "Valid")
}
return (false, Constants.invalidPassword)
}
private func isValidEmail(testStr: String) -> message {
if testStr.isEmpty{
return (false, "Email " + Constants.emptyField)
}
let emailRegEx = "^(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?(?:(?:(?:[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+(?:\\.[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+)*)|(?:\"(?:(?:(?:(?: )*(?:(?:[!#-Z^-~]|\\[|\\])|(?:\\\\(?:\\t|[ -~]))))+(?: )*)|(?: )+)\"))(?:#)(?:(?:(?:[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)(?:\\.[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)*)|(?:\\[(?:(?:(?:(?:(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))\\.){3}(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))))|(?:(?:(?: )*[!-Z^-~])*(?: )*)|(?:[Vv][0-9A-Fa-f]+\\.[-A-Za-z0-9._~!$&'()*+,;=:]+))\\])))(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?$"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluate(with: testStr)
if result{
return (result, "Valid")
}else{
return (result, Constants.invalidEmail)
}
}
}
I am trying to follow MVVM pattern. So, my ViewModel class for RegisterViewViewModel:
struct RegisterViewModel {
private let minUserNameLength = 4
private let minPasswordLength = 6
var name: String
var email: String
var password: String
private var userModel: User{
return User(name: name, username: email, password: password, image: "")
}
func isValid() -> (Bool, String) {
return userModel.isValidForRegister()
}
func register(){
....
}
}
And in my RegisterViewController :
class RegisterViewController: UIViewController{
#IBOutlet weak var txtName: UITextField!
#IBOutlet weak var txtUsername: UITextField!
#IBOutlet weak var txtPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnSignUpPressed(_ sender: UIButton) {
if let name = txtName.text, let email = txtUsername.text, let password = txtPassword.text{
let userModel = RegisterViewModel(name: name, email: email, password: password)
let validate = userModel.isValid()
if validate.0{
userModel.register()
}else{
//do error handling here
print(validate.1)
}
}
}
}
Am I going in right direction? Any suggestion will be appreciated.
I would recommend you to use RxSwift with MVVM. Also you could export validation to a separate ValidationService class. Otherwise you will probably have to copy same validation methods between different models.
enum ValidationResult {
case ok
case empty
case validating
case failed(message: String)
}
extension ValidationResult {
var isValid: Bool {
switch self {
case .ok:
return true
default:
return false
}
}
var isEmpty: Bool {
switch self {
case .empty:
return true
default:
return false
}
}
}
class ValidationService {
let minPasswordCount = 4
static let shared = ValidationService()
func validateName(_ name: String) -> Observable<ValidationResult> {
if name.isEmpty {
return .just(.empty)
}
if name.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return .just(.failed(message: "Invalid name"))
}
return .just(.ok)
}
}
What you are trying to do is not MVVM pattern.
You are creating a new ViewModel when button is clicked. It is the same as you are creating a business class to handle some business logics.
ViewModel and View are communicating through data binding. If you are familiar with RxSwift, the I suggest to use this library: https://github.com/duyduong/DDMvvm
I wrote this library after using it a lot on private projects. There are examples for you to start and understand how MVVM works. Give it a try!
To implement MVVM in iOS we can use a simple combination of Closure and didSet to avoid third-party dependencies.
public final class Observable<Value> {
private var closure: ((Value) -> ())?
public var value: Value {
didSet { closure?(value) }
}
public init(_ value: Value) {
self.value = value
}
public func observe(_ closure: #escaping (Value) -> Void) {
self.closure = closure
closure(value)
}
}
An example of data binding from ViewController:
final class ExampleViewController: UIViewController {
private func bind(to viewModel: ViewModel) {
viewModel.items.observe(on: self) { [weak self] items in
self?.tableViewController?.items = items
// self?.tableViewController?.items = viewModel.items.value // This would be Momory leak. You can access viewModel only with self?.viewModel
}
// Or in one line:
viewModel.items.observe(on: self) { [weak self] in self?.tableViewController?.items = $0 }
}
override func viewDidLoad() {
super.viewDidLoad()
bind(to: viewModel)
viewModel.viewDidLoad()
}
}
protocol ViewModelInput {
func viewDidLoad()
}
protocol ViewModelOutput {
var items: Observable<[ItemViewModel]> { get }
}
protocol ViewModel: ViewModelInput, ViewModelOutput {}
final class DefaultViewModel: ViewModel {
let items: Observable<[ItemViewModel]> = Observable([])
// Implmentation details...
}
Later it can be replaced with SwiftUI and Combine (when a minimum iOS version in of your app is 13)
In this article, there is a more detailed description of MVVM
https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
class RegisterViewController: UIViewController {
var user = User() {
didSet {
// update UI
}
}
}
Most MVVM/RxSwift developers don't understand the notion of "over-engineering", as can be seen from all previous answers. Two of them refer you to a even more complicated design pattern, and one of them built the said pattern from scratch.
You don't need any of the RxSwift nonsense. MVVM isn't about having an object called view model and shoving everything to it.
Build a model so that when it changes, it updates associated view.
Simple, as all things should be.
Below is the pinnacle of over-engineering
protocol ViewModel: ViewModelInput, ViewModelOutput {}
After you define all these, write them down, train colleagues, draw diagrams, and implement them, you would've realized that it's all boilerplate and you should just drop them.
I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.
I wrote a TaskSync class that is responsible for creating/updating Task objects:
class TaskSync: ISync {
typealias Model = Task
func sync(model: Task) {
let realm = try! Realm()
let inWrite = realm.isInWriteTransaction
if !inWrite {
realm.beginWrite()
}
let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)
// Persist matches as they are not getting fetched with the task
if let _task = _task {
print("matches: \(_task.matches.count)")
model.matches = _task.matches
}
realm.add(model, update: true)
if _task == nil {
var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)
if (user == nil) {
user = model.getUser()
realm.add(user!, update: true)
}
user!.tasks.append(model)
}
if !inWrite {
try! realm.commitWrite()
}
}
func sync(models: List<Task>) {
let realm = try! Realm()
try! realm.write {
models.forEach { task in
sync(model: task)
}
}
}
}
When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.
Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.
Here are the two models:
class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var title: String = ""
dynamic var desc: String = ""
dynamic var price: Float = 0.0
dynamic var calculatedPrice: Float = 0.0
dynamic var location: String = ""
dynamic var duration: Int = 0
dynamic var date: String = ""
dynamic var category: Category?
dynamic var currency: Currency?
dynamic var longitude: Double = 0.0
dynamic var latitude: Double = 0.0
dynamic var state: Int = 0
dynamic var userId: Int = 0
// Existing images
var imagesExisting = List<URLImage>()
// New images
var imagesNew = List<Image>()
// Images deleted
var imagesDeleted = List<URLImage>()
private let users = LinkingObjects(fromType: User.self, property: "tasks")
var user: User?
var matches = List<Match>()
dynamic var notification: Notification?
override static func ignoredProperties() -> [String] {
return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
}
override static func primaryKey() -> String? {
return "id"
}
func getImageMain() -> URLImage? {
for image in imagesExisting {
if image.main {
return image
}
}
return imagesExisting.first
}
func getSection() -> Int {
return state
}
func getSectionFieldName() -> String? {
return "state"
}
func getId() -> Int {
return id
}
func getURL() -> URL? {
if let image = getImageMain() {
return image.getResizedURL()
}
return nil
}
func getState() -> TaskOwnState {
return TaskOwnState(rawValue: state)!
}
func getUser() -> User {
return (user != nil ? user : users.first)!
}
}
class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var state: Int = -1
dynamic var priorityOwnRaw: Int = 0
dynamic var priorityOtherRaw: Int = 0
dynamic var user: User!
var messages = List<Message>()
private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
var task: Task?
dynamic var notification: Notification?
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["task"]
}
func getId() -> Int {
return id
}
func getSection() -> Int {
return 0
}
func getURL() -> URL? {
if let image = user.getImageMain() {
return image.getResizedURL()
}
return nil
}
func getPriorityOwn() -> PriorityType {
if priorityOwnRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getPriorityOther() -> PriorityType {
if priorityOtherRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getSectionFieldName() -> String? {
return nil
}
func getTask() -> Task {
return (task != nil ? task : tasks.first)!
}
}
I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!
This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.
List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).
I have a class named UserManager.
public class UserManager{
static let sharedInstance = UserManager()
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, User>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> User?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> User? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = mapJSONToUser(data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
Notice that I have an object called User, and it's used everywhere in this class.
I'd like to have classes called PostManager and AdminManager, which uses the same logic as the class above.
I could simply copy and paste the code above and replace the object User with Post and Admin. But...obviously this is bad practice.
What can I do to this class so that it accepts any resource? Not just User
The most obvious way to do something like this is to embed all of the generic functionality in a generic class, then inherit your UserManager from that:
protocol Managable {
init(json:JSON)
}
public class Manager<T:Manageable> {
let center = NSNotificationCenter.defaultCenter()
let queue = NSOperationQueue.mainQueue()
var resources = Dictionary<Int, T>()
var clients = Dictionary<Int, Set<String>>()
private init(){
}
private func addToClientMap(id: Int, clientName: String){
if clients[id] == nil {
clients[id] = Set<String>()
clients[id]!.insert(clientName)
}else{
clients[id]!.insert(clientName)
}
}
func getResource(id: Int, clientName: String) -> T?{
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
return nil
}
}
func createResource(data:JSON, clientName: String) -> T? {
if let id = data["id"].int {
if let resource = resources[id] {
addToClientMap(id, clientName: clientName)
return resource
}else{
resources[id] = T(json:data) //need to make generic
addToClientMap(id, clientName: clientName)
return resources[id]
}
}
return nil
}
func releaseResource(id: Int, clientName: String){
if clients[id] != nil {
clients[id]!.remove(clientName)
if clients[id]!.count == 0 {
resources.removeValueForKey(id)
clients.removeValueForKey(id)
}
}
}
}
class User : Managable {
required init(json:JSON) {
}
}
class UserManager : Manager<User> {
static var instance = UserManager()
}
Now then, any class that implements the Manageable protocol (ie., it has an init(json:JSON) method can have a Manager class variant. Note that since a generic class can't have a static property, that's been moved into the subclass.
Given that inheritance can hide implementation details, if reference semantics are not needed then a protocol + associated type (generics) implementation using structs might be safer and arguably more "Swifty".
Define your protocol with an associated type (Swift 2.2) or type alias (Swift 2.1):
protocol Manager {
associatedtype MyManagedObject // use typealias instead for Swift 2.1
func getResource(id: Int, clientName: String) -> MyManagedObject?
func createResource(data: JSON, clientName: String) -> MyManagedObject?
func releaseResource(id: Int, clientName: String)
}
And then your implementation becomes:
public struct UserManager: Manager {
typealias MyManagedObject = User
func getResource(id: Int, clientName: String) -> User? { ... }
func createResource(data: JSON, clientName: String) -> User? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
And you can further add objects using the same protocol easily, specifying what 'MyManagedObject' should be:
public struct PostManager: Manager {
typealias MyManagedObject = Post
func getResource(id: Int, clientName: String) -> Post? { ... }
func createResource(data: JSON, clientName: String) -> Post? { ... }
func releaseResource(id: Int, clientName: String) { ... }
}
I would recommend reading up more on protocols and generics in detail (there are many examples online, Apple's documentation is a good place to start).