How do I create a lazy property without getting errors? - ios

Trying to get the hang of creating lazy class properties. Sometimes it works, other times I get errors and I'm not sure I understand when it's okey to do it and when its not. For example, I created this:
lazy var backgroundContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
Works wonders with no errors. Then I tried one that fetches core data:
lazy var fetchCoreData: [LocalDoorCoreDataObject] = { return coreDataHandler.fetchAll(fetchRequest: NSFetchRequest<LocalDoorCoreDataObject>(entityName: "LocalDoorCoreDataObject") }()
As far as I can see I use the exact same format, but I still get an error
Expected , seperator
and
Expected expression in list expressions
I gues there is some reason I'm not allowed to do it with the core dataproperty, but I'm not sure what. What am I missing?

Related

Invalid parameter not satisfying: initialSnapshot.numberOfSections == initialSections.count

Working on a new project, our team decided to use UICollectionViewDiffableDataSource to handle our collection views. It works fine, but we're getting (infrequent) crashes logged via an online tool with the message Invalid parameter not satisfying: initialSnapshot.numberOfSections == initialSections.count. We can't seem to reproduce this locally.
The crash occurs at the point where we update the data source with new data, specifically at dataSource.apply(snapshot). We're unsure as to how this could happen, as the data is always created the same.
Specifically, the unit working on this one view decided to forgo the creation of a section model and instead decided to use an Int as section identifiers, because they didn't want to use sections, just display items. That's one thing I hadn't seen before, but an Int satisfies the requirements for identifiers, so the code does compile correctly.
Here is the code:
Collection View and Data Source creation
These variables are inside the class of a UIView which is programmatically created.
var collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout())
lazy var dataSource = UICollectionViewDiffableDataSource<Int, URL>(collectionView: collectionView, cellProvider: provideCell(_:indexPath:item:))
Updating the Datasource
func updateUI() {
var snapshot = dataSource.snapshot()
snapshot.deleteAllItems()
snapshot.appendSections([0])
if let urls = viewModel?.imageUrls {
snapshot.appendItems(urls)
}
dataSource.apply(snapshot)
}
In all (production) cases I have tested, viewModel?.imageUrls is empty on the first call, then contains items on the second call and all calls after that. The number of items usually doesn't change.
I have thought about not using dataSource.snapshot() and instead creating a new one, then I also wouldn't have to call deleteAllItems() every time. But I don't want to just push this as the solution when I can't be sure whether this really fixes the issue.
Has anyone encountered an issue like this before? Is it correct to use an Int as the section identifier? What could be other causes of the crash?
My guess is that you delete only items and each time you add an additional section to your snapshot, which causes an error.
So if your Snapshot already has a "0" section, you do not need to add a new one each time.
In my projects I create a new snapshot every time:
var snapshot = NSDiffableDataSourceSnapshot<Int, URL>()
snapshot.appendSections([0])
if let urls = viewModel?.imageUrls {
snapshot.appendItems(urls, toSection: 0)
}
customDataSource.apply(snapshot, animatingDifferences: animate)
The same approach is used in WWDC videos.

Override Local Data in Siesta?

I'm having trouble successfully setting local data for Siesta in Swift. My goal is to set a UIImage for a URL locally, so that this local image can be displayed with no download time.
To do this, I'm setting the image data for the URL as such:
let resource = CustomRemoteImageView.imageCache.resource(myPhoto.url.absoluteString)
let imageData = UIImagePNGRepresentation(image)! // I've also tried putting the UIImage directly in there, because the transformation chain doesn't apply to local data, right?
let entity: Entity<Any> = Entity(content: imageData, contentType: "*/*") // I've played around with the content type too!
resource.overrideLocalData(with: entity)
I'm then using a custom Service that always tries to parse content as an Image:
private let imageTransformer =
ResponseContentTransformer
{ Image(data: $0.content)}
convenience init() {
self.init(standardTransformers: [])
configure {
$0.pipeline[PipelineStageKey.parsing].add(self.imageTransformer, contentTypes: ["*/*"])
}
}
This system is working great for all remote images, but it always seems to fail to parse this overridden local image. It seems like it's trying to parse but just fails every time.
i.e. I'm getting a Siesta.ResourceEvent of
(Siesta.ResourceEvent) $R20 = newData {
newData = network
}
but the actual .typedContent is nil.
overrideLocalData and overrideLocalContent do not interact with the pipeline at all. Siesta won’t try to parse what you pass; what you override is what your resource gets.
Furthermore, overrideLocalData and overrideLocalContent don’t fail. They always update the resource’s content. If you call those methods, the resource content will match what you passed.
So … the problem isn’t parsing. What might it be?
Entity.typedContent is a shortcut for applying as? to a resource’s entity’s content. If you're getting nil, it means that either (1) the content of the entity you passed to the overrideLocalData was nil or (2) the contextual type in which you’re calling typedContent doesn’t match the content’s actual runtime type.
What do you see if you print resource.latestData.content? That will show you what’s actually there, and will rule out type conversion issues with typedContent.
If it’s not nil, compare its value from a network request and get the types to match.
If it is nil, then either something else cleared the content or you passed nil content in the first place. Try SiestaLog.Category.enabled = .common and see if you can spot where it is or isn’t getting set to the right thing.

