How to update NSKeyedUnarchiver unarchiveObjectWithData to NSKeyedUnarchiver unarchivedObjectOfClass:[fromData:error: - ios

My app currently uses this deprecated function:
id unarchivedObject=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns TRUE when reading existing user data.
}
To update, I've converted to this:
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:codedData error:nil];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns FALSE when reading existing user data.
}
The data was originally encoded like this:
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:text forKey:#"text"];
}
-(instancetype)initWithCoder:(NSCoder*)decoder{
if(self=[super init]){
text=[decoder decodeObjectForKey:#"text"];
}
What could be causing the IF statement to return FALSE using the newer code?
Please note that I am concerned primarily with reading existing data stored prior to deprecating the Archiving functions. Simply changing to the newer functions does not resolve the issue.

Interesting question! I've been supporting iOS 10.0 so I haven't encountered such issue until I saw this. I was tinkering for an hour and I successfully found the issue.
What could be causing the IF statement to return FALSE using the newer
code?
It's because your unarchivedObject object is nil!
If you use the parameter error in the new method, you would see an error like this:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only
decode classes that adopt NSSecureCoding. Class 'QTPerson' does not
adopt it." UserInfo={NSDebugDescription=This decoder will only decode
classes that adopt NSSecureCoding. Class 'QTPerson' does not adopt it.
But how do we get the correct value for this unarchivedObject and not nil? It would take a couple of steps.
First off, make your model/class conform to <NSCoding, NSSecureCoding>
Example:
QTPerson.h
#import <Foundation/Foundation.h>
#class QTPerson;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Object interfaces
#interface QTPerson : NSObject <NSCoding, NSSecureCoding>
#property (nonatomic, copy) NSString *text;
#end
NS_ASSUME_NONNULL_END
And then implement the protocol methods:
QTPerson.m
#import "QTPerson.h"
#implementation QTPerson
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_text forKey:#"text"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_text = [coder decodeObjectOfClass:[NSString class] forKey:#"text"];
}
return self;
}
#end
And then when archiving an object, you would want to pass YES to the parameter requiringSecureCoding, like so:
QTPerson *person = [[QTPerson alloc] init];
person.text = #"Glenn";
NSData *codedData1 = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setValue:codedData1 forKey:#"boom"];
Lastly, when unarchiving, just do what you did correctly, like so:
NSData *codedData = [[NSUserDefaults standardUserDefaults] dataForKey:#"boom"];
NSError *er;
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[QTPerson class] fromData:codedData error:&er];
if([unarchivedObject isKindOfClass:[QTPerson class]]){
NSLog(#"TRUE!");
} else {
NSLog(#"FALSE!");
}
Voila! You'll get nonnull object unarchivedObject, hence the TRUE/YES value you're looking for!

Related

NSSecureCoding returns nil for properly saved object

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:

store key/value in NSDictionary/NSCache which retains data till the app is terminated

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.

Unable to access static variables from NSObject class but I am able from UIViewController

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.

iOS Game Center unable to send match data with NSCoding

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.

BNHtmlPdfKit Code Not Doing Anything on iOS

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. :)

Resources