Before I learned about categories, I had a declared a method for my entity in the generated NSManagedObject Subclass:
// UserRecording.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import <AVFoundation/AVFoundation.h>
#interface UserRecording : NSManagedObject {
AVAudioPlayer *audioPlayer;
}
#property (nonatomic, retain) NSDate * dateCreated;
#property (nonatomic, retain) NSData * audioData;
#property (nonatomic, retain) NSString * name;
-(void) URplay;
#end
and here's the implementation:
// UserRecording.m
#import "UserRecording.h"
#implementation UserRecording
#dynamic dateCreated;
#dynamic audioData;
#dynamic name;
-(void) URplay {
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithData:self.audioData error:&error];
[audioPlayer play];
}
#end
When I learned about categories (via the Stanford iTunes U videos), I moved the code to a category. But the sound stopped playing. The only difference was that there was no declared instance variable (IVAR). Indeed I tested it in my old code. The above code plays audio, but this code doesnt (in the Simulator):
// UserRecording.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface UserRecording : NSManagedObject
#property (nonatomic, retain) NSDate * dateCreated;
#property (nonatomic, retain) NSData * audioData;
#property (nonatomic, retain) NSString * name;
-(void) URplay;
#end
and the implementation:
// UserRecording.m
#import "UserRecording.h"
#import <AVFoundation/AVFoundation.h>
#implementation UserRecording
#dynamic dateCreated;
#dynamic audioData;
#dynamic name;
-(void) URplay {
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithData:self.audioData error:&error];
[audioPlayer play];
}
#end
Maybe it have something to do with ARC? But regardless, what can I do? You can't declare an instance variable in a category so that won't do.
I'm paraphrasing what I learned on the Apple Developer Forums:
Due to ARC, local variables will be deallocated when they leave scope. So the AVAudioPlayer declared in the method will vanish before it gets a chance to play. While adding an IVAR is possible in some contexts, here it isn't. The reason is that you cannot add IVARs to categories. To elaborate, the above NSManagedObject subclass is generated by XCode - if I added an IVAR to the entity, it would get overwritten by XCode when I changed the entity & regenerated.
Therefore, the solution is to rethink where my persistent AVAudioPlayer should go. I'll probably add a persistent AVAudioPlayer* as an argument to the category's method and pass it along when the method gets called.
It has to do with ARC in the sense that your AVAudioPlayer is getting deallocated right after you instantiate it, because with ARC local variables are deallocated when you leave the scope, try declaring a property for it, you don't need an ivar per se:
In header file or interface declaration in the implementation:
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
In implementation:
#synthesize audioPlayer = _audioPlayer;
This last part implicitly creates an ivar _audioPlayer for your property. You can access it directly, but I'd suggest still going throw the setter self.audioPlayer =.
Related
I'm a novice working through an old Objective C XCode project, when I noticed some unexpected behavior: a singleton behaves as expected when I run the project on an iPhone XSMax from XCode. It behaves differently when I run it on the iPhone without Xcode.
In this radio station app, I'm using the Basemodel Cocoapod (https://github.com/nicklockwood/BaseModel) as the basis for a singleton to store and persist favorite songs the user chooses as they listen to the radio. Each favorite item consists of several properties of strings, including track name, title, album, including one NSDictionary item that contains four value/key pairs for some html about the artist.
The user presses a button on a VC to save the current track info (trackInfoSRK) into a mutable array of other favorite tracks, that's then saved.
//FavoriteItem.h
#import <Foundation/Foundation.h>
#import "BaseModel.h"
#interface FavoriteItem : BaseModel;
#property (nonatomic, retain) NSDate *time;
#property (nonatomic, retain) NSTimeZone *listenedTimeZone;
#property (nonatomic, retain) NSString *artist;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *album;
#property (nonatomic, retain) NSString *year;
#property (nonatomic, retain) NSString *duration;
#property (nonatomic, retain) NSURL *albumURL;
#property (nonatomic, retain) UIImage *albumImg;
#property BOOL albumErr;
#property BOOL albumLoaded;
#property(nonatomic, retain) NSMutableDictionary *trackInfo;
#end
//FavoriteItem.m
#import <Foundation/Foundation.h>
#import "FavoriteItem.h"
#import "FavoriteList.h"
#implementation FavoriteItem
- (id)init
{
self = [super init];
if (self) {
self.albumErr=FALSE;
self.albumLoaded=FALSE;
}
return self;
}
- (BOOL)save
{
//save the favorites list
return [[FavoriteList sharedInstance] save];
}
#end
//FavoriteList.h
#interface FavoriteList : BaseModel
#property (nonatomic, strong) NSMutableArray *favorites;
#end
//FavoriteList.m
#import "FavoriteList.h"
#import "FavoriteItem.h"
#implementation FavoriteList
- (void)setUp
{
self.favorites = [NSMutableArray array];
}
+ (BMFileFormat)saveFormat
{
//return BMFileFormatHRCodedJSON;
return BMFileFormatHRCodedXML;
}
#end
When the properties are set in a VC:
//trackInfoSRK is currently playing trackInfo dict, and it set up in viewDidLoad:
trackInfoSRK = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"", #"PAArtistHTML",
#"", #"PAArtistLink",
#"", #"lastFMArtistHTML",
#"", #"lastFMArtistLink",nil];
//trackInfoSRK is populated along the way. Then when it's favorited:
FavoriteItem *thisFavorite =[[FavoriteItem alloc] init];
thisFavorite.artist = artistName;
thisFavorite.title = songTitle;
.
.
.
thisFavorite.trackInfo = trackInfoSRK;
[[FavoriteList sharedInstance].favorites insertObject:thisFavorite atIndex:0];
NSLog(#"Adding Favorite at index 0");
BOOL saveGood=[[FavoriteList sharedInstance] save];
This works just fine when the app is running on an iPhone XSMax launched from Xcode. However, when the app run by itself on the phone, any favorite and the contained properties are saved to the mutable array are saved properly except for the dictionary trackInfo property: the trackInfo for any new favorite made will instantly show the trackInfo for the currently playing track, trackInfoSRK (all the rest of the string properties are fine).
It's like the favorited track's trackInfo becomes a pointer to the variable it was being set to from the currently playing track, and it will always mirror that until the app is restarted (at which point the favorite is frozen to the last trackInfoSRK it had in this "mirroring" state.
Why would any behavior change from running it in Xcode on the device from running it standalone on the device?
What should I be looking for in this sense that would tell me how to fix this? I feel like I'm missing something Big But Simple here.
I'm using Xcode 10.2.1 with SDK 12.2. Deployment Target is iOS 11.0.
Thanks in advance!
i successfully integrated core data in my JSQ project, for my JSQMessageData i use NSManagedObject i created called CDChatMessage
#interface CDChatMessage : NSManagedObject < JSQMessageData >
#end
at my JSQMessagesViewController i use NSfetchedresultsController,
it works fine for text messages but i can't figure out how to implement media messages.
JSQMessage.h have a property that represent the Media Data
#property (copy, nonatomic, readonly) id< JSQMessageMediaData > media;
but obviously i cant assassin property of type JSQMessageMediaData to my NSManagedObject,
anyone have a solution for using JSQMessageMediaData with Core Data ?
thanks.
Basically what I've done to solve this kind of issue is this:
Instead of using CoreData object which conforms to JSQMessageData I use something called viewModel.
A ViewModel is basically a normal NSObject which just unwraps all necessary information from the CoreData object and conforms to JSQMessageData protocol - providing text, senderId, and other information (and also media message if necessary)
#interface ChatMessageViewModel : NSObject <JSQMessageData>
#property (nonatomic, strong, readonly) CDChatMessage *chatMessage;
// main properties
#property (nonatomic, copy, readonly) NSString *text;
#property (nonatomic, copy, readonly) NSString *senderId;
#property (nonatomic, copy, readonly) NSString *watcherId;
...
#property (nonatomic, strong, readonly) JSQMessage *mediaMessage;
- (instancetype)initWithChatMessage:(CDChatMessage *)chatMessage;
#end
.m file could look like this:
#interface ChatMessageViewModel ()
#property (nonatomic, strong, readwrite) CDChatMessage *chatMessage;
// main properties
#property (nonatomic, copy, readwrite) NSString *text;
#property (nonatomic, copy, readwrite) NSString *senderId;
#property (nonatomic, copy, readwrite) NSString *watcherId;
...
#property (nonatomic, strong, readwrite) JSQMessage *mediaMessage;
#end
#implementation ChatMessageViewModel
- (instancetype)initWithChatMessage:(CDChatMessage *)chatMessage
if (self = [super init]) {
_chatMessage = chatMessage;
[self unpackViewModel];
}
return self;
}
- (void)unpackViewModel {
self.senderId = self.chatMessage.senderId;
self.text = self.chatMessage.senderId;
self.mediaMessage = [self unpackMediaData];
}
- (JSQMessage *)unpackMediaData {
// Here CDCustomPhotoMediaItem is a subclass of JSQPhotoMediaItem which just lets me create custom look of JSQ media item.
JSQPhotoMediaItem *photoItem = [[CDCustomPhotoMediaItem alloc] init];
return [JSQMessage messageWithSenderId:self.senderId displayName:#"" media:photoItem];
}
After I fetch data using NSFetchResultsController I just take all core data objects and turn them into immutable viewModels.
Then in cellForItemAtIndexPath I just call this:
cell.mediaView = [viewModel.media mediaView];
This approach creates nice immutable wrapper which contains only necessary chunk of information needed by the JSQ chat library. Also, you can easily write tests for such object. If you're using swift, you can use struct for this kind of purpose.
Hope my answer helps. Please ask if you need more detailed answer. ;-)
I'm using core data in Xcode 7 beta 6 and I just generated categories and managed object subclasses for each of my entities. The issue is that when I try to utilize the properties created from the attributes in my model, I get a "use of undeclared identifier" error. I was under the impression that I was supposed to put custom behavior in the managed object subclass that were generated, however I was not clear on how I could use the properties from the categories in the managed object subclass, so I placed the custom behavior in the categories as shown below. I feel like I'm merely missing an import statement, but I'm not sure. I understand I'm using beta software.
Core Data Model:
Thought+CoreDataProperties.h:
#import "Thought.h"
NS_ASSUME_NONNULL_BEGIN
#interface Thought (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *objectId;
#property (nullable, nonatomic, retain) id recordId;
#property (nullable, nonatomic, retain) Collection *parentCollection;
#property (nullable, nonatomic, retain) NSNumber *placement;
#property (nullable, nonatomic, retain) NSString *text;
#property (nullable, nonatomic, retain) NSString *extraText; // allows for extra description text to be set. Should be in smaller print than headline text and should only appear as an option in text != nil
#property (nullable, nonatomic, retain) NSSet<Photo *> *photos;
#property (nullable, nonatomic, retain) id location; // place a CLLocation here
#property (nullable, nonatomic, retain) id tags; // place an NSArray here
#property (nullable, nonatomic, retain) NSDate *creationDate;
#pragma mark - Initializers
/*!
#abstract this method converts a CKRecord into a Thought object
#discussion parentCollection will still be nil after this method executes
*/
-(nullable instancetype) initWithRecord: (nonnull CKRecord *) record;
/*!
#abstract this method converts a CKRecord into a Thought object. photos set is not populated
*/
-(nullable instancetype)initWithRecord: (nonnull CKRecord *) record collection: (nonnull Collection *) collection;
/*!
#abstract Creates a new Thought object with generic recordId, objectId, placement, and photos array
#discussion parentCollection will still be nil after this method executes
*/
-(nullable instancetype) init;
… other methods
#end
#interface Thought (CoreDataGeneratedAccessors)
- (void)addPhotosObject:(Photo *)value;
- (void)removePhotosObject:(Photo *)value;
- (void)addPhotos:(NSSet<Photo *> *)values;
- (void)removePhotos:(NSSet<Photo *> *)values;
#end
NS_ASSUME_NONNULL_END
Thought+CoreDataProperties.m:
#import "Thought+CoreDataProperties.h"
#implementation Thought (CoreDataProperties)
#dynamic creationDate;
#dynamic extraText;
#dynamic location;
#dynamic objectId;
#dynamic placement;
#dynamic recordId;
#dynamic tags;
#dynamic text;
#dynamic parentCollection;
#dynamic photos;
-(nullable instancetype) init {
self = [super init];
if (self) {
// THIS IS WHERE I GET MANY ERROR FOR USE OF UNDECLARED IDENTIFIER
_objectId = [IdentifierCreator createId];
_recordId = [[CKRecord alloc] initWithRecordType:THOUGHT_RECORD_TYPE zoneID:[[CKRecordZone alloc] initWithZoneName:ZONE_NAME].zoneID].recordID;
_photos = [NSArray new];
_placement = [NSNumber numberWithInt:0];
_creationDate = [NSDate date];
}
return self;
}
-(instancetype) initWithRecord:(nonnull CKRecord *)record {
self = [super init];
if (self) {
_objectId = [record objectForKey:OBJECT_ID_KEY];
_recordId = [record recordID];
_text = [record objectForKey:TEXT_KEY];
_extraText = [record objectForKey:EXTRA_TEXT_KEY];
_location = [record objectForKey:LOCATION_KEY];
_photos = [NSSet new];
_tags = [record objectForKey:TAGS_KEY];
_placement = [record objectForKey:PLACEMENT_KEY];
_creationDate = record.creationDate;
}
return self;
}
-(instancetype) initWithRecord:(CKRecord *)record collection:(Collection *)collection {
self = [self initWithRecord:record];
self.parentCollection = collection;
return self;
}
Thought.h:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Frameworks.h" // includes Frameworks I'm using and some string constants
#import "ForFundamentals.h" // includes mostly string constants
#import "Photo.h"
#import "Collection.h"
#class Collection, Photo;
NS_ASSUME_NONNULL_BEGIN
#interface Thought : NSManagedObject
// I think I should put method declarations here
#end
NS_ASSUME_NONNULL_END
#import "Thought+CoreDataProperties.h"
Thought.m:
#import "Thought.h"
#import "Collection.h"
#import "Photo.h"
#implementation Thought
// I think I should put method implementations here
#end
Subclasses of NSManagedObject do their initialization in awakeFromInsert or awakeFromFetch. Don't override init or implement initWith.... You have to wait until the object is instantiated, and alive within an NSManagedObjectContext, before you can set its properties.
Don't assign collection instances to your ivars corresponding to Core Data relationships (i.e. _photos, parentCollection. Core Data will do that for you when you insert or fetch the object.
Instead of your init methods, rethink your approach. Write a method insertInManagedObjectContext:withSKRecord. That method calls -insertNewObjectForEntityForName:#"Thought" inManagedObjectContext:foo, which returns an instance of Thought. Now, with that istance, set the objecgtID, recordID, etc–but with your accessors, not by directly banging the instance variables.
So it seems that although I can't use _name = #"string" syntax to set property values, I can use method syntax, like [self setName: #"string"]. This seems very strange to me. However, the method syntax does work in both the subclass and the category so I guess problem solved… for now.
UPDATE
I didn't understand #dynamic. This post helped clear it up. I can't use _propertyName because the accessor methods are dynamically created by core data.
I would like to create an NSObject class that I can use the instance of and save to its variables and later pass its data elsewhere (NSManagedObject). Do I need to do anything else besides creating a new Object-C Class that inherits from NSObject. Create Variables in .h and synthesize in .m.
i.e.:
my .h file:
#import <Foundation/Foundation.h>
#interface MyDataClass : NSObject
#property (nonatomic, retain) NSNumber *variable1
#property (nonatomic, retain) NSString *variable2
#property (nonatomic, retain) NSDate *variable3
#end
.m file:
#import "MyDataClass.h"
#implementation MyDataClass
#synthesize variable1, variable2, variable3
#end
I would like to be able do the following in some SomeViewController:
#property (nonatomic, strong) MyDataClass *newDataClass
#systhesize newDataClass;
newDataClass.variable1 = #"123457890";
newDataClass.variable2 = #"This is the new Variable";
newDataClass.variable3 = [NSDate date];
Is there anything else I need to do to initialize each variable when an instance of this class is created? Am I missing anything?
That's all that you need for a custom object which you want to use to store data.
Of course when you go to use it (in SomeViewController), you need to actually create an instance of your class before you start setting the variables:
self.newDataClass = [[MyDataClass alloc] init];
I would also use self.newDataClass instead of just newDataClass unless you have a reason not to.
I'm new to iOS development and I've been seeing the following in several tutorials as well as when Xcode autogenerates code for me when subclassing one of my classes. Let's say I have the following .h and .m files
#import <UIKit/UIKit.h>
#interface Class : NSObject {
NSArray *_infos;
NSString *_context;
}
#property (nonatomic, retain) NSArray *infos;
#property (nonatomic, retain) NSString *context;
#end
#import "Class.h"
#implementation Class
#synthesize infos = _infos;
#synthesize context = _context;
#end
And then consider this which is how I would normally do it:
#import <UIKit/UIKit.h>
#interface Class : NSObject {
NSArray *infos;
NSString *context;
}
#property (nonatomic, retain) NSArray *infos;
#property (nonatomic, retain) NSString *context;
#end
#import "Class.h"
#implementation Class
#synthesize infos;
#synthesize context;
#end
What is the difference? From the notation I can just infer that they're just declaring the variables as private, but how does it work? If I'm correct.
It's a silly naming convention. Use it if you want to, leave it if you don't.
The advantage is that a method argument/local variable named context does not conflict with the ivar _context. It has little to do with privacy, since you can just specify #private to make the ivars private (or just #synthesize them in the first place).
The disadvantage is that you have underscores everywhere, and underscores are occasionally a bit special in C (though an underscore followed by a lowercase letter is only reserved in file scope, so you should be fine provided the ivar starts with a lowercase letter...). Where necessary, I stick an underscore at the end which feels less dirty.
Sidenote: A few people use method names beginning with an underscore to mean "private", but this convention is reserved by Apple.
The only difference is the name of the ivars ("instance variables") holding the properties.