Swift + CoreData +小さなファむル

画像



私の手はここでcombいおいお、そのようなスりィフトがどんな皮類の獣で、実際に䜕を食べおいたのかを調べたした。 予想どおり、これたで倚くの問題ず萜ずし穎がありたした。たあ、このSwiftを調理する方法がたったくわかりたせん。 CoreDataでこのSwiftの友達を䜜ろうずしたずきに、最倧の問題は私を埅っおいたした-物事は基本的に動䜜を拒吊したした。 豊富なグヌグルは、少なくずも良い結果に぀ながりたせんでした-情報は非垞に断片的であるか、束葉杖で叩かれたした。 したがっお、苊しみの最初の倜に、私は叀い方法でCoreDataを操䜜する際に最も愚かな゜リュヌションを䜿甚するこずを断念し、決定したした-すべおのコヌドを叀き良きObjective-Cに保存し、Swiftからすでにアクセスしたすたずえば、むンタヌフェヌスで。 しかし、魂の完党䞻矩は䌑息を䞎えず、束葉杖なしではなく、認めおいるものの、実際に行うこずができる玔粋な単䞀蚀語の決定を実装する必芁がありたした。 誰がこのプロセスに興味があるのか​​、猫をお願いしたす。 たた、途䞭でバグを収集するこずをお勧めしたすが、新しい蚀語に付属するものは私の意芋では最も䟿利ではありたせん。 おそらく私は䜕かおかしなこずをした-コメントや修正、そしおベストプラクティスの議論に感謝する。



時間を気にする人



蚘事https://github.com/KoNEW/CoreDataTest.gitを読むこずなく、ここからサンプルをすぐにダりンロヌドしお、すべお自分で吞うこずができたす。



合成䟋



䜕を遞びたすか


以䞋では、すべおの問題ず䟋の分析のために、合成プロゞェクトを䜿甚したす。叀兞的な゚ンティティ「Department」ず「Employee」を衚瀺および管理するためのアプリケヌションを䜜成したす。



さらに、次のようなフィヌルドで郚門を特城付けたす。







そしお埓業員、それぞれ







デヌタ管理者


実際、最初のステップはXcodeを開き、CoreDataずSwift蚀語セットを䜿甚しお簡単なプロゞェクトを䜜成するこずです。 この段階で行う唯䞀の線集は、アプリケヌションデリゲヌトからCoreDataのすべおの䜜業を切り取り、それを別のクラスに転送するこずです。これは、シングルトンずしお機胜したす。 私はコヌドを曞くのに慣れおいたずきにそれをやっおいたのですが、ここで繰り返したす-同時に、Swiftでシングルトンを䜜成する方法を芋るこずができたす。 すべおのクラスのプレフィックスは、以降CSCoreData + Swiftによっお䜿甚されたす。



ゞャム番号1
これがXcode 6 Betaのバグなのか機胜なのかはわかりたせんが、毎回曞き蟌たないように、独自のクラスのプレフィックスは、手動で蚭定する必芁がありたす。 プロゞェクトファむルを遞択するず、[ファむルむンスペクタヌ]タブでこれを実行できたす。




それで、私たちは䜕をしたすか





その結果、AppDelegate.swiftファむルは次のようになりたす。



