(Objective-c/Mac OSX) How to distinguish managed AD users (AD user create mobile card) from local users on Mac OSX - objective-c

<\RESOLVED>, Please see the first reply
My mac(10.9) has joined into a AD domain. In my program, I tried to recognize whether the current login user is local account or AD user. I can successfully distinguish them by using the following code.
+ (bool)isLocalUser:(NSString*)user
{
NSError *dirSearchError = nil;
ODRecord *foundUser = findUser(user, &dirSearchError);
if(foundUser !=nil)
{
return YES;
}else
{
return NO;
}
}
ODRecord *findUser(NSString *user, NSError **error)
{
NSLog(#"[MacLogonUI] findUser");
ODNode *searchNode = [ODNode nodeWithSession: [ODSession defaultSession]
type: kODNodeTypeLocalNodes
error: error];
if (searchNode == nil) {
return nil;
}
NSDictionary *nodeInfo = [searchNode nodeDetailsForKeys:nil error:error];
/* query this node for the user record we're interested in.
* We only need one result, which is why maximumResults is set to 1.
*/
ODQuery *userSearch = [ODQuery queryWithNode: searchNode
forRecordTypes: kODRecordTypeUsers
attribute: kODAttributeTypeRecordName
matchType: kODMatchEqualTo
queryValues: user
returnAttributes: kODAttributeTypeStandardOnly
maximumResults: 1
error: error];
if (userSearch == nil) {
return nil;
}
/* For this example we'll use a synchronous search. This could take a while
* so asynchronous searching is preferable.
*/
NSArray *foundRecords = [userSearch resultsAllowingPartial: NO error: error];
if (foundRecords == nil || [foundRecords count] == 0) {
return nil;
}
ODRecord *userRecord = [foundRecords objectAtIndex: 0];
return [[userRecord retain] autorelease];
}
While when the AD user create a mobile card, it is viewed as a managed user(from the System preference -> Users & Groups). The code also recognize this kind of AD user as local. How to deal with this kind of situation?
Do you guys have any idea of this problem?

I have solved this problem by myself. Hope the following code helps:
#import "DasUser.h"
#import <OpenDirectory/OpenDirectory.h>
#import <Collaboration/Collaboration.h>
#implementation DasUser
+ (bool)isLocalUser:(NSString*)user
{
NSError *dirSearchError = nil;
ODRecord *foundUser = findUser(user, &dirSearchError);
if(foundUser !=nil)
{
return YES;
}else
{
return NO;
}
}
ODRecord *findUser(NSString *user, NSError **error)
{
NSLog(#"[MacLogonUI] findUser");
CSIdentityAuthorityRef defaultAuthority = CSGetManagedIdentityAuthority();
CSIdentityClass identityClass = kCSIdentityClassUser;
CSIdentityQueryRef query = CSIdentityQueryCreate(NULL, identityClass, defaultAuthority);
CFErrorRef err = NULL;
CSIdentityQueryExecute(query, 0, &err);
CFArrayRef results = CSIdentityQueryCopyResults(query);
int numResults = CFArrayGetCount(results);
NSMutableArray * managedUsers = [NSMutableArray array];
for (int i = 0; i < numResults; ++i) {
CSIdentityRef identity = (CSIdentityRef)CFArrayGetValueAtIndex(results, i);
CBIdentity * identityObject = [CBIdentity identityWithCSIdentity:identity];
NSString* posixName = [identityObject posixName];
[managedUsers addObject:posixName];
}
CFRelease(results);
CFRelease(query);
ODNode *searchNode = [ODNode nodeWithSession: [ODSession defaultSession]
type: kODNodeTypeLocalNodes
error: error];
if (searchNode == nil) {
return nil;
}
/* query this node for the user record we're interested in.
* We only need one result, which is why maximumResults is set to 1.
*/
ODQuery *userSearch = [ODQuery queryWithNode: searchNode
forRecordTypes: kODRecordTypeUsers
attribute: kODAttributeTypeRecordName
matchType: kODMatchEqualTo
queryValues: user
returnAttributes: kODAttributeTypeStandardOnly
maximumResults: 1
error: error];
if (userSearch == nil) {
return nil;
}
/* For this example we'll use a synchronous search. This could take a while
* so asynchronous searching is preferable.
*/
NSArray *foundRecords = [userSearch resultsAllowingPartial: NO error: error];
if([foundRecords count]>0)
{
NSString *nameStr = [foundRecords[0] recordName];
NSLog(#"[MacLogonUI] findUser nameStr %#", nameStr);
int j;
for( j = 0; j<[managedUsers count]; j++)
{
if([nameStr isEqualToString:managedUsers[j]])
{
break;
}
}
if(j<[managedUsers count])
{
foundRecords = nil;
}
}
if (foundRecords == nil || [foundRecords count] == 0) {
return nil;
}
ODRecord *userRecord = [foundRecords objectAtIndex: 0];
return [[userRecord retain] autorelease];
}
#end
While when network of the mac is disconnected. The managed user can not be listed. Is there anybody has any idea of this?

Related

CKQueryOperation working with big batch

I have problem with creating CKQuery operation with big batch of data. My query works with 100 results but after more results query fail, because one thread is bad dispatched or something (libdispatch.dylib`dispatch_group_leave:
) i am lost... any idea?
+ (void) fetchAnswersWithRecordId:(CKRecordID *)recordId completionHandler:(CloudKitCompletionHandler)handler {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANSrecordID == %#", recordId];
CKQuery *query = [[CKQuery alloc] initWithRecordType:ckAnswers predicate:predicate];
CKQueryOperation *operation = [[CKQueryOperation alloc] initWithQuery:query];
CKQueryOperation * __weak weakSelf = operation;
operation.resultsLimit = 300;
NSMutableArray *tmp = [[NSMutableArray alloc] init];
operation.recordFetchedBlock = ^(CKRecord *record) {
[tmp addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (!handler)
return;
NSArray *array = [NSArray arrayWithArray:tmp];
if(cursor != nil) {
CKQueryOperation *newOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
newOperation.recordFetchedBlock = weakSelf.recordFetchedBlock;
newOperation.completionBlock = weakSelf.completionBlock;
[[self publicCloudDatabase] addOperation:newOperation];
} else {
NSLog(#"Results: %lu", [array count]);
dispatch_async(dispatch_get_main_queue(), ^{
handler(array, error);
});
}
};
[[self publicCloudDatabase] addOperation:operation];}
I think your issue lies with the __weak operation and the way you create an operation inside another operation. Here is an example (in swift) of how I do something similar i.e. get additional results, but in a fetch and not a query. Note the use of a instance variable to initialize first time and the use of semi-recursion through GCD dispatch_aync:
private func _fetchRecordChangesFromCloud() {
if !_fetching {
// this is the first and only time this code is called in GCD recusion
// we clean the caches we use to collect the results of the fetch
// so we can then save the record in the correct order so references can be created
_fetchedModifiedRecords = []
_fetchedDeletedRecordIDs = []
// mark fetching has started
_fetching = true
}
let operation = CKFetchRecordChangesOperation(recordZoneID: _customRecordZoneID, previousServerChangeToken: _serverChangeToken)
operation.recordChangedBlock = { (record: CKRecord?) in
if let record = record {
println("Received record to save: \(record)")
self._fetchedModifiedRecords.append(record)
}
}
operation.recordWithIDWasDeletedBlock = { (recordID: CKRecordID?) in
if let recordID = recordID {
println("Received recordID to delete: \(recordID)")
self._fetchedDeletedRecordIDs.append(recordID)
}
}
operation.fetchRecordChangesCompletionBlock = {
(serverChangeToken: CKServerChangeToken?, clientChangeToken: NSData?, error: NSError?) -> Void in
if let error = error {
println("Error in fetching record changes: \(error)")
// try again next sync
self._fetchAfterNextSuccessfullSync = true
self._fetching = false
return
}
// fetched records successfuly
println("fetched records successfuly")
if let serverChangeToken = serverChangeToken {
self._serverChangeToken = serverChangeToken
}
if operation.moreComing {
// we need to create another operation object and do it again
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
self._fetchRecordChangesFromCloud()
}
} else {
// we are finally done
// process the fetched records
self._processFetchedRecords()
// save all changes back to persistent store
self._saveBackgroundContext()
// we are done
self._fetching = false
}
}
self._privateDatabase.addOperation(operation)
}
FYI, dispatch_async() is not necessary, this is a memory management issue. The following works properly for a multi-batch fetch:
//
__block CKQueryOperation* enumerateOperationActive = nil;
//
NSPredicate* predicate = [NSPredicate predicateWithValue:TRUE];
CKQuery* query = [[[CKQuery alloc] initWithRecordType:#"myType" predicate:predicate] autorelease];
CKQueryOperation* enumerateOperation = [[[CKQueryOperation alloc] initWithQuery:query] autorelease];
// DEBUG: fetch only 1 record in order to "force" a nested CKQueryOperation cycle
enumerateOperation.resultsLimit = 1;
enumerateOperation.recordFetchedBlock = ^(CKRecord* recordFetched)
{
// ...
};
enumerateOperation.queryCompletionBlock = ^(CKQueryCursor* cursor, NSError* error)
{
if (error)
{
// ...
}
else
{
if (cursor)
{
CKQueryOperation* enumerateOperationNested = [[[CKQueryOperation alloc] initWithCursor:cursor] autorelease];
// DEBUG: fetch only 1 record in order to "force" a doubly-nested CKQueryOperation cycle
enumerateOperationNested.resultsLimit = 1;
enumerateOperationNested.recordFetchedBlock = /* re-used */ enumerateOperationActive.recordFetchedBlock;
enumerateOperationNested.queryCompletionBlock = /* re-used */ enumerateOperationActive.queryCompletionBlock;
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperationNested;
[database addOperation:enumerateOperationNested];
}
}
};
//
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperation;
[database addOperation:enumerateOperation];
NOTE: if you attempt to access (the original) enumerateOperation.queryCompletionBlock instead of (the very last) enumerateOperationActive.queryCompletionBlock, the operation will never complete.
I liked the recursive solution on the main thread. Below is my solution in Objective C. I'm using a class level var: _recordArray, allocated ahead of time.
- (void) readRecords_Resurs: (CKDatabase *) database query: (CKQuery *) query cursor: (CKQueryCursor *) cursor {
// Send either query or cursor
CKQueryOperation *operation;
if (query != nil) operation = [[CKQueryOperation alloc] initWithQuery: query];
else operation = [[CKQueryOperation alloc] initWithCursor: cursor];
operation.recordFetchedBlock = ^(CKRecord *record) {
[_recordArray addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (cursor == nil || error != nil) {
// Done
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecordsDone: error == nil ? nil : [error localizedDescription]]; });
}
else {
// Get next batch using cursor
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecords_Resurs: database query: nil cursor: cursor]; });
}
};
[database addOperation: operation]; // start
}
- (void) readRecordsDone: (NSString *) errorMsg {
}
My solution is a category that uses two operations but both use the same blocks, and you can supply how many results per request you'd like.
#interface CKDatabase (MH)
/* Convenience method for performing a query receiving the results in batches using multiple network calls. Best use max 400 for cursorResultsLimit otherwise server sometimes exceptions telling you to use max 400. Even using CKQueryOperationMaximumResults can cause this exception. */
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler;
#end
#implementation CKDatabase(MH)
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler{
//holds all the records received.
NSMutableArray* records = [NSMutableArray array];
//this block adds records to the result array
void (^recordFetchedBlock)(CKRecord *record) = ^void(CKRecord *record) {
[records addObject:record];
};
//this is used to allow the block to call itself recurively without causing a retain cycle.
void (^queryCompletionBlock)(CKQueryCursor *cursor, NSError *error)
__block __weak typeof(queryCompletionBlock) weakQueryCompletionBlock;
weakQueryCompletionBlock = queryCompletionBlock = ^void(CKQueryCursor *cursor, NSError *error) {
//if any error at all then end with no results. Note its possible that we got some results,
// and even might have got a cursor. However if there is an error then the cursor doesn't work properly so will just return with no results.
if(error){
completionHandler(nil,error);
}
else if(cursor){
CKQueryOperation* cursorOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
cursorOperation.zoneID = zoneID;
cursorOperation.resultsLimit = cursorResultsLimit;
cursorOperation.recordFetchedBlock = recordFetchedBlock;
cursorOperation.queryCompletionBlock = weakQueryCompletionBlock; // use the weak pointer to prevent retain cycle
//start the recursive operation and return.
[self addOperation:cursorOperation];
}
else{
completionHandler(records,nil);
}
};
//start the initial operation.
CKQueryOperation* queryOperation = [[CKQueryOperation alloc] initWithQuery:query];
queryOperation.zoneID = zoneID;
queryOperation.resultsLimit = cursorResultsLimit;
queryOperation.recordFetchedBlock = recordFetchedBlock;
queryOperation.queryCompletionBlock = queryCompletionBlock;
[self addOperation:queryOperation];
}
#end

iOS: Best Way to do This w/o Calling Method 32 Times?

I'm currently retrieving the Top 100 Scores for one of my leaderboards the following way:
- (void) retrieveTop100Scores {
__block int totalScore = 0;
GKLeaderboard *myLB = [[GKLeaderboard alloc] init];
myLB.identifier = [Team currentTeam];
myLB.timeScope = GKLeaderboardTimeScopeAllTime;
myLB.playerScope = GKLeaderboardPlayerScopeGlobal;
myLB.range = NSMakeRange(1, 100);
[myLB loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
if (scores != nil) {
for (GKScore *score in scores) {
NSLog(#"%lld", score.value);
totalScore += score.value;
}
NSLog(#"Total Score: %d", totalScore);
[self loadingDidEnd];
}
}];
}
The problem is I want to do this for 32 leaderboards. I have an array with all 32 leaderboard identifiers:
NSArray *leaderboardIDs;
So my question is, how can I combine those 2 segments of code to pull in the 100 values for each leaderboard, resulting in a dictionary with all of the leaderboard names as keys, and the Total (of each 100 scores) of the leaderboards for values.
UPDATED:
So I have updated my answer using CrimsonChris' help. The only question I have is, how can I know when it is done totaling the score for all 32 teams? The reason I ask, is because I would then like to organize them from highest to lowest, and display them in that order in a tableView.
This is what I've updated my answer to:
In my viewDidLoad:
- (void)loadLeaderboardData {
// Array of leaderboard ID's to get high scores for
NSArray *leaderboardIDs = #[#"algeria", #"argentina", #"australia", #"belgium", #"bosniaandherzegovina", #"brazil", #"cameroon", #"chile", #"colombia", #"costarica", #"croatia", #"ecuador", #"england", #"france", #"germany", #"ghana", #"greece", #"honduras", #"iran", #"italy", #"ivorycoast", #"japan", #"mexico", #"netherlands", #"nigeria", #"portugal", #"russia", #"southkorea", #"spain", #"switzerland", #"unitedstates", #"uruguay"];
scoresByLeaderboardID = [NSMutableDictionary dictionary];
__block int requestCount = (int)leaderboardIDs.count;
for (NSString *leaderboardID in leaderboardIDs) {
[self loadScoresForLeaderboardID:leaderboardID range:NSMakeRange(1, 100) callback:^(NSArray *scores) {
scoresByLeaderboardID[leaderboardID] = scores;
if (--requestCount <= 0) {
if (callback)callback(scoresByLeaderboardID);
}
}];
}
}
- (void)loadScoresForLeaderboardID:(NSString*)leaderboardID range:(NSRange)range callback:(CallbackBlock)callback {
__block int totalScore = 0;
GKLeaderboard *myLB = [[GKLeaderboard alloc] init];
myLB.identifier = leaderboardID;
myLB.timeScope = GKLeaderboardTimeScopeAllTime;
myLB.playerScope = GKLeaderboardPlayerScopeGlobal;
myLB.range = range;
[myLB loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
if (error != nil) {
//NSLog(#"%#", [error localizedDescription]);
}
if (scores != nil) {
for (GKScore *score in scores) {
//NSLog(#"Individual Scores: %lld (For %#)", score.value, leaderboardID);
}
}
}];
}
Your method can be cleaned up by using callback blocks.
typedef void(^CallbackBlock)(id object);
//callback accepts an NSArray* of GKScore*
- (void)loadScoresForLeaderboardID:(NSString *)leaderboardID range:(NSRange)range callback:(CallbackBlock)callback {
GKLeaderboard *myLB = [[GKLeaderboard alloc] init];
myLB.identifier = leaderboardID;
myLB.timeScope = GKLeaderboardTimeScopeAllTime;
myLB.playerScope = GKLeaderboardPlayerScopeGlobal;
myLB.range = range;
[myLB loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
if (error != nil) NSLog(#"%#", [error localizedDescription]);
if (callback) callback(scores);
}];
}
You can then loop over your leaderboards.
//callback accepts an NSDictionary* of NSArray*(of GKScore*) by NSNumber*
- (void)loadScoresForLeaderboardIDs:(NSArray *)leaderboardIDs withCallback:(CallbackBlock)callback {
NSMutableDictionary *scoresByLeaderboardID = [NSMutableDictionary dictionary];
__block int requestCount = leaderboardIDs.count;
for (NSString *leaderboardID in leaderboardIDs) {
[LeaderboardLoader loadScoresForLeaderboardID:leaderboardID range:NSMakeRange(1, 100) callback:^(NSArray *scores) {
scoresByLeaderboardID[leaderboardID] = scores;
if (--requestCount <= 0) { //not thread safe
if (callback) callback(scoresByLeaderboardID);
}
}];
}
}
Once this method fires its callback you should have all your scores.
[LeaderboardLoader loadScoresForLeaderboardIDs:#[#"FirstID", #"SecondID", #"ThirdID"] withCallback:^(NSDictionary *scoresByLeaderboardID) {
NSLog(#"%#", scoresByLeaderboardID);
//do whatever you need to with all your scores here.
}];

How to save data in Core Data?

I created an entity named House. This entity has two attributes which are street (of type string) and numOfStories (of type integer 32). I was able to successfully save and NSLog data in my AppDelegate.m didFinishLaunchingWithOptions method. However, when I try to make 2 textfields and show the User's input, and make a button to save, the result is SIGABRT.
Here is all the code I'm using in my MainViewController.m:
- (BOOL)createNewHouseWithStreet:(NSString *)paramStreet numOfStories: (NSUInteger)paramNumOfStories
{
BOOL result = NO;
if ([paramStreet length] == 0)
{
NSLog(#"Street name is mandatory");
return NO;
}
House *newHouse = [NSEntityDescription insertNewObjectForEntityForName:#"House" inManagedObjectContext:self.managedObjectContext];
if (newHouse == nil)
{
NSLog(#"Failed to create the new House");
return NO;
}
newHouse.street = paramStreet;
newHouse.numOfStories = #(paramNumOfStories);
NSError *savingError = nil;
if ([self.managedObjectContext save:&savingError])
{
return YES;
}
else
{
NSLog(#"Failed the save the new House. Error = %#", savingError);
}
return result;
}
and below this I have
- (IBAction)save:(id)sender {
[self createNewHouseWithStreet:#"10 WYOMING" numOfStories:2];
[self createNewHouseWithStreet:#"6 WYOMING" numOfStories:3];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"House"];
NSError *requestError = nil;
NSArray *houses = [self.managedObjectContext executeFetchRequest:fetchRequest error:&requestError];
if ([houses count] > 0)
{
NSUInteger counter = 1;
for (House *thisHouse in houses)
{
NSLog(#"House %lu Street Name = %#", (unsigned long)counter, thisHouse.street);
NSLog(#"House %lu number of stories = %ld", (unsigned long)counter, (unsigned long)[thisHouse.numOfStories unsignedIntegerValue]);
counter++;
}
}
else
{
NSLog(#"Could not find any House entities in the context.");
}
}
So what's weird is that this code ends up resulting in sigabrt whenever I tap the save button, but when I put the code from the save method into my AppDelegate.m didFinishLaunchingWithOptions method it works great.
All help is appreciated, thanks.

Mysterious EXC_BAD_ACCESS with no zombies or uninitialized variables

My app crashes on the following line:
sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
in the method of the FMDB sqlite wrapper:
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
if (![self databaseExists]) {
return 0x00;
}
if (inUse) {
[self warnInUse];
return 0x00;
}
[self setInUse:YES];
FMResultSet *rs = nil;
int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *statement = 0x00;
if (traceExecution && sql) {
NSLog(#"%# executeQuery: %#", self, sql);
}
if (shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
}
int numberOfRetries = 0;
BOOL retry = NO;
if (!pStmt) {
do {
retry = NO;
const char *sqlStatement = [sql UTF8String];
rc = sqlite3_prepare_v2(db, sqlStatement, -1, &pStmt, 0);
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
retry = YES;
usleep(20);
if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
NSLog(#"%s:%d Database busy (%#)", __FUNCTION__, __LINE__, [self databasePath]);
NSLog(#"Database busy");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
else if (SQLITE_OK != rc) {
if (logsErrors) {
NSLog(#"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(#"DB Query: %#", sql);
#ifndef NS_BLOCK_ASSERTIONS
if (crashOnErrors) {
NSAssert2(false, #"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
}
#endif
}
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
while (retry);
}
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
while (idx < queryCount) {
if (arrayArgs) {
obj = [arrayArgs objectAtIndex:idx];
}
else {
obj = va_arg(args, id);
}
if (traceExecution) {
NSLog(#"obj: %#", obj);
}
idx++;
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
if (idx != queryCount) {
NSLog(#"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
[statement retain]; // to balance the release below
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
if (shouldCacheStatements) {
[self setCachedStatement:statement forQuery:sql];
}
}
// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[openResultSets addObject:openResultSet];
statement.useCount = statement.useCount + 1;
[statement release];
[self setInUse:NO];
return rs;
}
The app crashes with EXC_BAD_ACCESS. I have tried to find out why by debugging with NSZombieEnabled and malloc_history, but it does not give me any answers. Also - the debugger tells me that the sql variable has a retain count of a very large number (which is probably because it is a static NSString) - so the EXC_BAD_ACCESS should not be because of the sql object being over-relesed.
Does anyone have any ideas on how to further debug this to find out what the problem is?
Solution: The problem was that my database was accessed by several threads. And even if all threads had synchronized access to the database handle, for sqlite versions prior to 3.3.1 (iOS uses 3.0) you can not safely use the same database handle across threads.
My solution was to create on-demand handles to the database for each thread that tries to access the database, like this:
- (ADatabaseConnection *)databaseConnection {
NSDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSString *key = #"aDatabaseConnection";
ADatabaseConnection *connection = [dictionary objectForKey:key];
if (connection == nil) {
connection = [[[ADatabaseConnection alloc] initWithDatabase:self] autorelease];
[dictionary setValue:connection forKey:key];
}
return connection;
}
Note that for sqlite versions >= 3.3.1, this is not needed as the same handle can be used across threads.
Another important thing to remember is that even if you use this approach to safely use the same database across threads, it might be wise to synchronize access to the database so that you do not access it simultaneously anyway to avoid database lock errors. I do both, use one handle for each thread and synchronize on the database.
It's not safe to use FMDatabase from multiple threads at the same time- so I've started work on a new class to help you make queries and updates from multiple threads using a pool. Right now it's on a branch, which you can view here:
https://github.com/ccgus/fmdb/tree/threadtests
Read the section titled "Using FMDatabasePool and Thread Safety."

Send email to multiple recipients with SKPSMTPMessage?

I need to send email in background, so I have to use the library named: SMTP. And the main class I used is: SKPSMTPMessage. The problem is "ccEmail", when I add more than 2 recipients, it can't send email. (that takes too long time to go to delegate methods). It works well with recipient <= 2.
smtpEmail.ccEmail = #"xyz#gmail.com, xyz1#gmail.com, xyz2#gmail.com";
Anyone knows this, please help me. Thanks you so much !
There is my changes in the parseBuffer function:
case kSKPSMTPWaitingFromReply:
{
if ([tmpLine hasPrefix:#"250 "]) {
if (!multipleRcptTo) {
NSMutableString *multipleRcptToString = [NSMutableString string];
[multipleRcptToString appendString:[self formatAddresses:toEmail]];
[multipleRcptToString appendString:[self formatAddresses:ccEmail]];
[multipleRcptToString appendString:[self formatAddresses:bccEmail]];
multipleRcptTo = [[multipleRcptToString componentsSeparatedByString:#"\r\n"] mutableCopy];
[multipleRcptTo removeLastObject];
}
if ([multipleRcptTo count] > 0) {
NSString *rcptTo = [NSString stringWithFormat:#"%#\r\n", [multipleRcptTo objectAtIndex:0]];
[multipleRcptTo removeObjectAtIndex:0];
//DEBUGLOG(#"C: %#", rcptTo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[rcptTo UTF8String], [rcptTo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
if ([multipleRcptTo count] == 0) {
sendState = kSKPSMTPWaitingToReply;
}
}
break;
}
and add this into header:
NSMutableArray *multipleRcptTo;
EDIT : Also change below method as multipleRcptTo is used as NSMutableString which is local declaration :
- (NSString *)formatAddresses:(NSString *)addresses {
NSCharacterSet *splitSet = [NSCharacterSet characterSetWithCharactersInString:#";,"];
NSMutableString *multipleRcpt = [NSMutableString string];
if ((addresses != nil) && (![addresses isEqualToString:#""])) {
if( [addresses rangeOfString:#";"].location != NSNotFound || [addresses rangeOfString:#","].location != NSNotFound ) {
NSArray *addressParts = [addresses componentsSeparatedByCharactersInSet:splitSet];
for( NSString *address in addressParts ) {
[multipleRcpt appendString:[self formatAnAddress:address]];
}
}
else {
[multipleRcpt appendString:[self formatAnAddress:addresses]];
}
}
return(multipleRcpt);
}
SKPSMTPMessage sends to the SMTP address all at once, and must send one by one.