i am on macOS 10.15 (bigSUr), XCode 12, objective-c not ios.
I have document based app. It has a simple object "SHGlobalAppData" (NSObject) that contains a property object of a custom class "SHSetupDataModel" (NSObject).
When loading, initWithCoder returns nil for a saved value. Why?
This is the implementation:
I use NSSecureCoding, therefore both SHSetupDataModel and SHGlobalAppData have included the appropriate class method
+ (BOOL)supportsSecureCoding { return YES;}
Saving is done within NSDocument with secure coding
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
NSKeyedArchiver* archiver=[[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
[archiver encodeObject:self.appData forKey:#"appData"]; // SHSetupDataModel is a property of appData object
//[...]
}
How saving is done
When it comes to saving, this is the code for SHGlobalAppData
- (void)encodeWithCoder:(NSCoder *)coder {
// Other properties here
if (_setupData){
// Tests
NSLog(#"%#",[_setupData className]); // returns "SHSetupDataModel"
BOOL test = [_setupData isKindOfClass:[SHSetupDataModel class]]; // returns TRUE
[coder encodeObject:_setupData forKey:#"setupData"];
}
}
The above saving runs through smoothly. Tests are fine.
How loading is done
Now when loading the saved file, the following NSDocument method is invoked:
- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)typeName error:(NSError *__autoreleasing _Nullable *)outError {
NSData* data = [[NSData alloc] initWithContentsOfURL:url];
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:outError];
[unarchiver setRequiresSecureCoding:YES];
// Load appData
SHGlobalAppData* appData = [unarchiver decodeObjectOfClass:[SHGlobalAppData class] forKey:#"appData"];
[unarchiver finishDecoding];
// [...]
}
This invokes the initWithCoder method form SHGlobalAppData - where i get a nil result
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
if ([coder containsValueForKey:#"setupData"]){
_setupData = [coder decodeObjectOfClass:[SHSetupDataModel class] forKey:#"setupData"]; // <---- This is nil. Why?
}
// [...]
}
}
Can anyone please help me why this is returning nil? Or lead me towards a more effective debugging?
The data model itself contained a NSDictionary that was not properly decoded. The solution was to analyze the outError and then step by step work through initWithCoder methods.
Finally I had to pass multiple classes in decodeObjectWithClasses:ofKey:
I have an app where i want to create a temporary cache which stores key and value.I have done the following
My code is : IN appDelegate.h
#property (strong, nonatomic) NSMutableDictionary *articleCache;
In appDelegate.m
#synthesize articleCache;
and i am calling it in viewController.m
here i need to store the data so that it is cleared only when the app is terminated and is accessible anywhere in the app otherwise.
every time i visit an article i add it to the array so that next time i wont have to fetch it from the network thereby speed up the process.
the Problem is when i set the temp NSMutableDictionary the content gets added but for checkCache.articleCache i get nil.
#define DELEGATE ((AppDelegate*)[[UIApplication sharedApplication]delegate])
this is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//[self loadfeeds];
[self.activityIndi startAnimating];
AppDelegate *checkCache = DELEGATE;
NSString *link = self.webUrl;
//check if the article is already opened and cached before
if([[checkCache.articleCache allKeys] containsObject:link])
{
NSLog(#"Key Exists");
NSString *contents = [checkCache.articleCache valueForKey:link];
[self loadDataOnView:contents];
}
else
{
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock:^{
NSLog(#"Key not Exists");
[self startParsing];
}];
}
}
In parser method at the end i do the following i.e to store the article..
but if i add it directly to the checkCache.articleCache nothing is added what should i do?? but it gets added to temp.. do i access the articleCache incorrectly??
AppDelegate *checkCache = DELEGATE;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[checkCache.articleCache setObject:Content forKey:url];
[temp setObject:Content forKey:url];
So how can i solve it??
or Suggest me how can i use NSCache for the same problem. thanks a lot.
It might be a silly question but i m quite new to ios thanks.
In App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.articleCache = [NSMutableDictionary new];
return YES;
}
When you have to set the object in cache.
AppDelegate *checkCache = DELEGATE;
[checkCache.articleCache setObject:obj forKey:#"Key1"];
To get the object back:
AppDelegate *checkCache = DELEGATE;
id obj = [checkCache.articleCache objectForKey:#"Key1"];
Though there are better ways to get this done.
Ok, strange thing occurred and I guess answer is quite simple, but I fail to figure out what's going on.
Situation is next:
I have an NSObject class called Constants.
Constants.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <GooglePlus/GooglePlus.h>
#interface Constants : NSObject
+(Constants*)shared;
#property GTLPlusPerson* googlePlusUser;
#property int profileType;
#property NSString *userName, *userLastName, *userEmail, *userGoogleId,*userProfilePicture;
#end
Constants.m
#import "Constants.h"
#implementation Constants
#synthesize profileType, userProfilePicture, userLastName,userName,userGoogleId,userEmail;
static Constants *constants = nil;
+ (Constants*)shared {
if (nil == constants) {
constants = [[Constants alloc] init];
}
return constants;
}
I use this class in order to save some static variables that I will use throughout the app.
Now, If I try and declare one of the variables like
[Constants shared].userName = #"name";
from an NSObject class method (Which I call from a ViewController), I fail to do so.
But If I declare Constant variables directly from ViewController (after viewDidLoad for example) everything works fine.
Here is the Class I try to declare variables from, but I fail (It also has singleton in it, that might be the source of the problem, but im not sure why would it)
#implementation GoogleLogin
static GoogleLogin* gLogin = nil;
+(GoogleLogin*)shared
{
if (nil == gLogin){
gLogin = [[[self class]alloc]init];
}
return gLogin;
}
-(void)getProfile
{
GTLServicePlus* plusService = [[GTLServicePlus alloc] init];
plusService.retryEnabled = YES;
[plusService setAuthorizer:[GPPSignIn sharedInstance].authentication];
GTLQueryPlus *query = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
plusService.apiVersion=#"v1";
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLPlusPerson *person,
NSError *error) {
if (error){
NSLog(#"Error while fetching user profile: %#", error);
}else{
NSLog(#"User profile information fetched OK");
[Constants shared].googlePlusUser = person;
[Constants shared].profileType = 1;
[Constants shared].userName = person.name.givenName;
[Constants shared].userLastName = person.name.familyName;
[Constants shared].userEmail = [GPPSignIn sharedInstance].authentication.userEmail;
[Constants shared].userGoogleId = person.identifier;
[Constants shared].userProfilePicture = person.image.url;
NSLog(#"%# %# %# %# %# ",person.name.givenName,person.name.familyName,[GPPSignIn sharedInstance].authentication.userEmail,person.identifier,person.image.url);
}
}];
}
and this is how I call those methods, from my ViewController:
- (IBAction)signupWithGoogle:(UIButton *)sender {
//if i call this method here, on button click, it will finish all the steps needed, except setting constant variables
[[GoogleLogin shared] googleLoginFromViewController:self];
//if I uncomment next line, username will be declared and I will be able to access it later
//[Constants shared].userName = #"Petar";
}
Can anybody figure out why is this happening and what should I do to change that?
When you define a property is strongly suggested to declare the attributes to use with it. I guess the compiler should complain about this with a message like
No 'assign', 'retain', or 'copy' attribute is specified - 'assign' is
assumed
So, use the following instead (copy semantics is fine for mutable classes).
#property (nonatomic, copy) NSString *myString;
You should also specify if the property should be accessed in a atomic or nonatomic way. If you don't specify it, the former will be applied.
Then, you are using a singleton pattern. The suggested way is to use GCD like so.
+ (ConstantsManager*)sharedManager {
static ConstantsManager *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[[self class] alloc] init];
});
return sharedManager;
}
Well you did not set your property attributes on the singleton class.
For example,
#property (nonatomic, strong, readonly) ...
Have you tried moving the property assignments out of the completionHandler? It may be that your properties are being assigned on a background thread and your view controller is not catching the assignment. An easy way to check is to override the setters and getters and put breakpoints in them to see what order they are being accessed.
1) Remove the #synthesize because it's not needed (properties will be synthesized as _property automatically)
2) Override setter & getter
-(void)setProfileType:(NSInteger)profileType {
_profileType = profileType;
}
-(NSInteger)profileType {
return _profileType;
}
3) Place breakpoints within these methods and see if the getter is being called before the setter. Alternatively, if simply moving the assignments out of the completionHandler fixes it you know you have some concurrency issues.
I suggest reading up on atomic/nonatomic properties, #synthesize and Objective-C concurrency.
So right now I'm working on sending the match data in a turn based game and I was using this post as a reference.
Good practices for Game Center matchData
I created a new class and it implements NSCoding. It currently only holds one variable for a NSString. This is the code for when I send the match data.
self.game.status = #"Test";
NSData *updatedMatchData = [NSKeyedArchiver archivedDataWithRootObject:self.game];
[self.currentMatch endTurnWithNextParticipants:[NSArray arrayWithObject:nextPerson]
turnTimeout:1000
matchData:updatedMatchData
completionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
}
}];
NSLog(#"Successfully ended turn");
}
When I try retrieving the match data, I tried this.
[match loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error) {
if (matchData)
{
RaceGame *updatedGame = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
NSLog(#"Match Data: %#", updatedGame.status); //prints null
callback(matchData);
}
}];
However, status is null. I've checked that match isn't null either. I also printed out the match and it said that matchData.length = 135, but I kept changing things around and it was still 135 so I'm not sure if that's helpful.
Any ideas on why status isn't changing?
--EDIT--
.m
#implementation RaceGame
#synthesize status;
#pragma mark - NSCoding protocol
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:status forKey:#"status"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.status = [aDecoder decodeObjectForKey:#"status"];
}
return self;
}
#end
.h
#interface RaceGame : NSObject <NSCoding> {
NSString *status;
}
/* Match Data */
#property (nonatomic, copy) NSString *status;
#end
Never mind, really stupid mistake by me. I was testing it on two devices and I only ran the updated version on one of the devices.
I am trying to use BNHtmlPdfKit to save some HTML as a PDF. Just to see if it works, I'm trying to take a web page and write it to a PDF. I'm unable to get it to work (at all). Below is my code.
First, I include the delegate reference:
#interface PPToolsTableViewController () <BNHtmlPdfKitDelegate>
Then I do the following:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Exported.pdf"]];
BNHtmlPdfKit *htmlPdfKit = [[BNHtmlPdfKit alloc] init];
htmlPdfKit.delegate = self;
[htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
Nothing happens. No errors, and none of the delegate methods fire:
- (void) createPdf:(id)sender {
NSLog(#"Create PDF");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfData:(NSData *)data {
NSLog(#"PDF Save Data");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didSavePdfFile:(NSString *)file {
NSLog(#"PDF Save File");
}
- (void)htmlPdfKit:(BNHtmlPdfKit *)htmlPdfKit didFailWithError:(NSError *)error {
NSLog(#"PDF Error");
}
Is anyone familiar with this library able to provide me with a working example? Or perhaps spot what's wrong with what I'm doing here? Thanks in advance.
I finally found this little snippet in the documentation which led me to a solution:
Be sure to retain a reference to the BNHtmlPdfKit object outside the
scope of the calling method. Otherwise, no delegate methods will be
called...
So making the BNHtmlPdfKit object a #property made all the difference:
#property (strong, nonatomic) BNHtmlPdfKit *htmlPdfKit;
...then this worked:
NSString *exportsPath = [[PPHelpers documentsPath] stringByAppendingPathComponent:[NSString stringWithFormat:#"exports/Logbook.pdf"]];
self.htmlPdfKit = [[BNHtmlPdfKit alloc] init];
self.htmlPdfKit.delegate = self;
[self.htmlPdfKit saveUrlAsPdf:[NSURL URLWithString:#"http://google.com"] toFile:exportsPath];
All better now. :)