One To Many Relationship setter

this is my first time working with Core Data in swift. I'm really enjoying it but it's also a challenge making sure my Appdelegate saves etc.
The Problem
Basically I am creating an budgeting app. Once a budget ends I need to take the current budget and store it away into a history entity. Now I have 2 different entities that work here:
NewBudgetCreateMO and HistoryBudgetHolderMO. What should happen is that the HistoryBudgetHolder should add a budget (newBudgetCreateMO) into it's One-To-Many relationship. Here is an image of my graph and their relationship.
Now if I've set this up right I should be allow to have as many NewBudgetCreateMOs in my History as I like by adding them? The code below is the generated code for my History entity which shows that it contains an NSSet
extension HistoryBudgetHolderMO {
#nonobjc public class func fetchRequest() -> NSFetchRequest<HistoryBudgetHolderMO> {
return NSFetchRequest<HistoryBudgetHolderMO>(entityName: "HistoryBudgetHolder");
}
#NSManaged public var budgets: NSSet?
}
extension HistoryBudgetHolderMO {
#objc(addBudgetsObject:)
#NSManaged public func addToBudgets(_ value: NewBudgetCreateMO)
#objc(removeBudgetsObject:)
#NSManaged public func removeFromBudgets(_ value: NewBudgetCreateMO)
#objc(addBudgets:)
#NSManaged public func addToBudgets(_ values: NSSet)
#objc(removeBudgets:)
#NSManaged public func removeFromBudgets(_ values: NSSet)
}
So I assumed that I could just use "addToBudgets" to add a set piece of data and it does seem to work but for only one instance.
Where I'm doing the adding
So I do a fetch request on the HistoryBudgetHolderMO to see if I have any in the data base. If not then I create a new one from my App Delegate (Please NOTE: I have done the app delegate casting etc in a method above and then have passed the App Delegate and Context to this method)
private func SaveAndDeleteCurrentBudget(context : NSManagedObjectContext, appDele : AppDelegate){
let fetchHistory : NSFetchRequest<HistoryBudgetHolderMO> = HistoryBudgetHolderMO.fetchRequest()
//Saves the budget to the history budget. If we don't have oen we created one and add it to that
do{
let historyBudgets : [HistoryBudgetHolderMO] = try context.fetch(fetchHistory)
if historyBudgets.count <= 0{
let newHistoryBudget : HistoryBudgetHolderMO = HistoryBudgetHolderMO(context: context)
newHistoryBudget.addToBudgets(budgetData.first!)
print("entered new historyBudget")
}else{
historyBudgets.first!.addToBudgets(budgetData.first!)
}
appDele.saveContext()
}catch{
print("Error when looking for history fetch result")
}
//Deletes all budget data and budget entries that are currently used
for object in budgetData{
context.delete(object)
}
let fetchAllDataEntries = NSFetchRequest<NSFetchRequestResult>(entityName: "BudgetEntry")
let deleteReq = NSBatchDeleteRequest(fetchRequest: fetchAllDataEntries)
do{
try context.execute(deleteReq)
}catch{
print("Error when deleting budget entries")
}
appDele.saveContext()
}
I do the fetch request and check if a history entity is there. If not then I create a new one, add the budget entry and then save the context.
If not then I grab the first instance of the history holder (as there should only ever be one as it's just a container) and I add the budget entry and then save.
Where it gets bad
So the first time I do this and it's in state 2 I get a value of Optional(1) which means it has stored one entry of the History. However any more additions after this keep saying it's Optional(1). I've tried looking up countless solutions, tried messing around with the extensions etc. I figured this would be a simple Get/Set operation but It's just not working.
Any help with this would be greatly appreciated.
Thanks for taking the time to read this.
Your solution seems good now. I also would have suggested to get rid of the HistoryBudgetHolderMO class. May I suggest to add another field/property to the NewBudget class: a creationDate (Date type). That way you can always fetch the latest one (e.g. fetch all of them and sort by creationDate). You could als add an active/historic boolean property to mark Budgets as active/inactive. Another suggestion is try to avoid force unwrapping. Instead of writing
budgetData.first!.attributeName
try to work with the 'if let' construct
if let budget = budgetData.first {
budget.attributeName
}
Solution For Anyone Interested
As I mentioned before I'm still learning Core Data and I'm grateful for KaraBenNensi for his comment to get me thinking.
Right so there was no need for a "holder" type object. Instead what I have done is I have used the last index of my budgets. So everytime I create a new budget I simply keep them all in the array. So instead of saying:
budgetData.first!.attributeName
I now use
budgetData.last!.attributeName.
This means that my database will grow but it would have grown with the history holder anyway. Now when I want to display history I just fetch all the results from the budgetData core data model. When I want to display my actual budget I just use .last so I get the most recently created budget.
I hope this helps someone and I'm glad I could figure it out. If anyone needs help in the future just reply to this and I'll try to help (But I'm no expert!)

Casting generic array in swift cause fatal error

I have a class which acts as a BLL, wrapping a service protocol. The service protocol provides a list of SerializableObjectProtocol objects. For instance, I have User, which implements SerializedObjectProtocol.
The following function casts a SerializedObjectProtol array into a User
public func Get() -> [T]
{
let result = self._service.Get()
return result as! [T]
}
As a result, I am getting the following error:
array element cannot be bridged to Objective-C
I am aware that the code is error prone, because if the object is not T, down casting cannot happen. As a result, here is what I can verify:
T in constrained to implement SerializedObjectProtol i.e.
class DataLayer<T:SerializableObjectProtocol>
T is type User. The result is an array of user. i.e. [User]
I can get around this issue, but I have to manually cast each item. As a result, this works perfectly fine:
var returnArray = [T]()
for item in result
{
returnArray.append(item as! T)
}
return returnArray;
I have just picked up Swift for a project so I have limited experience with it. As a result, I have gone out to see if what I am trying is possible (casting array [S] to [T]). It seems that it is possible if the array is [Any].
Is this a valid operation in Swift? Or is casting this way not possible.
Generally it is not possible to cast directly between an array of Any to the type it contains, because Any has a completely different representation in memory: sizeof(Any) isn't equal to sizeof(User)! An array of 10 Anys might have a length of 320 bytes, but 10 Users only need 80 bytes, the same applies to any protocol. Conclusion: You need to cast every single item.
Maybe do it like this:
return results.map{ $0 as! User }
or if you're not sure whether every item is a User, you can only return the Users like this:
return results.flatMap{ $0 as? User }
If you're still having problems, please post some minimal code that still produces the error, it's really hard to understand what your code looks like without the actual code

iOS 8.1 Swift Dictionary Error: EXC_BAD_ACCESS (XCode 6.1)

TLDR: I'm getting an EXC_BAD_ACCESS error using swift in XCode 6.1 building for iOS8.1. I believe the issue is likely a compiler error.
I am making an app wherein the user is allowed to add (Korean) words from a dictionary (in the English sense) to a word list. I have the following classes that define a word (with associated definitions, audio files, user-interaction statistics, etc.) and a word list (with an associated list of words and methods to add/remove words to the list, alphabetize the list, etc):
class Word: NSObject, NSCoding, Printable {
var hangul = String("")
var definition = String("")
// ...
}
class WordList: NSObject, NSCoding {
var title = ""
var knownWords = Dictionary<String,String> ()
// ...
func addWordToList(wordName: String, wordDefinition: String) {
// Debug statements
println("I am WordList \"\(self.title)\" and have the following knownWords (\(self.knownWords.count) words total): ")
for (_wordName,_wordDefinition) in self.knownWords {
println("\t\"\(_wordName)\" : \(_wordDefinition)")
}
println("\nI am about to attempt to add the word \"\(wordName)\" with definition \"\(wordDefinition)\" to the above dictionary")
// Add word to wordList, including the 'let' fix
fix_EXC_BAD_ACCESS_bug()
knownWords[wordName] = wordDefinition // EXC_BAD_ACCESS
}
func fix_EXC_BAD_ACCESS_bug() {
// This empty line attempts to solve a exc_bad_access compiler bug when adding a new value to a WordList dictionary
let newDic = self.knownWords
}
// ...
}
Next I have a UITableViewController with a UISearchBar that I use to display the dictionary (again in the English sense) of words to the user. The user adds words by tapping a button (which is displaying an image) on the right of each cell, which calls the #IBAction func addWord() in the viewController:
class AddingWordsToWordList_TableViewController: UITableViewController, UISearchResultsUpdating {
var koreanDictionary = KoreanDictionary() // Custom class with method 'getWord(index: Int)' to return desired Word
var filteredSearch = [Word]()
// ...
// Add word
#IBAction func addWord(sender: UIButton) {
// Add word
if self.resultSearchController.active {
let word = filteredSearch[sender.tag]
wordList.addWordToList(word.hangul, definition: word.definition) // Enter addWordToList() here
} else {
let word = koreanDictionary.getWord(sender.tag)
wordList.addWordToList(word.hangul, definition: word.definition)
}
// Set image to gray
sender.imageView?.image = UIImage(named: "add133 gray")
// Save results
wordList.save()
// Reload table data
tableView.reloadData()
}
// ...
At first the app compiles and seems to run fine. I can add a new word list and start adding words to it, until I add the 8th word. I (always on the 8th word) get a EXC_BAD_ACCESS error in (WordList) addWordToList with the following println information:
I am WordList "School" and have the following knownWords (7 words total):
"방학" : school vacation
"대학원" : graduate school
"학년" : school year
"고등학생" : high school student
"초등학교" : elementary school
"학생" : student
"학교" : school
I am about to attempt to add the word "대학생" with definition "college student" to the above dictionary
Note that the order in which I add the words appears irrelevant (i.e. the word "college student" can be added successfully if it is one of the first 7 words). There is nowhere in the code where I explicitly change behavior based on the number of words in a word list (except to display the words in a word list as cells in a UITableView), or any dependencies that would (to my knowledge) make the 8th word a special number. And indeed this number can change by using the let hack= solution (see below) to a different number, but for a particular build is always the same number.
At this point I'm at a complete loss of what to do. I'm relatively new to swift and have spent quite a few hours looking up how to fix exc_bad_access errors. I've come to the following point:
It seems exc_bad_access errors usually mean one of three things (http://www.touch-code-magazine.com/how-to-debug-exc_bad_access/)
An object is not initialized
An object is already released
Something else that is not very likely to happen
I don't feel like either 1 or 2 can be the case for me, as right before we get the access error we can print out the contents of the dictionary in question (knownWords). However, as is always the case it is highly likely I'm missing something obvious, so please let me know if this is indeed true.
If the error is caused by 3 above ("something else") I have tried the following:
(From EXC_BAD_ACCESS on iOS 8.1 with Dictionary and EXC_BAD_ACCESS when updating Swift dictionary after using it for evaluate NSExpression solutions)
I have tried different variations of the let stupidHack = scenario, and it does sometimes change the results, but only the number of entries allowed before crashing (i.e. I can sometimes get to the ~15th/16th entry in the dictionary before the error). I have not been able to find an actual, bug-free solution using this method, however.
I have rebuilt using both Release and Debug modes; the error appears in both. Note I am only using the simulator and do not have a developer's license to try it on an actual device.
I have turned off (set to "None") compiler optimization (it was already off for the Debug build anyway).
I have tried building to iOS 8.0 with the same error results.
Any way to get around this issue is appreciated. A let hack = solution is acceptable, but preferably a solution will at least guarantee to not produce the error in the future under different build conditions.
Thanks!

Resources