Ok, just starting to look at Swift and trying to use a Coredata model and SQLfile from an existing Obj-C program.
Created new swift Coredata project
replaced the coredata model with the existing model
pointed program to sql data file
Created Swift NSMangagedObject Subclass
Run program, get an error that class types don't match.
Remove Swift NSMangedObject Subclass and created Objective-C version
Run program, no issues, fetch executes nicely.
I want to eventually rewrite the program in swift, but I'll have to deal with old SQL data files. Is there a way to get this to work without having to blend the two languages?
I have not tried this, but is this something lightweight migration would do?
-[EDIT]---
Error message is in console:
"fatal error: NSAarry element failed to match the Swift Array Element type"
fetch Code:
let request = NSFetchRequest(entityName: "Account")
var error:NSError?=nil
var fetchResults = managedObjectContext?executeFetchRequest(request, error: &error)! as [Account]
for aAccount in fetchResults {
println(aAccount.name)
}
This code does works - IF the NSManagedObject Subclass (Account) IS Obj-C
This code does NOT work - IF the NSManagedObject subclass (Account) IS Swift - This is when I get the error.
The NSManagedObject Subclass is generated through "Create NSMangedObject Subclass" in XCode.
The CoreData Model and the SQL data file are from a previous, functioning, program when 100% Obj-C.
-[EDIT 2]-------
Unless I did something wrong, it is type cased...
var fetchResults = ...(request, error: &error)! as [Account]
To test, I changed the above to:
var fetchResults = ...(request, error: &error)! as [NSManagedObject]
with this change the following works - as you would expect:
for aAccount in fetchResults {
println(aAccount.valueForKey("name")!)
}
The following DOES NOT work (added it inside the for-in loop):
if let account = aAccount as? Account {
println(account.name)
}
So, it will NOT let me cast to the Swift Account Class. I used Xcode's "Create ManagedObject Subclass" to create the Swift subclass.
-[EDIT 3]-----
XCode Version: 6.1
Account.swift file:
import Foundation
import Coredata
class Account: NSManagedObject {
#NSManaged var name: String
#NSManaged var number: String
#NSManaged var type: String
#NSManaged var status: String
}
If I create an Obj-C version of Account (Account.h and Account.m), add them to the Bridging-Header, remove the .swift version, everything works.
You need to cast the NSManagedObject in fetchedResults to your Swift class in order to access the properties of the Swift class directly.
for aAccount in fetchResults {
println(aAccount.name) // NOT GOING TO WORK
}
aAccount will be a NSManagedObject so you must cast this to your Swift class in order to access it's property via the Swift class (aAccount.name). Otherwise you have to access the property like this
aAccount.getValueForKey("Name")
Alternately try this
for managedObject in fetchResults {
if let account = managedObject as? Account {
println(account.name)
}
}
Here is one of my Swift model classes, try adding the #objc()
import Foundation
import CoreData
#objc(AssetX)
class AssetX: BaseNode {
#NSManaged var childCostTotal: NSNumber
#NSManaged var dateOfManufacture: NSDate
#NSManaged var dateOfPurchase: NSDate
#NSManaged var dateOfValuation: NSDate
#NSManaged var invoice: NSAttributedString
#NSManaged var make: String
#NSManaged var model: String
#NSManaged var photo: NSAttributedString
#NSManaged var purchaseCost: NSNumber
#NSManaged var serialNo: String
#NSManaged var valuationCost: NSNumber
#NSManaged var category: AssetCategory
}
The base class is:
import Foundation
import CoreData
#objc(BaseNode)
class BaseNode: NSManagedObject {
#NSManaged var canCollapse: NSNumber
#NSManaged var canExpand: NSNumber
#NSManaged var details: NSAttributedString
#NSManaged var displayName: String
#NSManaged var isExpanded: NSNumber
#NSManaged var isLeaf: NSNumber
#NSManaged var isSelectable: NSNumber
#NSManaged var isSpecialGroup: NSNumber
#NSManaged var reminderDate: NSDate
#NSManaged var reminderPeriod: String
#NSManaged var sortIndex: NSNumber
#NSManaged var status: NSNumber
#NSManaged var children: NSSet
#NSManaged var linkedTo: People
#NSManaged var parent: BaseNode
var found:Bool = false
}
Related
I need to write test case and in the test case, I need to mock an object of the following class and put them into an array. e.g. moviesArray = [MovieCoreData]. I am wondering how I can instantiate this?
#objc(MovieCoreData)
class MovieCoreData {
static var genre: Genre = "Comedy"
#NSManaged var actor: String
#NSManaged var released: Bool
#NSManaged var name: String?
public static func mapping() -> [FieldName: String] {
return [
"actor": "actor",
"released": "released",
"name": "name"
]
}
}
For example, I tried to write
moviesArray = [MovieCoreData(actor: "Johnny Depp", released: True, name: "Pirate Captain"]
and
moviesArray.first.actor = "Johnny Depp"
moviesArray.first.released = "true"
moviesArray.first.name = "Pirate Captain"
none of them worked, I admit I don't know much about Objective-C and NSManaged, can someone tell me how to create an array of MovieCoreData with fake data?
The #NSManaged attribute belongs to Core Data.
The class must be a subclass of NSManagedObject and instances must be created with regard to the NSManagedObjectContext to take advantage of its functionality
I have a simple Swift extension on NSManagedObject, in which I have a parametrized method for finding a single object - the signature looks like:
public class func findFirst<T:NSManagedObject>(inContext context : NSManagedObjectContext? = .None) -> T?
I'm trying to call this from Objective-C, but it seems like it cannot be seen. If I create a non-parameterized version I can see and call it just fine from Objective-C:
public class func findFirstUntypedWithPredicate(predicate:NSPredicate?, inContext context : NSManagedObjectContext? = .None) -> NSManagedObject?
Is there any way for ObjectiveC to be able to reach the parameterized version of the call?
I would use Self like so:
public class func findFirst(inContext context : NSManagedObjectContext? = .None) -> Self?
using the technique found here:
How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
However, that causes the Swift compiler to segfault when compiling the code (Xcode 6.3.1, or Xcode 6.4 beta 2).
Edit: Here's a link with the full source of the framework I'm trying to build, including bonus Swift compiler crashes caused by templated methods:
https://www.dropbox.com/s/fixaj9ygdoi4arp/KiGiCoreData.zip?dl=0
Generic methods are not visible from Objective-C. However you can use
the ideas from How to use generic types to get object with same type to define a findFirst() class method
which returns Self? (the Swift equivalent of instancetype) without
being generic:
// Used to cast `AnyObject?` to `Self?`, `T` is inferred from the context.
func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}
extension NSManagedObject
{
class func entityName() -> String {
let classString = NSStringFromClass(self)
// The entity is the last component of dot-separated class name:
let components = split(classString) { $0 == "." }
return components.last ?? classString
}
// Return any matching object, or `nil` if none exists or an error occurred
class func findFirst(context : NSManagedObjectContext, withPredicate pred : NSPredicate?) -> Self? {
let name = entityName()
let request = NSFetchRequest(entityName: name)
request.predicate = pred
var error : NSError?
let result = context.executeFetchRequest(request, error: &error)
if let objects = result {
return objcast(objects.first)
} else {
println("Fetch failed: \(error?.localizedDescription)")
return nil
}
}
}
This can be used from Swift
if let obj = YourEntity.findFirst(context, withPredicate: nil) {
// found
} else {
// not found
}
and from Objective-C:
YourEntity *obj = [YourEntity findFirst:context withPredicate:nil];
I canĀ“t get the object properties when retrieving an object from Parse Data Browser. This happened after I changed from "#NSManaged var friends" to "dynamic var friends". Even "name" show nil in User.logInWithUsernameInBackground block which is crazy because the login succeeds. The ACL for User is set to "public read".
User object:
class User : PFUser, PFSubclassing {
dynamic var friends:[User]!
dynamic var name:String!
override class func load() {
self.registerSubclass()
}
}
Retrieving the User along with the friends. println showing nil
var query = User.query()
query.includeKey("friends")
query.getObjectInBackgroundWithId(currentUser.objectId) {
(pfObject: PFObject!, error: NSError!) -> Void in
if pfObject != nil {
var user = pfObject as User
var friends = user.friends as [User]
println("friends: \(friends)") //nil
} else {
println(error)
}
}
Login. println showing nil
User.logInWithUsernameInBackground(USERNAME, password:PASSWORD) {
(user: PFUser!, error: NSError!) -> Void in
if user != nil {
println("Logged in with user: \(user.name)") //nil
} else {
println(error)
}
}
Looking a little deeper for you, it seems the hurdle is a misunderstanding of what the dynamic modifier in Swift does. Apparently, dynamic in Swift is used for Key-Value observing, not declaring a variable's accessors to be defined at runtime (what #dynamic does in Objective-C)
See this for a description of dynamic in Swift https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-XID_8
and this for the description of why #NSManaged works the way #dynamic does in Objective-C
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html#//apple_ref/doc/uid/TP40014216-CH5-XID_66
With xCode 6.1.1 I was able to get this working without the bridging header but I did need to use #NSManaged. Here's how... Just:
import Parse
at the top of the calling module. For the class declaration .swift file dynamic didn't work so I needed to use #NSManaged for the variable types to get them to link to the Parse class variables successfully. Like this:
class PSCategory : PFObject, PFSubclassing {
override class func load() {
self.registerSubclass()
}
class func parseClassName() -> String! {
return "Category"
}
#NSManaged var Name: String
}
Then in my query all the names are dynamically linked:
var query = PSCategory.query() // PFQuery(className: "Category")
query.cachePolicy = kPFCachePolicyCacheElseNetwork // kPFCachePolicyNetworkElseCache
query.maxCacheAge = 60 * 60 * 24 // One day, in seconds.
query.findObjectsInBackgroundWithBlock {
(categories: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for abstractCategory in categories {
let category = abstractCategory as PSCategory
NSLog("Category Name: %#", category.Name)
}
} else {
NSLog("Unable to retrieve categories from local cache or network")
}
}
In Objective-C I am capable of doing the following on properties:
#property (nonatomic, strong) Person *currentPerson;
Then intellisense will reveal that I can add the following method:
- (void)setCurrentPerson:(Person *)currentPerson {
...
}
When that properties method is set, that method is then called.
Is there an equivalent way of having setter methods fired in Swift? I'm noticing that there's no intellisense and adding the same method in Swift never gets called.
func setCurrentPerson(currentPerson: Person) {} // never called
You can achieve the same result by using a pseudo-private property embedded into a computed property, as follows:
class Test {
var _data: String = ""
var data: String {
get { return self._data}
set { self._data = newValue }
}
}
the _data property is still publicly accessible (there's no private/protected/private access modifier in swift yet) - that's the reason why I prefix it with an underscore, as a reminder for me that's a pseudo-private property.
With the data computed property you have getter and setter you can use to override the standard behavior.
This method is a workaround mimicking properties in objective c. willSet and didSet property observers can still be used as outlined by #gotnull in his answer.
As defined in the Swift Properties guide:
var currentPerson: Person? {
willSet(currentPerson) {
// ...
}
didSet {
// ...
}
}
Is there any equivalent to gson in Objective-C?
Thanks.
DCKeyValueObjectMapping https://github.com/dchohfi/KeyValueObjectMapping is not a JSON parser but an object-json mapper to inject NSObject properties from NSDictionary/NSArray.
I recently used Mantle which works great and is very similar to GSON (which is use for android projects)
https://github.com/Mantle/Mantle
In Objective-C the functionality of GSON is sort of built in. Say I have a class defined like so:
#interface MyModel : NSObject
#property(nonatomic,strong) NSString *name;
#property(nonatomic,strong) NSString *address;
#end
And lets say that I have a JSON object defined like so
{
"name":"marc",
"address":"1234 Some Street"
}
Then I can use AFNetowrking to get an NSDictionary of the JSON object which is pretty easy. Finally you can just do a loop like so where dict is the dictionary returned by AFNetworking parsing the JSON and self is an instance of MyModel.
for (NSString *key in dict) {
[self setObject:dict[key] forKey:key];
}
In Java GSON uses reflection to achieve the same effect as the above loop. Its just a lot easier in objective-c so no need for a library to do it. If you have nested objects maybe AFNetworking with DCKeyValueObjectMapping.
OCMapper is very similar to Gson and easy to use
https://github.com/aryaxt/OCMapper
Json
{
"firstName" : "FirstName",
"lastName" : "LastName",
"age" : 26,
"dateOfBirth" : "01/01/2013",
"address" : {
"city" : "San Diego",
"country" : "US"
},
"posts" : [
{
"title" : "Post 1 title",
"datePosted : "04/15/2013",
},
{
"title" : "Post 2 title",
"datePosted : "04/12/2013",
}
]
}
Model
#objc public class User: NSObject {
var firstName: String?
var lastName: String?
var age: NSNumber?
var dateOfBirth: NSDate?
var address: Address?
var posts: [Post]?
}
Usage Swift
let user = ObjectMapper.sharedInstance().objectFromSource(dict, toInstanceOfClass:User.self) as User
or
let User = User.objectFromDictionary(dictionary)
Usage Objective C
User *user = [[ObjectMapper sharedInstance] objectFromSource:dictionary toInstanceOfClass:User.class];
or
User *user = [User objectFromDictionary:dictionary];
At WWDC 2017, Apple has introduced the new feature in Swift to parse JSON without any pain using Swift Codable protocol
struct YourStructure: Codable {
let name: String?
let avatarUrl: URL?
private enum CodingKeys: String, CodingKey {
case name
case avatarUrl = "avatar_url"
}
}
decoder:
let decoder = JSONDecoder()
parsedData = decoder.decode(YourStructure.self, from: YourJsonData)
encode:
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(data)
more info: Encoding and Decoding Custom Types
OCMapper is the best i know and the easiest library and it have reverse mapping as well and map complex objects without the need of configuration , and work with realmObjects as well
Yes - see http://psionides.jogger.pl/2010/03/04/cocoa-json-parsing-libraries/
I think I have found few libraries which can server this purpose but most important one seems to be RestKit