I have a small test function and I am getting memory allocation issue and memory warning after an hour of running. The problem is from the call to
NSData* data = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error1];
Below is my code.
Any helps would be appreciated.
Here is my app stat after 1 hour running:
Low: 712Kb, High 275.4 MB, Durarion 1 hour 26 min
2015-01-31 14:38:11.811 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:16.836 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:24.573 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:29.850 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:34.779 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:40.058 testMem[5268:546116] Received memory warning.
2015-01-31 14:38:44.922 testMem[5268:546116] Received memory warning.
MainView.m
#import "ViewController.h"
#import "global.h"
#import "ServiceSvc.h"
#import "ClsTickets.h"
#interface ViewController ()
{
NSTimer* g_BACKUPTIMER;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
g_BACKUPTIMER = [NSTimer scheduledTimerWithTimeInterval:.4 target:self selector:#selector(runBackupProcess) userInfo:nil repeats:YES];
}
-(void)runBackupProcess
{
if (stopTimer == 0)
{
[NSThread detachNewThreadSelector: #selector(runtest) toTarget:self withObject:nil];
}
else
{
}
}
- (void) runtest
{
#autoreleasepool {
#try
{
ServiceSvc_DoAdminUnitIpad *req2 = [[ServiceSvc_DoAdminUnitIpad alloc] init];
req2.UnitDesc = #"Unit 6";
req2.CustomerID = #"800014";
req2.MachineID = nil;
req2.UnitID = #"1";
req2.GPSData = nil;
ServiceSynchronous* synCrhous = [[ServiceSynchronous alloc] init];
NSData* data = [synCrhous makeSynchrounousCall:req2];
data = nil;
req2 = nil;
}
#catch (NSException *exception)
{
}
#finally
{
}
} // end auto
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
ServiceSvc.m
#implementation ServiceSynchronous
-(NSData*) makeSynchrounousCall:(ServiceSvc_DoAdminUnitIpad*) parameters
{
NSURLResponse* response = nil;
NSError* error1 = nil;
NSString *operationXMLString = #"<?xml version=\"1.0\"?> <soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:ServiceSvc=\"https://xxx/service.asmx\" xsl:version=\"1.0\"> <soap:Body> <ServiceSvc:DoAdminUnitIpad> <ServiceSvc:UnitID>1</ServiceSvc:UnitID> <ServiceSvc:UnitDesc>Unit 6</ServiceSvc:UnitDesc> <ServiceSvc:CustomerID>800014</ServiceSvc:CustomerID> </ServiceSvc:DoAdminUnitIpad> </soap:Body> </soap:Envelope>";
NSString *msgLength = [NSString stringWithFormat:#"%d",
[operationXMLString length]];
NSMutableURLRequest * req = [[NSMutableURLRequest alloc] init];
[req setURL:[NSURL URLWithString:#"https://xxx/service.asmx"]];
[req addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[req addValue:#"http://tempuri.org/webservice-name/method-name" forHTTPHeaderField:#"SOAPAction"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody:[operationXMLString dataUsingEncoding:NSUTF8StringEncoding]];
NSData* data = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error1];
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:req];
if (error1 == nil)
{
}
else
{
}
operationXMLString = nil;
req = nil;
response = nil;
return data;
}
#end
#implementation ServiceSvc_DoAdminUnitIpad
- (id)init
{
if((self = [super init])) {
UnitID = 0;
UnitDesc = 0;
CustomerID = 0;
MachineID = 0;
GPSData = 0;
}
return self;
}
- (NSString *)nsPrefix
{
return #"ServiceSvc";
}
- (xmlNodePtr)xmlNodeForDoc:(xmlDocPtr)doc elementName:(NSString *)elName elementNSPrefix:(NSString *)elNSPrefix
{
}
/* elements */
#synthesize UnitID;
#synthesize UnitDesc;
#synthesize CustomerID;
#synthesize MachineID;
#synthesize GPSData;
/* attributes */
- (NSDictionary *)attributes
{
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
return attributes;
}
+ (ServiceSvc_DoAdminUnitIpad *)deserializeNode:(xmlNodePtr)cur
{
ServiceSvc_DoAdminUnitIpad *newObject = [ServiceSvc_DoAdminUnitIpad new];
[newObject deserializeAttributesFromNode:cur];
[newObject deserializeElementsFromNode:cur];
return newObject;
}
- (void)deserializeAttributesFromNode:(xmlNodePtr)cur
{
}
- (void)deserializeElementsFromNode:(xmlNodePtr)cur
{
}
#end
ServiceSvc.h
#interface ServiceSvc_DoAdminUnitIpad : NSObject {
/* elements */
NSString * UnitID;
NSString * UnitDesc;
NSString * CustomerID;
NSString * MachineID;
NSString * GPSData;
/* attributes */
}
- (NSString *)nsPrefix;
- (xmlNodePtr)xmlNodeForDoc:(xmlDocPtr)doc elementName:(NSString *)elName elementNSPrefix:(NSString *)elNSPrefix;
- (void)addAttributesToNode:(xmlNodePtr)node;
- (void)addElementsToNode:(xmlNodePtr)node;
+ (ServiceSvc_DoAdminUnitIpad *)deserializeNode:(xmlNodePtr)cur;
- (void)deserializeAttributesFromNode:(xmlNodePtr)cur;
- (void)deserializeElementsFromNode:(xmlNodePtr)cur;
/* elements */
#property (retain) NSString * UnitID;
#property (retain) NSString * UnitDesc;
#property (retain) NSString * CustomerID;
#property (retain) NSString * MachineID;
#property (retain) NSString * GPSData;
/* attributes */
- (NSDictionary *)attributes;
#end
#interface ServiceSynchronous : NSObject {
}
- (NSData*) makeSynchrounousCall:(ServiceSvc_DoAdminUnitIpad*) parameters;
#end
After some research, I came to the conclusion that Apple's NSURLConnection is the issue. The problem was reported back in 2008 and Apple had acknowledged it but now it is 2015 and the problem is still there. I have switched to asynchronous call and the memory allocation issue is still there, but smaller. Those geeks at Apple are a joke.
Related
I want to use PHImagemanager to get all photos on the device.
If I set the targetsize too high the app will crash because of memory warnings. So I tested the request without any use of the returned images and set each image to nil, but still app is crashing. I don't know what I'm doing wrong. Can someone help please?
requestOptions = [[PHImageRequestOptions alloc] init];
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
requestOptions.synchronous = false;
assetsOfPhotos = [PHAsset fetchAssetsWithMediaType: PHAssetMediaTypeImage options: nil];
PHImageManager *manager = [PHImageManager defaultManager];
#autoreleasepool {
for (int i = 0; i <= totalImages - 1; i++) {
PHAsset *asset = assetsOfPhotos[i];
[manager requestImageForAsset: asset
targetSize: CGSizeMake(640, 480)
contentMode: PHImageContentModeDefault
options: requestOptions
resultHandler: ^void(UIImage *image, NSDictionary *info) {
image = nil;
}];
}
}
Setting size to 640x480 crash after about 200 images, 320x240 after about 800 images. As a 640x480 image needs 4 times memory then 320x240 image it seems that the app crashes after the same amount of memory that was allocated. So for me this means that I cannot show more images than 200 imags with 640x480 on the test device, because I cannot free allocated memory.
In order to make your #autoreleasepool work you need to set requestOptions.synchronous to YES, and use your own async queue if you want to make the request operation asynchronously.
Please use #autoreleasepool inside the for loop.
for (int i = 0; i <= totalImages - 1; i++) {
#autoreleasepool {
//Your code
}
}
If you want load all photos that you have in Photos.app and you didn't want iCloud. You can do:
That example works with a collection view.
#interface GalleryViewModel ()
#property (strong, nonatomic) NSMutableArray<PHAsset *> *assets;
#property (strong, nonatomic) PHImageManager *imageManager;
#property (strong, nonatomic) PHImageRequestOptions *requestOptions;
#property (strong, nonatomic) NSMutableArray<UIImage *> *imagesList;
#end
#implementation GalleryViewModel
- (instancetype) initWithContext:(ITXAppContext *)context {
self = [super initWithContext:context];
if (self) {
_assets = [[NSMutableArray alloc] init];
_imageManager = [PHImageManager defaultManager];
_requestOptions = [[PHImageRequestOptions alloc] init];
_imagesList = [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark - Public methods
// ==================================================================================
// Public methods
- (void) viewModelDidLoad {
[self obtainAllPhotos];
}
#pragma mark - Private methods
// ==================================================================================
// Private methods
- (void) obtainAllPhotos {
self.requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
self.requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
self.requestOptions.synchronous = YES;
self.requestOptions.networkAccessAllowed = NO;
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
__weak GalleryViewModel *weakSelf = self;
[result enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[weakSelf.assets addObject:obj];
if (idx >= ([result count] - 1)) {
[weakSelf.viewDelegate setupView];
}
}];
}
#pragma mark - Get data from object
// ==================================================================================
// Get data from object
- (NSInteger) sizeGallery {
if (self.assets) {
return [self.assets count];
}
return 0;
}
- (UIImage *) imagesFromList:(NSInteger) index {
__block UIImage *imageBlock;
[self.imageManager requestImageForAsset:[self.assets objectAtIndex:index] targetSize:CGSizeMake(200, 200) contentMode:PHImageContentModeAspectFit options:self.requestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
if (result) {
imageBlock = result;
}
}];
return imageBlock;
}
#end
The problem is that I have error when I try to press magnifying glass (lens) and in index goes to C instead of C. The written code is as follows:
///
// RBook.h
// ObjBook
//
// Created by pan on 24/08/16.
// Copyright © 2016 pan. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface RBook : NSObject
{
}
#property (nonatomic,assign) int ID;
#property (nonatomic,strong) NSString *ISBN;
#property (nonatomic,strong) NSString *Code;
#property (nonatomic,strong) NSString *RBMDate;
#property (nonatomic,strong) NSString *Category;
#property (nonatomic,strong) NSString *FTitle;
#property (nonatomic,strong) NSString *FSubTitle;
#property (nonatomic,strong) NSString *GTitle;
#property (nonatomic,strong) NSString *GSubTitle;
#property (nonatomic,strong) NSString *Authors;
#property (nonatomic,strong) NSString *Publisher;
#property (nonatomic,strong) NSString *Cover;
#property (nonatomic,strong) NSString *CovPhoto;
#property NSInteger section;
#end
//
// RBooks.m
// ObjBook
//
// Created by pan on 24/08/16.
// Copyright © 2016 pan. All rights reserved.
//
#import "RBook.h"
#implementation RBook
#synthesize ID;
#synthesize ISBN;
#synthesize Code;
#synthesize RBMDate;
#synthesize Category;
#synthesize FTitle;
#synthesize FSubTitle;
#synthesize GTitle;
#synthesize GSubTitle;
#synthesize Authors;
#synthesize Publisher;
#synthesize Cover;
#synthesize CovPhoto;
-(NSString *) getName
{
return [NSString stringWithFormat:#"%d %# %# %# %# %# %# %# %# %# %# %# %#",self.ID,self.ISBN,self.Code,self.RBMDate,self.Category,self.FTitle,self.FSubTitle,self.GTitle,self.GSubTitle,self.Authors,self.Publisher,self.Cover,self.CovPhoto];
}
#end
//
// RBooksTableViewController.h
// MyRBooks
//
// Created by pan on 30/08/16.
// Copyright © 2016 pan. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FMDBDataAccess.h"
#interface RBooksTableViewController : UITableViewController<UISearchBarDelegate , UITableViewDataSource , UITableViewDelegate> {
NSString *tID;
NSString *tFTitle;
NSString *tGTitle;
NSString *tAuthors;
NSString *tPublisher;
NSString *found;
NSString *from;
}
#property (nonatomic,strong) NSMutableArray *books;
#property (nonatomic,strong) NSMutableArray *filteredbooks;
#property (weak, nonatomic) IBOutlet UISearchBar *mysearchBar;
#property (nonatomic, assign) bool isFiltered;
//-(void) populateBooks;
#end
#import "RBooksTableViewController.h"
#import "RBooksTableViewCell.h"
#import "FMDBDataAccess.h"
#import "BooksDetailsViewController.h"
#import "RBook.h"
#implementation RBooksTableViewController
#synthesize books;
#synthesize filteredbooks;
#synthesize mysearchBar;
#synthesize isFiltered;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
mysearchBar.delegate = (id)self;
tID = #"A/A :";
tFTitle = #"FOREIGN TITLE :";
tGTitle = #"GREEK TITLE :";
tAuthors = #"AUTHORS. :";
tPublisher = #"PUBLISHERS :";
// [self populateBooks];
self.books = [NSMutableArray arrayWithCapacity:1];
NSMutableArray *booksTemp;
// Get the books array from the database
self.books = [[NSMutableArray alloc] init];
FMDBDataAccess *db = [[FMDBDataAccess alloc] init];
booksTemp = [db getBooks];
UILocalizedIndexedCollation *indexedCollation =
[UILocalizedIndexedCollation currentCollation];
// Iterate over the products, populating their section number
for (RBook *therbook in booksTemp) {
NSInteger section = [indexedCollation sectionForObject:therbook
collationStringSelector:#selector(FTitle)];
therbook.section = section;
// NSLog(#" section is %d" , section);
}
// Get the count of the number of sections
NSInteger sectionCount = [[indexedCollation sectionTitles] count];
// NSLog(#" sectionCount is %d" , sectionCount);
// Create an array to hold the sub arrays
NSMutableArray *sectionsArray = [NSMutableArray
arrayWithCapacity:sectionCount];
// Iterate over each section, creating each sub array
for (int i=0; i<=sectionCount; i++) {
NSMutableArray *singleSectionArray = [NSMutableArray
arrayWithCapacity:1];
[sectionsArray addObject:singleSectionArray];
}
// Iterate over the products putting each product into the correct sub-array
for (RBook *therbook in booksTemp) {
[(NSMutableArray *)[sectionsArray objectAtIndex:therbook.section]
addObject:therbook];
}
// Iterate over each section array to sort the items in the section
for (NSMutableArray *singleSectionArray in sectionsArray) {
// Use the UILocalizedIndexedCollation sortedArrayFromArray: method to
// sort each array
NSArray *sortedSection = [indexedCollation
sortedArrayFromArray:singleSectionArray
collationStringSelector:#selector(FTitle)];
[self.books addObject:sortedSection];
// NSLog(#" sortedCollection is %#" , sortedSection);
}
}
- (void)viewDidUnload
{
// [self setSearchBar:nil];
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if (tableView == self.tableView)
{
return [books count];
}
return 1;
}
NSIndexPath *currentSelection;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
currentSelection = indexPath;
[self showDetailsForIndexPath:indexPath];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int rowCount;
if(self.isFiltered)
{
rowCount = filteredbooks.count;
} else
rowCount = [[books objectAtIndex:section] count];
return rowCount;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
RBooksTableViewCell *cell = (RBooksTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[RBooksTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
RBook* rbook;
if(isFiltered)
rbook = [filteredbooks objectAtIndex:[indexPath row]];
else
// rbook = [books objectAtIndex:[indexPath row]];
rbook = [[books objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
// cell.textLabel.font = [UIFont systemFontOfSize: 17.0]; εχουν μπει στο RBooksTableViewCell.m
// cell.imageView.bounds = CGRectMake(15,17,80,128); εχουν μπει στο RBooksTableViewCell.m
// [[cell textLabel] setText:[NSString stringWithFormat:#"%# %#\r %# %# ", tFirstName,customer.firstName,tLastName,customer.lastName]];
// cell.IDlabel!.text = "\(books.ID_Lbl)"
[[cell textLabel] setText:[NSString stringWithFormat:#"%# %d\r %# %#\r %# %#\r %# %#\r %# %#",tID,rbook.ID,tFTitle,rbook.FTitle ,tGTitle,rbook.GTitle,tAuthors,rbook.Authors,tPublisher,rbook.Publisher]];
[[cell imageView] setImage:[UIImage imageNamed:rbook.CovPhoto]];
return cell;
}
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// create our UIImages
UIImage * defaultBG = [UIImage imageNamed:#"wood1.jpg"];
UIImage * selectedBG = [UIImage imageNamed:#"marble2.jpg"];
// Create UIImageView
UIImageView * defaultBGView = [[UIImageView alloc] initWithImage:defaultBG];
UIImageView * selectedBGView = [[UIImageView alloc] initWithImage:selectedBG];
// set the UIImageView
cell.backgroundColor = [UIColor clearColor];
cell.backgroundView = defaultBGView;
// change the background for the selectedview
cell.selectedBackgroundView = selectedBGView;
}
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[self.tableView resignFirstResponder];
}
#pragma mark - Table view delegate
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if(searchText.length==0)
{
isFiltered = FALSE;
}else
{
isFiltered = TRUE;
filteredbooks = [[NSMutableArray alloc] init];
for(RBook* rbook in books)
{
NSRange ftitleRange = [rbook.FTitle rangeOfString:searchText options:NSCaseInsensitiveSearch];
NSRange gtitleRange = [rbook.GTitle rangeOfString:searchText options:NSCaseInsensitiveSearch];
NSRange authorsRange = [rbook.Authors rangeOfString:searchText options:NSCaseInsensitiveSearch];
NSRange publisherRange = [rbook.Publisher rangeOfString:searchText options:NSCaseInsensitiveSearch];
if(ftitleRange.location != NSNotFound || gtitleRange.location != NSNotFound || authorsRange.location != NSNotFound || publisherRange.location != NSNotFound )
{
[filteredbooks addObject:rbook];
}
}
}
found = #"THEY FOUND FOR:";
from = #" / ";
self.title = [NSString stringWithFormat:#"%# %# %lu %# %lu",found , searchText ,(unsigned long)filteredbooks.count ,from , (unsigned long)books.count];
[self. tableView reloadData];
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
[self showDetailsForIndexPath:indexPath];
}
//-(void) populateBooks
//{
// self.books = [[NSMutableArray alloc] init];
//
// FMDBDataAccess *db = [[FMDBDataAccess alloc] init];
//
// self.books = [db getBooks];
//}
-(void) showDetailsForIndexPath:(NSIndexPath*)indexPath
{
[self.mysearchBar resignFirstResponder];
BooksDetailsViewController* vc = [self.storyboard instantiateViewControllerWithIdentifier:#"BooksDetailsViewController"];
RBook* rbook;
if(isFiltered)
{
rbook = [filteredbooks objectAtIndex:indexPath.row];
}
else
{
// rbook = [books objectAtIndex:indexPath.row];
rbook = [[books objectAtIndex:[indexPath section]] objectAtIndex:[indexPath row]];
}
vc.rbook = rbook;
[self.navigationController pushViewController:vc animated:true];
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section {
// Make sure that the section will contain some data
if ([[books objectAtIndex:section] count] > 0) {
// If it does, get the section title from the
// UILocalizedIndexedCollation object
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles]
objectAtIndex:section];
}
return nil;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
if (tableView == self.searchDisplayController.searchResultsTableView) {
return nil;
} else {
NSMutableArray *retval = [NSMutableArray arrayWithObject:UITableViewIndexSearch];
[retval addObjectsFromArray:[[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]];
return retval;
}
}
- (NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [[UILocalizedIndexedCollation currentCollation]
sectionForSectionIndexTitleAtIndex:index];
//}
}
#end
The problem is somewhere in RBooksTableViewController.m
I have a problem in getting a method to work, and I am totally confused.
I am unable to hand over a string as a variable for my method.
I call the function even with the string, no variable currently.
Engine *myEngine = [Engine sharedInstance];
[myEngine getContentArrayFromEngine:#"zumbra"];
My method
-(NSMutableArray*) getContentArrayFromEngine:(NSString *)catName{
NSMutableSet* categorieContent = [[NSMutableSet alloc] init];
NSLog(#"Catname:%#", catName);
//some more code
}
NSLOG output
2011-12-18 18:49:44.165 Zitate[77224:15203] Catname:(null)
Why is catName empty ???
edit1: the complete code
ThirdViewController.m
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString* myTempCatname;
myTempCatname = cell.textLabel.text;
// NSLog(#"test select %#", myTempCatname);
DetailViewController *detailVC = [self.storyboard instantiateViewControllerWithIdentifier:#"detailzitat"];
[self.navigationController pushViewController:detailVC animated:YES];
Engine *myEngine = [Engine sharedInstance];
[myEngine getContentArrayFromEngine:myTempCatname];
}
and in the engine.m
-(NSMutableArray*) getContentArrayFromEngine:(NSString *)catName{
NSMutableSet* categorieContent = [[NSMutableSet alloc] init];
NSLog(#"Übergebener Catname:%#", catName);
// catName=#"zumbra";
// NSLog(#"Inhalt InhalteFromWeb:%#", InhalteFromWeb);
NSLog(#"Catname:%#", catName);
unsigned count = [InhalteFromWeb count];
while (count--) {
NSLog(#"count %d %#", count, [[InhalteFromWeb objectAtIndex:count] objectForKey:CATEGORY]);
if([[[InhalteFromWeb objectAtIndex:count] objectForKey:CATEGORY] isEqualToString:catName]) {
[categorieContent addObject:[InhalteFromWeb objectAtIndex:count]];
NSLog(#"Row %d has Content%#",count, [InhalteFromWeb objectAtIndex:count]);
}
}
NSLog(#"Inhalt Category:%#", categorieContent);
NSArray* tempAr = [[NSArray alloc] initWithArray:[categorieContent allObjects]];
return [NSMutableArray arrayWithArray:tempAr];
}
EDIT2:
Ok, even the hint with the catName did not work. so I have changed my code a little bit.
I have an array with a category, title, content, author, image for each row
I would like to do two things
1) get a unique list of all categories (its working fine)
2) when tapping on one of these categories , open a detailView , show the first element of this category, jump to previous and next item in category by swiping around.
For this, I am going to SET the category I have chosen
First try was to handover in the method, which did not work.
Now I though, SET the category in my engine.h and when displaying the single item, get the array for this category back.
but again, the value of the category is not stored.
ThirdViewController.h
#import <UIKit/UIKit.h>
#import "SecondViewController.h"
#interface ThirdViewController : UIViewController<UITableViewDelegate, UITableViewDataSource> {
NSMutableArray* CategoryList;
}
#property (nonatomic, retain) NSMutableArray* CategoryList;
#end
ThirdViewController.m
#import "ThirdViewController.h"
#import "engine.h"
#import "DetailViewController.h"
#implementation ThirdViewController
#synthesize CategoryList;
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
Engine *myEngine = [Engine sharedInstance];
CategoryList = [myEngine getCategories];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [CategoryList count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier =#"Cell";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [CategoryList objectAtIndex:indexPath.row];
return cell;
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString* myTempCatname;
myTempCatname = cell.textLabel.text;
DetailViewController *detailVC = [self.storyboard instantiateViewControllerWithIdentifier:#"detailzitat"];
[self.navigationController pushViewController:detailVC animated:YES];
Engine *myEngine = [Engine sharedInstance];
[myEngine setCategName:myTempCatname];
NSLog(#"Aufruf %#", myTempCatname);
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
DetailViewController.h
#import <UIKit/UIKit.h>
#import <MessageUI/MFMailComposeViewController.h>
#import "engine.h"
#interface DetailViewController : UIViewController<MFMailComposeViewControllerDelegate> {
IBOutlet UILabel *authorLabel;
IBOutlet UILabel *categoryLabel;
IBOutlet UILabel *titleLabel;
IBOutlet UITextView *contentTextView;
NSString *authorText, *contentText, *categoryText, *titleText, *imageText, *catName;
NSMutableArray *contentArray;
}
#property (nonatomic, retain) IBOutlet UITextView *contentTextView;
#property (nonatomic, retain) IBOutlet UILabel *authorLabel;
#property (nonatomic, retain) IBOutlet UILabel *categoryLabel;
#property (nonatomic, retain) IBOutlet UILabel *titleLabel;
#property (nonatomic, retain) NSString *authorText, *contentText, *categoryText, *titleText, *imageText, *catName;
#property (nonatomic, retain) NSMutableArray *contentArray;
-(IBAction)vorher:(id)sender;
-(IBAction)nachher:(id)sender;
#end
DetailViewController.m
#import "DetailViewController.h"
#implementation DetailViewController
#synthesize contentTextView;
#synthesize authorText, contentText, categoryText, titleText, imageText;
#synthesize authorLabel, categoryLabel, titleLabel;
#synthesize contentArray;
#synthesize catName;
int contentIndex;
int contentMax;
- (IBAction)swipeDetected:(UIGestureRecognizer *)sender {
NSLog(#"Right Swipe detected");
}
-(IBAction) vorher:(id)sender {
NSLog(#"-----VORHER Button gedrückt-------");
if (contentIndex==0) {contentIndex=contentMax-1;}
else {contentIndex--;}
titleText = [[contentArray objectAtIndex:contentIndex] objectForKey:TITLE];
authorText= [[contentArray objectAtIndex:contentIndex] objectForKey:AUTHOR];
contentText= [[contentArray objectAtIndex:contentIndex] objectForKey:CONTENT];
authorLabel.text=authorText;
titleLabel.text=titleText;
contentTextView.text=contentText;
}
-(IBAction) nachher:(id)sender {
NSLog(#"-----Nachher Button gedrückt-------");
if (contentIndex==contentMax-1) {contentIndex=0;}
else {contentIndex++;}
titleText = [[contentArray objectAtIndex:contentIndex] objectForKey:TITLE];
authorText= [[contentArray objectAtIndex:contentIndex] objectForKey:AUTHOR];
contentText= [[contentArray objectAtIndex:contentIndex] objectForKey:CONTENT];
authorLabel.text=authorText;
titleLabel.text=titleText;
contentTextView.text=contentText;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
Engine *myEngine = [Engine sharedInstance];
contentArray = [myEngine getContentArrayFromEngine];
contentMax = [contentArray count];
UISwipeGestureRecognizer *swipeRecognizerRight =
[[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(vorher:)];
swipeRecognizerRight.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeRecognizerRight];
UISwipeGestureRecognizer *swipeRecognizerLeft =
[[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(nachher:)];
swipeRecognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeRecognizerLeft];
titleText = [[contentArray objectAtIndex:contentIndex] objectForKey:TITLE];
authorText= [[contentArray objectAtIndex:contentIndex] objectForKey:AUTHOR];
contentText= [[contentArray objectAtIndex:contentIndex] objectForKey:CONTENT];
authorLabel.text=authorText;
titleLabel.text=titleText;
contentTextView.text=contentText;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
engine.h
//#import
#define AUTHOR #"author"
#define CATEGORY #"cat"
#define CONTENT #"content"
#define IMAGE #"image"
#define TITLE #"title"
#interface Engine : NSObject {
NSMutableArray* InhalteFromWeb;
NSInteger maxAnzahlInhalte;
NSString* categNameStorage;
}
+ (Engine *) sharedInstance;
- (NSMutableArray*) getZitateArrayFromEngine;
- (NSInteger) getMaxAnzahlZitateFromEngine;
- (NSString*) getAutor:(NSInteger)pos;
- (NSString*) getZitat:(NSInteger)pos;
- (NSString*) getAuthor:(NSInteger)pos;
- (NSString*) getCategory:(NSInteger)pos;
- (NSString*) getContent:(NSInteger)pos;
- (NSString*) getImage:(NSInteger)pos;
- (NSString*) getTitle:(NSInteger)pos;
-(NSMutableArray*) getContentArrayFromEngine;
-(void) setCategName:(NSString *) categNameVariable;
-(NSString*) getCategName;
-(NSMutableArray*) getCategories;
#end
engine.m
#import "Engine.h"
#implementation Engine
static Engine *_sharedInstance;
- (id) init
{
if (self = [super init])
{
// custom initialization
//Beginn my code
NSURL *url = [NSURL URLWithString:#"http://www.*/iMotivate.plist"];
NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if( theConnection )
{
InhalteFromWeb = [[NSMutableArray alloc] initWithContentsOfURL:url];
maxAnzahlInhalte = [InhalteFromWeb count];
}
else
{
NSLog(#"Connection failed");
}
}
return self;
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// NSLog(#"Recieving Response...");
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// NSLog(#"Recieving Data...");
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message : #"An error has occured.Please verify your internet connection."
delegate:nil
cancelButtonTitle :#"OK"
otherButtonTitles :nil];
[alert show];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// NSLog(#"DONE. Received Quotes: %d", maxAnzahlZitate);
}
// ###########
+ (Engine *) sharedInstance
{
if (!_sharedInstance)
{
_sharedInstance = [[Engine alloc] init];
}
return _sharedInstance;
}
// Getter and Setter for WebArray
- (NSMutableArray*) getZitateArrayFromEngine{
return InhalteFromWeb;
}
- (NSInteger) getMaxAnzahlZitateFromEngine{
return maxAnzahlInhalte;
}
- (NSString*) getAutor:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:AUTHOR];
}
- (NSString*) getZitat:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:CONTENT];
}
// #######
- (NSString*) getAuthor:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:AUTHOR];
}
- (NSString*) getCategory:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:CATEGORY];
}
- (NSString*) getContent:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:CONTENT];
}
- (NSString*) getImage:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:IMAGE];
}
- (NSString*) getTitle:(NSInteger)pos{
return [[InhalteFromWeb objectAtIndex:pos] objectForKey:TITLE];
}
-(NSArray*) getCategories {
NSMutableSet* categorieSet = [[NSMutableSet alloc] init];
unsigned count = [InhalteFromWeb count];
while (count--) {
NSString *tempString;
tempString=[[InhalteFromWeb objectAtIndex:count] objectForKey:CATEGORY];
// NSLog(#"tempString %#", tempString );
[categorieSet addObject:tempString];
}
// NSLog(#"categories from engine %#", categorieSet);
NSArray* tempAr = [[[NSArray alloc] initWithArray:[categorieSet allObjects]]sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
return [NSMutableArray arrayWithArray:tempAr];
}
-(void) setCategName:(NSString *) categNameVariable
{ NSLog(#"categNameStorage 2%#",categNameStorage);
categNameStorage=categNameVariable;
NSLog(#"setCategName 1 %#",categNameVariable);
NSLog(#"categNameStorage 2%#",categNameStorage);
}
-(NSString*) getCategName {
return categNameStorage;
}
-(NSMutableArray*) getContentArrayFromEngine{
NSMutableSet* categorieContent = [[NSMutableSet alloc] init];
NSLog(#"Übergebener Catname:%#", categNameStorage);
// NSLog(#"Inhalt InhalteFromWeb:%#", InhalteFromWeb);
unsigned count = [InhalteFromWeb count];
while (count--) {
// NSLog(#"count %d %#", count, [[InhalteFromWeb objectAtIndex:count] objectForKey:CATEGORY]);
if([[[InhalteFromWeb objectAtIndex:count] objectForKey:CATEGORY] isEqualToString:categNameStorage]) {
[categorieContent addObject:[InhalteFromWeb objectAtIndex:count]];
// NSLog(#"Row %d has Content%#",count, [InhalteFromWeb objectAtIndex:count]);
}
}
// NSLog(#"Inhalt Category:%#", categorieContent);
NSArray* tempAr = [[NSArray alloc] initWithArray:[categorieContent allObjects]];
return [NSMutableArray arrayWithArray:tempAr];
}
#end
There has to be a variable conflict with 'catName' in your view controller. I'm not sure why the view controller would be in scope, but I bet if you change your parameter to "inCatName" it will be fine. It's good to use naming conventions like that for this reason.
I'm using NSXMLParser and I get a memory leak that points to NSConcreteMapTable, whatever that is:
The leak occurs at this line of code when calling the parser from my AppDelegate.m:
I have searched for a solution and can't see what I'm doing wrong.
Here is my code.
Any help is greatly appreciated.
lq
// * * * XMLParser.h * * *
#import <Foundation/Foundation.h>
#protocol NSXMLParserDelegate;
#interface XMLParser : NSObject
<NSXMLParserDelegate>
{
NSMutableArray *xmlArray;
BOOL storingCharacters;
float xmlDataVersion;
}
#property (nonatomic, retain) NSMutableArray *xmlArray;
#property (nonatomic) BOOL storingCharacters;
#property (nonatomic, assign) float xmlDataVersion;
-(BOOL)parseXMLFileAtURL:(NSURL *)URL parseError:(NSError **)error;
#end
// * * * XMLParser.m * * *
#import "XMLParser.h"
#implementation XMLParser
#synthesize xmlArray;
#synthesize storingCharacters;
#synthesize xmlDataVersion;
- (BOOL)parseXMLFileAtURL:(NSURL *)URL parseError:(NSError **)error {
BOOL result = YES;
if (xmlArray == nil) {
// this array holds row data extracted from the XML parser didStartElement method
xmlArray = [[NSMutableArray alloc] init];
}
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:URL];
if (parser != nil) {
[parser setDelegate:self];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[[parser setShouldResolveExternalEntities:NO];
}
[parser parse];
if (parseError && error) {
*error = parseError;
result = NO;
}
[parser release];
return result;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if (qName) {
elementName = qName;
}
// Check the data version of the XML Data against my stored value
if ([elementName isEqualToString:#"data"]) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
self.xmlDataVersion = [[attributeDict objectForKey:#"version"] floatValue];
float storedDataVersion = [userDefaults floatForKey:kDataVersion];
if (self.xmlDataVersion <= storedDataVersion) {
// - - - - -> Abort parsing if the same or earlier data versions
[parser abortParsing];
}
}
if ([elementName isEqualToString:#"FirstColumnName"]) {
storingCharacters = YES;
} else if ([elementName isEqualToString:#"SecondColumnName"]) {
storingCharacters = YES;
// ... total of 16 elements
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (storingCharacters) {
[self.xmlArray addObject:string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (qName) {
elementName = qName;
}
// - - - - -> If at the end of a data row, save changes to object model
if ([elementName isEqualToString:#"ROW"]) {
// - - - - -> Make sure the data has the required number of elements before taking any action
if ([self.xmlArray count] == 16) {
// … //Store or Update Data in SQLite store depending on data values
}
[self.xmlArray removeAllObjects];
}
storingCharacters = NO;
}
-(void)dealloc {
[xmlArray release];
[super dealloc];
}
// * * * AppDelegate.m * * *
#import "XMLParser.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSURL *xmlURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FileName" ofType:#"xml"]];
NSError *parseError = nil;
XMLParser *xmlParse = [[XMLParser alloc] init];
[xmlParse parseXMLFileAtURL:xmlURL parseError:&parseError];
[xmlParse release];
. . .
}
I found a solution in another SO post:
Use:
NSData * dataXml = [[NSData alloc] initWithContentsOfURL:URL];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:dataXml];
[dataXml release];
Instead of:
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:URL];
The leak goes away.
This is probably a leak in Apple code, since Foundation is reported as "Responsible Library". There's probably nothing you can do, but to report the bug to apple.
I'm trying to implement a simple object bridge in cocoa where the bridge object acts as a kvo/bindings-compliant drop in for some arbitrary other NSObject instance.
Here is my problem (more details in the code below):
A bridge object acts as a drop in for a Person-Object, with an NSString* property called name and an Address* property address. Binding to the keyPath "name" or "address" of the Bridge works nicely. Trouble starts when binding some object to the keyPath "address.street" of the bridge and a new Address-Object is set for Person's address property. That results in KVO-related exceptions that look like this:
Cannot remove an observer <NSKeyValueObservance 0x126b00> for the key path "street" from <Address 0x12f1d0> because it is not registered as an observer
This happens even though the bridge notices the change in the "address"-Property and emits a willChangeValueForKeyPath/didChangeValueForKeyPath tuple.
The code below produces the the problem. It's self-contained objective-c code that can be saved in a file "BridgeDemo.m" and compiled run with
gcc -o test BridgeDemo.m -framework AppKit -framework Foundation; ./test
If you know a solution to this problem or can offer me a better approach solving the same problem you make me a very happy programmer!
BridgeDemo.m:
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
/* --- Address ----------------------------------------- */
#interface Address : NSObject {
NSString* street;
NSNumber* zipCode;
NSString* city;
}
#property(retain) NSString* street;
#property(retain) NSNumber* zipCode;
#property(retain) NSString* city;
#end
#implementation Address
#synthesize street, zipCode, city;
-(id)init {
if( !( self = [super init] ) ) { return nil; }
self.street = #"Elm Street";
self.zipCode = #"12345";
self.city = #"Crashington";
return self;
}
-(void) modifyStreet {
self.street = #"Main Street";
}
-(void)dealloc {
[street release]; [zipCode release]; [city release];
[super dealloc];
}
#end
/* --- Person ----------------------------------------- */
#interface Person : NSObject {
NSString* name;
Address* address;
}
#property(retain) NSString* name;
#property(retain) Address* address;
#end
#implementation Person
#synthesize address, name;
-(id)init {
if( !( self = [super init] ) ) { return nil; }
self.name = #"Tom";
self.address = [[Address new] autorelease];
return self;
}
- (void)modifyAddress {
Address* a = [[Address new] autorelease];
a.street = #"Jump Street";
a.zipCode = #"54321";
a.city = #"Memleakville";
self.address = a;
}
- (void)dealloc { [address release]; [name release]; [super dealloc]; }
#end
/* --- Bridge ----------------------------------------- */
#interface Bridge : NSObject {
NSMutableDictionary* observedKeys;
NSObject* obj;
}
#property(retain) NSObject* obj;
#end
#implementation Bridge
#synthesize obj;
- (id)init {
if( !( self = [super init] ) ) { return nil; }
observedKeys = [NSMutableDictionary new];
return self;
}
- (void)forwardInvocation:(NSInvocation*)inv {
[inv invokeWithTarget:obj];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [obj methodSignatureForSelector:aSelector];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog( #">>>> Detected Change in keyPath: %#", keyPath );
[self willChangeValueForKey:keyPath];
[self didChangeValueForKey:keyPath];
}
-(id)valueForUndefinedKey:(NSString*)key {
/* Register an observer for the key, if not already done */
if( ![observedKeys objectForKey:key] ) {
[observedKeys setObject:[NSNumber numberWithBool:YES] forKey:key];
[obj addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil];
}
return [obj valueForKey:key];
}
- (void)dealloc {
for( NSString* key in [observedKeys allKeys] ) {
[obj removeObserver:self forKeyPath:key];
}
[obj release];
[observedKeys release];
[super dealloc];
}
#end
/* --- MyObserver ------------------------------------ */
#interface MyObserver : NSObject {
Address* address;
NSString* street;
}
#property(retain) Address* address;
#property(retain) NSString* street;
#end
#implementation MyObserver
#synthesize street, address;
-(void)dealloc { [street release]; [super dealloc]; }
#end
/* This works fine */
void testBindingToAddress() {
NSLog( #"Testing Binding to 'address' --------------" );
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Bridge* b = [[Bridge new] autorelease];
b.obj = [Person new];
MyObserver* o = [[MyObserver new] autorelease];
[o bind:#"address" toObject:b withKeyPath:#"address"
options:nil];
NSLog( #"Before modifyStreet: %#", o.address.street );
[[b valueForKey:#"address"] performSelector:#selector(modifyStreet)];
NSLog( #"After modifyStreet: %#", o.address.street );
[b performSelector:#selector(modifyAddress)];
NSLog( #"After modifyAdress: %#", o.address.street );
[pool drain];
}
/* This produces an exception */
void testBindingToStreet() {
NSLog( #"Testing Binding to 'address.street' --------------" );
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Bridge* b = [[Bridge new] autorelease];
b.obj = [Person new];
MyObserver* o = [[MyObserver new] autorelease];
[o bind:#"street" toObject:b withKeyPath:#"address.street"
options:nil];
NSLog( #"Before modifyStreet: %#", o.street );
[[b valueForKey:#"address"] performSelector:#selector(modifyStreet)];
NSLog( #"After modifyStreet: %#", o.street );
[b performSelector:#selector(modifyAddress)];
NSLog( #"After modifyAdress: %#", o.street );
[pool drain];
}
/* --- main() ------------------------------------ */
int main (int argc, const char * argv[]) {
testBindingToAddress();
testBindingToStreet();
return 0;
}
Here is the problem:
[self willChangeValueForKey:keyPath]; <--- At this point in time the actual observer needs to unsubscribe to street
[self didChangeValueForKey:keyPath]; <--- and add itself to the new value.
By not providing the new value you are denying the observer the opportunity to unsubscribe.
Here is a hacked version that works and demonstrates the problem.
/* --- Bridge ----------------------------------------- */
....
.....
#interface Bridge : NSObject {
NSMutableDictionary* observedKeys;
NSObject* obj;
//**** Dictionary for old values just before we send the didChangeValue notification.
NSMutableDictionary * oldValues;
}
...
.....
- (id)init {
if( !( self = [super init] ) ) { return nil; }
observedKeys = [NSMutableDictionary new];
//************* Initialize the new dictionary
oldValues = [NSMutableDictionary new];
return self;
}
....
.....
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog( #">>>> Detected Change in keyPath: %#", keyPath );
// **** Cache the old value before telling everyone its going to change.
[oldValues setValue:[change valueForKey:NSKeyValueChangeOldKey] forKey:keyPath];
[self willChangeValueForKey:keyPath];
// **** Simulate the change by removing the old value.
[oldValues removeObjectForKey:keyPath];
// **** Now when we say we did change the value, we are not lying.
[self didChangeValueForKey:keyPath];
}
-(id)valueForUndefinedKey:(NSString*)key {
// **** Important part, return oldvalue if it exists
id oldValue;
if(oldValue = [oldValues valueForKey:key]){
return oldValue;
}
/* Register an observer for the key, if not already done */
if( ![observedKeys objectForKey:key] ) {
[observedKeys setObject:[NSNumber numberWithBool:YES] forKey:key];
NSLog(#"adding observer for:%#", key);
[obj addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
return [obj valueForKey:key];
}
....
......