Implementing a KVO/Bindings-Compliant Bridge-Pattern in Cocoa - cocoa

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];
}
....
......

Related

xcode crash after pressing lens

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

Memory Leak Calling Synchonous Method

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.

Cocoa: Making NSTextField editable after a click and short delay (like renaming in Finder)

I cannot find a simple example of how to use an NSTextField to edit it's contents in place.
Exactly like in the Finder - you're able to click, and with a short delay the text field becomes editable.
It seems like it's some combination of the textField, it's cell, and the fieldEditor? Problem is I can't find the most basic example of how to do it.
I've tried subclassing NSTextField with a couple different tests but it hasn't worked:
#import "GWTextField.h"
#implementation GWTextField
- (id) initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
return self;
}
- (void) mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[self.cell editWithFrame:self.frame inView:self.superview editor:[self.cell fieldEditorForView:self] delegate:self event:theEvent];
//[self setEditable:TRUE];
//[self setSelectable:TRUE];
//[self selectText:nil];
[NSTimer scheduledTimerWithTimeInterval:.3 target:self selector:#selector(edit:) userInfo:nil repeats:FALSE];
}
- (void) edit:(id) sende {
NSLog(#"edit");
[[NSApplication sharedApplication].mainWindow makeFirstResponder:self];
[self selectText:nil];
}
#end
Any ideas?
Here's another solution with no NSCell - one user pointed out that NSCell is deprecated and will at some point be gone.
#import <Cocoa/Cocoa.h>
#interface EditTextField : NSTextField <NSTextDelegate,NSTextViewDelegate,NSTextFieldDelegate>
#property BOOL isEditing;
#property BOOL commitChangesOnEscapeKey;
#property BOOL editAfterDelay;
#property CGFloat delay;
#end
----
#import "EditTextField.h"
#interface EditTextField ()
#property NSObject <NSTextFieldDelegate,NSTextViewDelegate> * userDelegate;
#property NSString * originalStringValue;
#property NSTimer * editTimer;
#property NSTrackingArea * editTrackingArea;
#end
#implementation EditTextField
- (id) initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
[self defaultInit];
return self;
}
- (id) initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
[self defaultInit];
return self;
}
- (id) init {
self = [super init];
[self defaultInit];
return self;
}
- (void) defaultInit {
self.delay = .8;
}
- (void) mouseDown:(NSEvent *) theEvent {
if(theEvent.clickCount == 2) {
[self startEditing];
} else {
[super mouseDown:theEvent];
if(self.editAfterDelay) {
[self startTracking];
self.editTimer = [NSTimer scheduledTimerWithTimeInterval:.8 target:self selector:#selector(startEditing) userInfo:nil repeats:FALSE];
}
}
}
- (void) startTracking {
if(!self.editTrackingArea) {
self.editTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInActiveApp|NSTrackingAssumeInside|NSTrackingInVisibleRect owner:self userInfo:nil];
}
[self addTrackingArea:self.editTrackingArea];
}
- (void) mouseExited:(NSEvent *)theEvent {
[self.editTimer invalidate];
self.editTimer = nil;
}
- (void) mouseMoved:(NSEvent *) theEvent {
[self.editTimer invalidate];
self.editTimer = nil;
}
- (void) startEditing {
id firstResponder = self.window.firstResponder;
if([firstResponder isKindOfClass:[NSTextView class]]) {
NSTextView * tv = (NSTextView *)firstResponder;
if(tv.delegate && [tv.delegate isKindOfClass:[EditTextField class]]) {
EditTextField * fr = (EditTextField *)tv.delegate;
[fr stopEditingCommitChanges:FALSE clearFirstResponder:FALSE];
}
}
if(self.delegate != self) {
self.userDelegate = (NSObject <NSTextFieldDelegate,NSTextViewDelegate> *)self.delegate;
}
self.isEditing = TRUE;
self.delegate = self;
self.editable = TRUE;
self.originalStringValue = self.stringValue;
[self.window makeFirstResponder:self];
}
- (void) stopEditingCommitChanges:(BOOL) commitChanges clearFirstResponder:(BOOL) clearFirstResponder {
self.editable = FALSE;
self.isEditing = FALSE;
self.delegate = nil;
[self removeTrackingArea:self.editTrackingArea];
if(!commitChanges) {
self.stringValue = self.originalStringValue;
}
if(clearFirstResponder) {
[self.window makeFirstResponder:nil];
}
}
- (void) cancelOperation:(id) sender {
if(self.commitChangesOnEscapeKey) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
} else {
[self stopEditingCommitChanges:FALSE clearFirstResponder:TRUE];
}
}
- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
BOOL handlesCommand = FALSE;
NSString * selector = NSStringFromSelector(commandSelector);
if(self.userDelegate) {
if([self.userDelegate respondsToSelector:#selector(control:textView:doCommandBySelector:)]) {
handlesCommand = [self.userDelegate control:self textView:textView doCommandBySelector:commandSelector];
} else if([self.userDelegate respondsToSelector:#selector(textView:doCommandBySelector:)]) {
handlesCommand = [self.userDelegate textView:textView doCommandBySelector:commandSelector];
}
if(!handlesCommand) {
if([selector isEqualToString:#"insertNewline:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
handlesCommand = TRUE;
}
if([selector isEqualToString:#"insertTab:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
handlesCommand = FALSE;
}
}
} else {
if([selector isEqualToString:#"insertNewline:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
handlesCommand = TRUE;
}
if([selector isEqualToString:#"insertTab:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
handlesCommand = FALSE;
}
}
return handlesCommand;
}
#end
I built a re-usable NSTextField subclass you can use for edit in place functionality. http://pastebin.com/QymunMYB
I came up with a better solution to the edit in place problem. I believe this is how to properly do edit in place with NSCell. Please show and tell if this is wrong.
#import <Cocoa/Cocoa.h>
#interface EditTextField : NSTextField <NSTextDelegate>
#end
---
#import "EditTextField.h"
#implementation EditTextField
- (void) mouseDown:(NSEvent *)theEvent {
if(theEvent.clickCount == 2) {
self.editable = TRUE;
NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
[self.cell editWithFrame:self.bounds inView:self editor:fieldEditor delegate:self event:theEvent];
} else {
[super mouseDown:theEvent];
}
}
- (void) cancelOperation:(id)sender {
[self.cell endEditing:nil];
self.editable = FALSE;
}
- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
NSString * selector = NSStringFromSelector(commandSelector);
if([selector isEqualToString:#"insertNewline:"]) {
NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
[self.cell endEditing:fieldEditor];
self.editable = FALSE;
return TRUE;
}
return FALSE;
}
#end
In my application I have two text fields - one non editable, and second, hidden, editable, and activates title editing by calling:
[self addSubview:windowTitle];
[windowTitleLabel removeFromSuperview];
[self.window makeFirstResponder:windowTitle];
This is called from mouseUp: on view behind the label.
I don't remember why I needed to have two text fields (i didn't know Cocoa good that time), probably it will work even without label swapping.

unable to handover a string to my method

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.

Setting kCFStreamSSLValidatesCertificateChain in one CFReadStream causes other CFReadStreams not to validate cert chains

In the iOS UIViewController code below, I connect to a server that uses a self-signed cert. I can verify this self-signed cert two ways: manually with the trust APIs, or automatically by adding the self-signed cert into my app's keychain.
Unfortunately, after I create a CFReadStream and set kCFStreamSSLValidatesCertificateChain to kBooleanFalse, every CFReadStream I create afterwards doesn't verify its cert chain. Am I failing to clean up code somewhere? I'll happily reformulate this question into something specific about API cleanup if so.
#import <UIKit/UIKit.h>
#import <Security/Security.h>
#interface SecureViewController : UIViewController<NSStreamDelegate> {
}
- (id) initWithCertificate: (SecCertificateRef) certificate;
#end
#import "SecureViewController.h"
#interface SecureViewController()
#property (nonatomic) SecCertificateRef certificate;
#property (nonatomic, retain) NSInputStream *inputStream;
#property (nonatomic, retain) NSOutputStream *outputStream;
#property (nonatomic) BOOL verifyOnHasSpaceAvailable;
- (void) verifyManually;
- (void) verifyWithKeychain;
#end
#implementation SecureViewController
#synthesize certificate = _certificate;
#synthesize inputStream = _inputStream;
#synthesize outputStream = _outputStream;
#synthesize verifyOnHasSpaceAvailable = _verifyOnHasSpaceAvailable;
#pragma mark -
#pragma mark init/dealloc methods
- (id) initWithCertificate: (SecCertificateRef) certificate {
if (self = [super initWithNibName:nil bundle:nil]) {
self.certificate = certificate;
}
return self;
}
- (void)dealloc {
self.certificate = NULL;
self.inputStream = nil;
self.outputStream = nil;
[super dealloc];
}
#pragma mark -
#pragma mark UIViewController
- (void)loadView {
[super loadView];
UIButton *manualVerificationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[manualVerificationButton addTarget:self
action:#selector(verifyManually)
forControlEvents:UIControlEventTouchUpInside];
manualVerificationButton.frame = CGRectMake(0,
0,
self.view.bounds.size.width,
self.view.bounds.size.height / 2);
[manualVerificationButton setTitle:#"Manual Verification"
forState:UIControlStateNormal];
[self.view addSubview:manualVerificationButton];
UIButton *keychainVerificationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[keychainVerificationButton addTarget:self
action:#selector(verifyWithKeychain)
forControlEvents:UIControlEventTouchUpInside];
keychainVerificationButton.frame = CGRectMake(0,
self.view.bounds.size.height / 2,
self.view.bounds.size.width,
self.view.bounds.size.height / 2);
[keychainVerificationButton setTitle:
#"Keychain Verification\n"
#"(Doesn't work after Manual Verification)\n"
#"((Don't know why yet.))"
forState:UIControlStateNormal];
keychainVerificationButton.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
keychainVerificationButton.titleLabel.numberOfLines = 0;
[self.view addSubview:keychainVerificationButton];
}
#pragma mark -
#pragma mark private api
- (void) verifyWithKeychain {
self.inputStream = nil;
self.outputStream = nil;
self.verifyOnHasSpaceAvailable = NO;
OSStatus err = SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
self.certificate, kSecValueRef,
nil],
NULL);
assert(err == noErr || err == errSecDuplicateItem);
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)#"localhost",
8443,
&readStream,
&writeStream);
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
self.inputStream = (NSInputStream *)readStream;
self.outputStream = (NSOutputStream *)writeStream;
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}
- (void) verifyManually {
self.inputStream = nil;
self.outputStream = nil;
// we don't want the keychain to accidentally accept our self-signed cert.
SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
self.certificate, kSecValueRef,
nil]);
self.verifyOnHasSpaceAvailable = YES;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)#"localhost",
8443,
&readStream,
&writeStream);
NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
nil];
CFReadStreamSetProperty(readStream,
kCFStreamPropertySSLSettings,
sslSettings);
// Don't set this property. The only settings that work are:
// kCFStreamSocketSecurityLevelNone or leaving it unset.
// Leaving it appears to be equivalent to setting it to:
// kCFStreamSocketSecurityLevelTLSv1 or kCFStreamSocketSecurityLevelSSLv3
//
// CFReadStreamSetProperty(readStream,
// kCFStreamPropertySocketSecurityLevel,
// kCFStreamSocketSecurityLevelTLSv1);
self.inputStream = (NSInputStream *)readStream;
self.outputStream = (NSOutputStream *)writeStream;
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}
#pragma mark -
#pragma mark private properties
- (void) setCertificate:(SecCertificateRef) certificate {
if (_certificate != certificate) {
if (_certificate) {
CFRelease(_certificate);
}
_certificate = certificate;
if (_certificate) {
CFRetain(_certificate);
}
}
}
- (void) setInputStream:(NSInputStream *) inputStream {
if (_inputStream != inputStream) {
[_inputStream setDelegate:nil];
[_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[_inputStream close];
[_inputStream release];
_inputStream = inputStream;
[_inputStream retain];
[_inputStream setDelegate:self];
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
}
}
- (void) setOutputStream:(NSOutputStream *) outputStream {
if (_outputStream != outputStream) {
[_outputStream setDelegate:nil];
[_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[_outputStream close];
[_outputStream release];
_outputStream = outputStream;
[_outputStream retain];
[_outputStream setDelegate:self];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
}
}
#pragma mark -
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream
handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
break;
case NSStreamEventHasSpaceAvailable:
NSLog(#"Socket Security Level: %#", [aStream propertyForKey:(NSString *) kCFStreamPropertySocketSecurityLevel]);
NSLog(#"SSL settings: %#", [aStream propertyForKey:(NSString *) kCFStreamPropertySSLSettings]);
if (self.verifyOnHasSpaceAvailable) {
SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost"));
SecTrustRef trust = NULL;
SecTrustCreateWithCertificates([aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates],
policy,
&trust);
SecTrustSetAnchorCertificates(trust,
(CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]);
SecTrustResultType trustResultType = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &trustResultType);
if (status == errSecSuccess) {
// expect trustResultType == kSecTrustResultUnspecified until my cert exists in the keychain
// see technote for more detail: http://developer.apple.com/library/mac/#qa/qa2007/qa1360.html
if (trustResultType == kSecTrustResultUnspecified) {
NSLog(#"We can trust this certificate! TrustResultType: %d", trustResultType);
} else {
NSLog(#"Cannot trust certificate. TrustResultType: %d", trustResultType);
}
} else {
NSLog(#"Creating trust failed: %d", status);
[aStream close];
}
if (trust) {
CFRelease(trust);
}
if (policy) {
CFRelease(policy);
}
} else {
NSLog(#"We can trust this server!");
}
break;
case NSStreamEventErrorOccurred:
if ([[aStream streamError] code] == -9807) { // header file with error code symbol isn't present in ios.
NSLog(#"We cannot trust this certificate.");
} else {
NSLog(#"unexpected NSStreamEventErrorOccurred: %#", [aStream streamError]);
}
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}
#end
The order of CFReadStream setter calls is important, apparently. The following verifyManually method works:
- (void) verifyManually {
self.inputStream = nil;
self.outputStream = nil;
// we don't want the keychain to accidentally accept our self-signed cert.
SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
(id) kSecClassCertificate, kSecClass,
self.certificate, kSecValueRef,
nil]);
self.verifyOnHasSpaceAvailable = YES;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)#"localhost",
8443,
&readStream,
&writeStream);
// Set this kCFStreamPropertySocketSecurityLevel before
// setting kCFStreamPropertySSLSettings.
// Setting kCFStreamPropertySocketSecurityLevel
// appears to override previous settings in kCFStreamPropertySSLSettings
CFReadStreamSetProperty(readStream,
kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelTLSv1);
NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
nil];
CFReadStreamSetProperty(readStream,
kCFStreamPropertySSLSettings,
sslSettings);
self.inputStream = (NSInputStream *)readStream;
self.outputStream = (NSOutputStream *)writeStream;
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
}

Resources