import UIKit import CoreData @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { // Override point for customization after application launch. return true } }
      
      





たた、CSDataManager.swftファむルは次のずおりです。

 import UIKit import Foundation import CoreData let kCSErrorDomain = "ru.novilab-mobile.cstest" let kCSErrorLocalStorageCode = -1000 @objc(CSDataManager) class CSDataManager:NSObject { //Managed Model var _managedModel: NSManagedObjectModel? var managedModel: NSManagedObjectModel{ if !_managedModel{ _managedModel = NSManagedObjectModel.mergedModelFromBundles(nil) } return _managedModel! } //Store coordinator var _storeCoordinator: NSPersistentStoreCoordinator? var storeCoordinator: NSPersistentStoreCoordinator{ if !_storeCoordinator{ let _storeURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent("CSDataStorage.sqlite") _storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedModel) func addStore() -> NSError?{ var result: NSError? = nil if _storeCoordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: _storeURL, options: nil, error: &result) == nil{ println("Create persistent store error occurred: \(result?.userInfo)") } return result } var error = addStore() if error != nil{ println("Store scheme error. Will remove store and try again. TODO: add scheme migration.") NSFileManager.defaultManager().removeItemAtURL(_storeURL, error: nil) error = addStore() if error{ println("Unresolved critical error with persistent store: \(error?.userInfo)") abort() } } } return _storeCoordinator! } //Managed Context var _managedContext: NSManagedObjectContext? = nil var managedContext: NSManagedObjectContext { if !_managedContext { let coordinator = self.storeCoordinator if coordinator != nil { _managedContext = NSManagedObjectContext() _managedContext!.persistentStoreCoordinator = coordinator } } return _managedContext! } //Init init() { super.init() NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil) } @objc(appDidEnterBackground) func appDidEnterBackground(){ var (result:Bool, error:NSError?) = self.saveContext() if error != nil{ println("Application did not save data with reason: \(error?.userInfo)") } } // Returns the URL to the application's Documents directory. var applicationDocumentsDirectory: NSURL { let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) return urls[urls.endIndex-1] as NSURL } //Save context func saveContext() -> (Bool, NSError?){ println("Will save") var error: NSError? = nil var result: Bool = false let context = self.managedContext if context != nil{ if context.hasChanges && !context.save(&error){ println("Save context error occurred: \(error?.userInfo)") }else{ result = true } }else{ let errorCode = kCSErrorLocalStorageCode let userInfo = [NSLocalizedDescriptionKey : "Managed context is nil"] error = NSError.errorWithDomain(kCSErrorDomain, code: errorCode, userInfo: userInfo) } return (result, error) } //Singleton Instance class func sharedInstance() -> CSDataManager{ struct wrapper{ static var shared_instance: CSDataManager? = nil static var token: dispatch_once_t = 0 } dispatch_once(&wrapper.token, {wrapper.shared_instance = CSDataManager()}) return wrapper.shared_instance! } }
      
      







基瀎は、Xcodeを生成したコヌドによっお自動的に採甚されたした。぀たり、ある皋床は参照ず芋なすこずができたす。 蚀語トレヌニングの面で興味深いこずから、このファむルでは私が自分で遞びたす。







ゞャム番号2
プロパティを操䜜する-私は本圓に奜きではありたせん。 この䟋は、Xcode自䜓がデフォルトで提䟛するものに基づいおいたす-したがっお、これは既存の゜リュヌションの䞭で最高のものであるず結論付けたす。 私はそれが特に奜きではありたせん-ストレヌゞ甚の内郚倉数を盎接宣蚀する必芁がありたす以前は内郚で機胜しおいたした。 さらに、先頭に䞋線が付いおいるにもかかわらず、倉数自䜓は倖郚から芋えるたたです。実際、ファむルむンスペクタヌでは、タスクごずに2぀のプロパティが衚瀺されおいたす。 私が奜きではない合蚈

  • そのような問題を解決するためにプロパティを明瀺的に耇補する必芁
  • 内郚倉数のみを䜜成できない
  • 内郚プロパティを䜜成できないこず-調達ファむルではなく、実装ファむル内でプロパティを定矩するこずを決定する前




画像








ゞャム番号3
シングルトンパタヌンは、内郚構造を䜿甚した堅い束葉杖によっお実装されたす。 理論的には、これは蚀語仕様で宣蚀されおいるクラス倉数クラスvarを䜿甚しお簡単な方法で解決する必芁がありたすが、実際にはコンパむラはただサポヌトされおいたせん。 悲しみ、悲しみ-私たちは修正を埅っおいたす。 たた、以前のObjective-Cず比范しお蚀語の珟圚のバヌゞョンでは、クラス初期化子をプラむベヌトメ゜ッドずしお指定するこずはできたせん。その結果、玔粋なバカに匷いシングルトンを䜜成するこずはただできたせん。




Jamb番号4たたは機胜、わかりたせん
NSNotificationCenterの呌び出しがどのように機胜するかに泚意を払う䟡倀もありたす。 1぀の簡単なポむントがありたす。 Appleは、すべおのシステムラむブラリUIKit、Foundation、CoreDataなどは既にSwiftず完党に友奜関係にあるず曞いおいたす。 しかし、実際にはこれはたったく真実ではありたせんが、そうではありたせんが、完党ではありたせん。 ぀たり、内郚的にNSNotificationCenterは玔粋なObjective-Cで実行され、ほずんどの堎合、コヌド内の他のすべおず互換性がありたす。 このため、そのアプリケヌションにはいく぀かのニュアンスず制限がありたす。



モヌメントワン


コヌドがObjective-C呌び出しで適切に機胜するためには、互換性を持たせる必芁がありたす。ここでは、䞀般に、すべおが指瀺に埓っおいたす。 @objc()



マゞック属性をクラス名ず必芁なメ゜ッドに远加したす-たずえば、これは䞀郚です



 @objc(CSDataManager) class CSDataManager:NSObject { ... @objc(appDidEnterBackground) func appDidEnterBackground(){ ...
      
      







モヌメント2


通知センタヌからの呌び出しをsaveContextメ゜ッド自䜓に結び付けるこずは論理的ですが、Tupleが返されるため、これを行うこずはできたせん。そのような構造はObjective-Cで定矩されおいたせん。 このため、単玔なvoid



メ゜ッド呌び出しで束葉杖を䜿甚したす。 原則ずしお、すべおが犅です。 しかし、補品を蚭蚈するずきの頭の䞭では、このようなこずを心に留めおおく䟡倀がありたす。





デヌタモデルを䜜成する


ここでは、すべおが非垞に暙準的なXcodeツヌルであり、デヌタモデルを䜜成したす-最終的にはそのようなものが埗られたす。



画像



実際に問題



そしお、䜕が問題なのか。 NSManagedObjectからの継承クラスのコヌド生成は、Xcode 6-Betaでは壊れおいたす。 より正確には、コヌドはSwiftではなくObjective-Cで生成されたすが、これは䞀般に䞀般的なものではありたせん。

それで、簡朔に、ここでの解決策は䜕ですか







指瀺に埓う



最初に「郚門」の1぀の゚ッセンスだけで䜜業するこずを考えおみたしょう。少し埌で関係に戻りたす。 そのため、Appleの指瀺に埓っお、CSDepartmentクラスを説明するためにこのファむルにアクセスしたす。



 import Foundation import CoreData import UIKit class CSDepartment : NSManagedObject{ @NSManaged var title: NSString @NSManaged var internalID: NSNumber @NSManaged var phone: NSString? }
      
      







そしお、簡単にするためにAppDelegateに残したコヌドを䜿甚しおすべおの䜜業を確認したす埌で、Bolで正しいバヌゞョンが倉曎されたす。



 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { //Get manager let manager = CSDataManager.sharedInstance() //Create new department let newDepartment : AnyObject! = NSEntityDescription.insertNewObjectForEntityForName("CSDepartment", inManagedObjectContext: manager.managedContext) //Save context manager.saveContext() //Get and print all departments let request = NSFetchRequest(entityName: "CSDepartment") let departments = manager.managedContext.executeFetchRequest(request, error: nil) println("Departments: \(departments)") return true }
      
      







始めお、ログを芋お、悲しいです。 重芁なポむント







ファむルに入れたす


ネットワヌクをさたよい、コヌドを機胜させる䞀連のゞェスチャヌを芋぀けたした。 それで、あなたがする必芁があるこず



ステップ1. Objective-Cず互換性がありたす。


内郚のどこかで、クリヌンなObjective-C呌び出しを再床実行しおいるため、新しいクラスをObjective-C呌び出しず互換性を持たせる必芁がありたす。 @objc()



ディレクティブにより、すでに通垞の方法で実行しおいたす



ステップ2.モデルファむルを研磚したす。


これは明らかなステップではありたせん。モデルのファむルを再床遞択し、゚ンティティの衚瀺に䜿甚するクラスをモデルの構成で手䜜業で登録する必芁がありたす。



画像



ステップ3.化粧品。


前の2぀のステップの埌、すべおが機胜するはずですが、awakeFromInsertメ゜ッドを远加したしたが、これも機胜するようになりたした。 たた、より矎しく理解しやすいデヌタ行がログに衚瀺されるように、説明メ゜ッドも远加したした。



その結果、クラスのコヌドは次のようになり始めたした。



 import Foundation import CoreData import UIKit @objc(CSDepartment) class CSDepartment : NSManagedObject{ @NSManaged var title: NSString @NSManaged var internalID: NSNumber @NSManaged var phone: NSString? override func awakeFromInsert() { self.title = "New department" self.internalID = 0 } func description() -> NSString{ return "Department: className=\(self.dynamicType.description()), title=\(self.title), id=[\(self.internalID)] and phone=\(self.phone)" } }
      
      







テストを再床実行したす-すべおが機胜し、喜んでいただけたす。



関係を扱う



したがっお、些现な゚ンティティが敎理されおいたす。 同様に、 CSEmployee



゚ンティティの説明を䜜成できたす。 システムに゚ンティティを正しく機胜させるために-接続を远加および削陀できるようにするために、あず1぀だけが残っおいたす。 郚門ず埓業員ずの関係は1察倚です。 ここで、新しい蚀語ずXcodeは2぀の方法で動䜜したした。

埓業員から郚門ぞの通信を実装するために、すべおが簡単であるこずが刀明したした-郚門自䜓を指すプロパティのリストに別のものを远加するだけです。 合蚈するず、埓業員クラスは次のようになり始めたした自分のグロヌバル配列から姓ず名のランダム生成を远加したした。



 import Foundation import CoreData let st_fNames = ["John", "David", "Michael", "Bob"] let st_lNames = ["Lim", "Jobs", "Kyler"] @objc(CSEmployee) class CSEmployee:NSManagedObject{ @NSManaged var firstName: NSString @NSManaged var lastName: NSString @NSManaged var age: NSNumber? @NSManaged var department: CSDepartment override func awakeFromInsert() { super.awakeFromInsert() self.firstName = st_fNames[Int(arc4random_uniform(UInt32(st_fNames.count)))] self.lastName = st_lNames[Int(arc4random_uniform(UInt32(st_lNames.count)))] } func description() -> NSString{ return "Employee: name= \(self.firstName) \(self.lastName), age=\(self.age) years" } }
      
      







しかし、郚門の偎でメカニズムのサポヌトを実装するために、私はファむルをより匷く手に入れなければなりたせんでした-再び、壊れたコヌド生成のために、子を远加するための魔法のメ゜ッドは䜜成されたせんでした。 合蚈で次のこずを行いたす。







その結果、クラスは次のようになり始めたした。



 import Foundation import CoreData @objc(CSDepartment) class CSDepartment : NSManagedObject{ @NSManaged var title: NSString @NSManaged var internalID: NSNumber @NSManaged var phone: NSString? @NSManaged var employees: NSSet override func awakeFromInsert() { self.title = "New department" self.internalID = 0 } func description() -> NSString{ let employeesDescription = self.employees.allObjects.map({employee in employee.description()}) return "Department: title=\(self.title), id=[\(self.internalID)], phone=\(self.phone) and employees = \(employeesDescription)" } //Working with Employees func addEmployeesObject(employee: CSEmployee?){ let set:NSSet = NSSet(object: employee) self.addEmployees(set) } func removeEmployeesObject(employee: CSEmployee?){ let set:NSSet = NSSet(object: employee) self.removeEmployees(set) } func addEmployees(employees: NSSet?){ self.willChangeValueForKey("employees", withSetMutation: NSKeyValueSetMutationKind.UnionSetMutation, usingObjects: employees) self.primitiveValueForKey("employees").unionSet(employees) self.didChangeValueForKey("employees", withSetMutation: NSKeyValueSetMutationKind.UnionSetMutation, usingObjects: employees) } func removeEmployees(employees: NSSet?){ self.willChangeValueForKey("employess", withSetMutation: NSKeyValueSetMutationKind.MinusSetMutation, usingObjects: employees) self.primitiveValueForKey("employees").minusSet(employees) self.didChangeValueForKey("employees", withSetMutation: NSKeyValueSetMutationKind.MinusSetMutation, usingObjects: employees) } }
      
      







最終確認コヌドず残りの問題



最終的に、次の調敎が行われたした。





したがっお、結果のAppDelegateコヌド



 import UIKit import CoreData @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { //Get manager let manager = CSDataManager.sharedInstance() //Testing insert new objects let newDepartment : CSDepartment = NSEntityDescription.insertNewObjectForEntityForName("CSDepartment", inManagedObjectContext: manager.managedContext) as CSDepartment let newEmployee: CSEmployee = NSEntityDescription.insertNewObjectForEntityForName("CSEmployee", inManagedObjectContext: manager.managedContext) as CSEmployee let newEmployee2: CSEmployee = NSEntityDescription.insertNewObjectForEntityForName("CSEmployee", inManagedObjectContext: manager.managedContext) as CSEmployee newEmployee.department = newDepartment newDepartment.addEmployeesObject(newEmployee2) manager.saveContext() //Get and print all departments println("Have add oen department and two employees") println("Departments: \(manager.departments())") println("Employees: \(manager.employees())") //Testing remove child object newDepartment.removeEmployeesObject(newEmployee2) manager.saveContext() println("Have delete one employee") println("Departments: \(manager.departments())") //Testing cascade remove manager.managedContext.deleteObject(newDepartment) manager.saveContext() println("\nHave delete department") println("Departments: \(manager.departments())") println("Employees: \(manager.employees())") //Uncomment to remove all records // let departments = manager.departments() // for i in 0..departments.count{ // let dep = departments[i] as CSDepartment // manager.managedContext.deleteObject(dep) // } // let employees = manager.employees() // for i in 0..employees.count{ // let emp = employees[i] as CSEmployee // manager.managedContext.deleteObject(emp) // } // manager.saveContext() // println("\nHave delete all data") // println("Departments: \(manager.departments())") // println("Employees: \(manager.employees())") return true } }
      
      







重倧なバグが芋぀かりたした-オブゞェクトのカスケヌド削陀はremoveEmployeesObject removeEmployees



発生したすがremoveEmployeesObject removeEmployees



メ゜ッドremoveEmployeesObject removeEmployees



メ゜ッドを䜿甚しお郚門から埓業員を削陀する堎合、郚門ぞのポむンタヌは子オブゞェクトでリセットされず、したがっお、オブゞェクトはストレヌゞ内のシステムによっおただ盗たれおいたす。

曎新これはバグではありたせん-Objective-Cのコヌドは同じように動䜜し、䜕かを混乱させおいるようです。 どうやら、これらの問題はアヌキテクチャのアプロヌチによっお解決する必芁がありたす。



おわりに



䞀般的に、あなたは働くこずができたすが、魂の痛みなしではできたせん。 ファむルは垞に手元に眮いおおく必芁がありたす。 サムラむの本圓の道を探しお、コメント、修正、自由な議論ができれば嬉しいです。



All Articles