Objective-C: work with weak references - ios

I have a few classes:
Book, Publisher, Author and Genre.
So here is the main class Book.h:
#import "Publisher.h"
#import "Author.h"
#import "Genre.h"
#interface Book : NSObject
#property (nonatomic, strong) NSString *bookName;
#property (nonatomic, strong) Author *author;
#property (nonatomic, strong) Publisher *publisher;
#property (nonatomic, weak) Genre *genre;
- (instancetype)initWithBookName:(NSString *)name andAuthorName:(NSString *)authorName
andPublisher:(NSString *)publisherName andGenreName:(__strong NSString *)genreName;
- (NSString *)description;
#end
and his implementation Book.m:
#import "Genre.h"
#import "Book.h"
#import <Foundation/Foundation.h>
#implementation Book
- (instancetype)initWithBookName:(NSString *)name andAuthorName:(NSString *)authorName
andPublisher:(NSString *)publisherName andGenreName:(__strong NSString *)genreName{
self = [super init];
if (self) {
_bookName = [name copy];
_author = [[Author alloc] initWithAuthorName:authorName];
_publisher = [[Publisher alloc] initWithPublisherName:publisherName];
_genre = [[Genre alloc] initWithGenreName:genreName];
}
return self;
}
- (instancetype)init {
return [self initWithBookName:#"unnamed" andAuthorName:#"unnamed" andPublisher:#"unnamed" andGenreName:#"unnamed"];
}
- (NSString *)description {
return [NSString stringWithFormat: #"Book: %#, Author: %#, Genre: %#", self.bookName, self.author, self.genre];
}
#end
I have delegate class - Genre, so to avoid strong reference cycles, a Book's Genre property must be weak.
At this point in the Book.m initializer:
_genre = [[Genre alloc] initWithGenreName:genreName];
it will be nil, because the Genre instance will be deallocated right after assignment.
According to Dan comment, here is my Genre.h:
#import <Foundation/Foundation.h>
#class Book;
#interface Genre : NSObject
#property (nonatomic, strong) NSString *genreName;
#property (nonatomic, strong) NSArray <Book *> *books;
- (instancetype)initWithGenreName:(NSString *)name andBooks:(NSArray <Book *>*)books;
- (instancetype)initWithGenreName:(NSString *)name;
- (NSString *)description;
#end
My question is "What is the best way to store genre object (genreName -> Genre constructor -> genre object) at weak property genre and how do I can store it without using constructor for assignment to weak property?".
SOLUTION: In my case it was collection of Genre and I take my weak property reference to one of objects from my collection.
Genre * genre1 = [[Genre alloc]initWithGenreName:#"Comedy"];
Genre * genre2 = [[Genre alloc]initWithGenreName:#"Drama"];
Genre * genre3 = [[Genre alloc]initWithGenreName:#"Fantastic"];
Genre * genre4 = [[Genre alloc]initWithGenreName:#"National"];
NSArray <Genre*> *genres = #[genre1, genre2, genre3, genre4];
Book *book1 = [[Book alloc] initWithBookName:#"Book #3!" andAuthorName:#"Grinch Burs" andPublisher:#"Ableton" andGenre:[genres objectAtIndex:0]];

The rule to remember is - strong properties increase the reference count, while weak ones do not - and when the reference count gets to 0, a proper is deallocated. So in the case of Genre - at your point in the code, there are no strong references to it so it is deallocated. The solution really is to have the Genres 'owned' by another class. This class would manage the genres, creating them and keeping strong references to them, perhaps in for eg an array of Genres. Your 'strong' genre would be passed in with the initializer, and then the weak reference is the correct approach, preventing a retain cycle, but the dealloc is prevented by the strong property that the Genre property already has - does that make sense?
In a way it makes sense to think of your objects as needing an 'owner' class, where strong references are defined that keep them alive. Then when passed to other classes like your Book class, those have weak references, which prevents the retain cycle as you say. The book class isnt the owner, but someone else is - so it doesnt go away.

One solution would be to make the genre property a strong reference.
If you really need to make Genre a weak reference,
you can solve this by storing all genres in a table and access them statically with something like this:
_genre = [Genre forName:genreName]
The static forName method would then look the correct genre up in a table of all genres. As storing the genre in a table would retain the object, it will not be released immediately on assignment.
#implementation Genre
static NSDictionary* genres;
+ (void) initGenres {
// initialize the dictionary and insert all genres
// or just initalize the dictionary and insert genres on demand
}
+ (Genre*) forName: (NSString*) genreName {
if (!genres) {
[Genre initGenres];
}
//lookup the genre in the dictionary and return it
}
#end

A weak reference does not create a reference count. If there are only weak references to an object, the object will be deallocated. That means if you want an object to stay alive, you either use a strong reference, or you store it somewhere else using a strong reference.
You use weak references to avoid reference cycles, and for objects that are held elsewhere at the moment but might disappear at some point. In your case, using a weak reference is something you won't get working properly.

Related

How to declare array in object and store with encode

I am creating a class Ticket. In that ticket I want a mutable array of NSStrings
i.e. in ticket.h
#interface Ticket : NSObject
#property NSString *ticketName;
#property NSMutableArray *games;
However Objective C doesn't allow me to do this. What am I supposed to do to have an array inside an object?
I then want to store that array using encodeWithCoder in the implementation of the object
like i said you might have a syntax problem, there is no reason why Objective-c won't allow you to add an NSMutableArray into your custom objects, try this:
#interface MyObject : NSObject
#property (nonatomic, strong) NSMutableArray *myMutableArray;
//
// .. other properties
//
#end
and in the implementation
#implementation MyObject
- (void)viewDidLoad
{
[super viewDidLoad];
[self.myMutableArray addObject:#"myString1"];
[self.myMutableArray addObject:#"myString2"];
[self.myMutableArray addObject:#"myString3"];
}

Obj C: Can't inherit public property from parent class in child class

I am practicing inheritance in Objective-C and this is my Person parent class
// This is Person.h
#interface Person : NSObject
#property(nonatomic, strong) NSNumber *age;
#property(nonatomic, strong) NSString *race;
-(instancetype)init;
-(instancetype)initWithAge:(NSNumber*)age andRace:(NSString*)race;
#end
This is what I'm trying to do in my Student class
// This is Student.h
#import "Person.h"
#interface Student : Person
#property(nonatomic, strong) NSString *classification;
#property(nonatomic, strong) NSString *major;
#end
And
// This is Student.m
#import "Student.h"
#import "Person.h"
#implementation Student
-(instancetype)init
{
return [self initWithClassification:#"Freshman" andMajor:#"Computer Science"
andAge:[[NSNumber alloc] initWithInt:20] andRace:#"Caucasian"];
}
-(instancetype)initWithClassification:(NSString*)classification andMajor:(NSString*)major
andAge:(NSNumber*)age andRace:(NSString*)race
{
self = [super init];
if (self)
{
_classification = classification;
_major = major;
_age = age;
_race = race;
}
return self;
}
#end
The compiler is not liking my doing
_age = age;
_race = race;
Use of undeclared identifier _age did you mean age? Can someone tell me where I went wrong? Thank you.
When you declare a property like that, clang will automatically #synthesize it for you (i.e it will create a getter and setter), but synthesized properties are not visible to subclasses, you have different alternatives to make it working.
You can synthesize the ivar in the interface of the subclass
#synthesize age = _age;
Or you can declare the ivar protected on the interface of the superclass, so that will be visible on the subclasses.
#interface Person : NSObject {
#protected NSNumber *_age;
}
Or you can use self.age = ... on your subclass, without using the ivar at all.
Since clang compiler now auto-synthesise properties you don't have, in most cases, to synthesise your properties.
Objective-C Autosynthesis of Properties
Clang provides support for autosynthesis of declared properties. Using
this feature, clang provides default synthesis of those properties not
declared #dynamic and not having user provided backing getter and
setter methods. __has_feature(objc_default_synthesize_properties)
checks for availability of this feature in version of clang being
used.
But in some cases (some examples are in this question) you should explicitly synthesise them.
In this case, to solve your problems you should just add:
#synthesize age = _age;
#synthesize race = _race;
to your code, and you'll be fine.
The subclass has access to the property, but not the backing variable. So you should set it with
self.age = age;

Objective C: allow properties in category via custom root class

There are many questions concerning the category-properties problem.
I know some possibilities to address this:
use a singleton registry
objc_setAssociatedObject and objc_getAssociatedObject
From my point of view both is not clean since the memory allocated is never cleared when the object that created such properties is deallocated.
Categories are a good way to keep code clean and dynamically add functionality to already existing classes. They help to group functionality and to distributed implementation work among more developers.
The bad about categories is the missing storage.
I came across this problem several times now and I'm wondering whether the following would address this problem in an clean way that also takes care about the memory and if there are any problems that I can't see right now.
There is one restriction, that I can ignore since I'm working as a framework developer: I'm able to create my own root class that all my other classes can inherit from.
First of all declare the new root object:
#interface RootObject : NSObject
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key;
- (id)runtimePropertyForKey:(id)key;
#end
With the corresponding implementation:
#import "RootObject.h"
#interface RootObject ()
#property (readwrite) NSMutableDictionary *runtimeProperties;
#end
#implementation RootObject
#synthesize runtimeProperties = _runtimeProperties;
- (id)init {
self = [super init];
if (self)
{
_runtimeProperties = [[NSMutableDictionary alloc] initWithCapacity:1];
}
return self;
}
- (void)dealloc {
[_runtimeProperties release];
_runtimeProperties = nil;
[super dealloc];
}
- (id)runtimePropertyForKey:(id)key {
return [self.runtimeProperties objectForKey:key];
}
- (void)setRuntimeProperty:(id)runtimeProperty forKey:(id<NSCopying>)key {
if (key)
{
if (runtimeProperty)
{
[self.runtimeProperties setObject:runtimeProperty forKey:key];
}
else
{
[self.runtimeProperties removeObjectForKey:key];
}
}
}
#end
By using this RootObject instead of NSObject it should be very easy to add a "property" to a category on a class. Consider having some class MyClass
#interface MyClass : RootObject
// some interface here
#end
When implementing a special behavior on top of this class you are now able to add a property like this:
#interface MyClass (specialBehavior)
#property (nonatomic, retain) NSString *name;
#property (nonatomic, copy) NSDate *birthday;
#end
With corresponding implementation:
#implementation MyClass (specialBehavior)
#dynamic name;
- (NSString *)name {
return [self runtimePropertyForKey:#"name"];
}
- (void)setName:(NSString *)name {
[self setRuntimeProperty:name forKey:#"name"];
}
#dynamic birthday;
- (NSDate *)birthday {
return [self runtimePropertyForKey:#"birthday"];
}
- (void)setBirthday:(NSDate *)birthday {
[self setRuntimeProperty:[birthday copy] forKey:#"birthday"];
}
#end
Such an implementation could KVO compatible as well by just adding the necessary calls in the setter method.
Very straight forward, but I'm wondering whether I missed something important? (E.g. very very bad runtime performance having many such declared properties or using many of these objects)
This is effectively the same as objc_setAssociatedObject and objc_getAssociatedObject, which do release memory when the object is deallocated (depending on the association type). I would guess they also have much lower overhead than your suggested code.

How to save to and read from a NSObject

I have two NSObject's.
#interface Car : NSObject{
#property (strong) NSSet *cars;
}
and
#interface Model : NSObject{
#property (strong) UIImage *picture;
#property (strong) NSString *name;
}
Basically the object Car has a NSSet cars and each object of the NSSet cars has the properties picture and name.
How can I relate this two NSObject's and how can I save a string or image to the Car NSSet using the properties of the Model NSObject. Thanks.
For easy adding or removing, your "cars" property should be declared as a NSMutableSet.
And assuming your single Car object is named "listOfCars", here is one way to do what (I think) you are trying to do:
Model * newModel = [[Model alloc] init];
if(newModel)
{
newModel.picture = [UIImage imageNamed: #"Edsel.jpg"];
newModel.name = #"Ugly Car";
[listOfCars.cars addObject: newModel];
}
And, in your Car .m file, do something like this:
- (id) init
{
self = [super init];
if(self)
{
_cars = [[NSMutableSet alloc] init];
}
return(self);
}
The init method is the only place you should be referring to the underlying variable for your "cars" property. Everywhere else it should be "listOfCars.cars" or "self.cars" if you're referring to the cars set from within the Car object.

Conditionally make a #property strong or weak

I am downloading a list of objects from an API to display to a user. The list has a mix of two types of objects. Imagine that they are combined books and authors, and the class definitions look like this:
#interface Book : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) Author *author;
#end
#interface Author : NSObject
#property (nonatomic, strong) NSString *fullName;
#property (nonatomic, weak) Book *book;
#end
Every Book can download its Author information from the API, and vice versa.
If the API gives me a Book, I can set its author property once I download it. The Author object points back to the Book through the book property, but this doesn't create an ARC Retain Cycle because the book property is weak.
However, if the API gives me an Author first, and I download its Book, the object will be deallocated once the method in which I set it returns, because the same property is weak.
I thought of a few ways around this:
Create a Content object that stores both (not viable for many-to-many relationships)
Create separate strongBook and weakBook properties, and then make a readonly property called book which checks which is set and returns that one
Those both seem messy to me, although the second option is preferable.
Is there a way to dynamically change a property from weak to strong (and vice-versa) using the Objective-C runtime?
UPDATE: I'm getting a few suggestions on how to work around the issue, which I don't have trouble coming up with myself. This question is specifically about whether there is a way to either (a) dynamically redefine #properties for a specific instance of a class, or (b) override ARC's retain/release behavior in specific circumstances (since this issue wouldn't exist in MRC).
Just a shot in the dark, but you could create the property and not specify and then use dynamic with the runtime apis. I didn't test it, but i think it should work:
//.h file
#import <Foundation/Foundation.h>
#interface SomeObject : NSObject
#property(nonatomic) NSObject *object;
#end
//.m file
#import "SomeObject.h"
#import <objc/runtime.h>
#implementation SomeObject
#dynamic object;
-(void)setObject:(NSObject *)object
{
BOOL isWeak = NO;
if(isWeak)
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
}
else
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_RETAIN);
}
}
-(NSObject *)object
{
return objc_getAssociatedObject(self, "object");
}
#end
For the period of the download, create a mutable dictionary to temporarily store author objects that arrive prior to the book. When a book is received, look in that array and see if the author info is there, if so attach it. When you are finished clean out the mutable array.

Resources