How to search iOS Address book efficiently - iphone

My app has the ability of sharing its content between different instances of the app i.e. there is a button that lets me attach a document to an email and send it off to another user of the app. When they receive this file it opens up in their instance of the app. This is all working.
However, during the import of this data file, I need the app to add address book records to the address book unless the email address is already in the address book then it must just return the ABRecordRef.
I need to decide which approach is more efficient:
A - Loop through the entire address book creating an array of user defined objects with name and email address (which is all I require) and ABRecordRef. Then when the app is reading the attachment file XML it must check this NSArray each time.
B - Access the address book every time I encounter a contact in the attachment XML and search for it based on email address e.g. ABAddressBookCopyArrayOfAllPeople
Any other ideas?

Note: this applies to searching by e-mail address. If you are searching by name this does not apply.
For an arbitrarily sized address book with a fixed size number of addresses to lookup, searching the address book will be faster. This is because the initial cost of putting all the addresses into a hash is guaranteed to take at least linear (O(n)) time with respect to the number of addresses in the book while (assuming the address book is at least moderately efficien) lookup should be either logarithmic (O(ln(n)) or constant time (O(x)) depending on the implementation since you have a fixed number of addresses to check.
Edit:
A brief survey of the address book api shows that you can't simply "search the address book", you have to make a copy and then filter it.
Using Record Identifiers
Every record in the Address Book database has a unique record identifier. This identifier always refers to the same record, unless that record is deleted or the MobileMe sync data is reset. Record identifiers can be safely passed between threads. They are not guaranteed to remain the same across devices.
The recommended way to keep a long-term reference to a particular record is to store the first and last name, or a hash of the first and last name, in addition to the identifier. When you look up a record by ID, compare the record’s name to your stored name. If they don’t match, use the stored name to find the record, and store the new ID for the record.
To get the record identifier of a record, use the function ABRecordGetRecordID. To find a person record by identifier, use the function ABAddressBookGetPersonWithRecordID. To find a group by identifier, use the function ABAddressBookGetGroupWithRecordID. To find a person record by name, use the function ABAddressBookCopyPeopleWithName.

Given the limitations of the address book in iOS, copying the address book into a NSDictionary is going to be faster than trying to search each time.
Assuming you are fine with overwriting on email address collisions, I would create a dictionary with the email address as the key and the ABRecordRef stored as a NSNumber for the object.
I would do all this on a background queue and then monitor for address book changes and reload the dictionary as needed.
We've encountered users with several thousand entries in their address books. In those cases, you may want to create a record object and store it in Core Data. By filtering on the last modified date, you can determine whether or not to update the email addresses for a given record.

Related

Efficient way to find if an IP is in a list of subnets that are stored in DynamoDB

I'm trying to create an API that I can send an IP address to and the response will contain the subnet that the IP belongs to (if it belongs to any in the table).
I have a list of subnets all stored in a table in DynamoDB like such:
subnet
45.221.27.0/24
102.215.216.0/23
192.168.0.0/16
etc...
I can't seem to figure out how I could efficiently query the table to determine which subnet an IP belongs to. I am using a Lambda to make the request so I am trying to avoid reading all the subnets in because that will use a lot of memory. I'm also trying to avoid scanning the table rather than querying because that can become too expensive.
I've been thinking about different ways of storing the subnets in the table such that it becomes possible to get more granular with queries but I also feel like I'm overcomplicating something that shouldn't be so complex.
How funny, I'm actually writing a blog on this. I'll add the link once it's published. There's a lot of interesting scaling topics related to this problem for how to load and query with max efficiency. Here's the simplest approach:
Use a singular Partition Key value (that is, the same for all items). Use the range start IP address as the Sort Key. But make it the 32-bit numeric value of the IP address not the string value, because we need to sort by it and sorting by the string value is problematic. (All IP addresses are really just 32-bit numbers underneath.) The other attributes will be the metadata you want to retrieve.
The lookup then is to issue a Query where the PK is the singular value and the SK is <= the lookup IP address (in numeric form).
The one caveat is you need to make sure that any gaps in the IP address range data set need to be filled during the load with marker items saying "gap here", otherwise a lookup that hits the gap will return the range ahead of the gap.

Entity Framework - How to manage suburb and state date across multiple databases

I have an SaaS application in the pipeworks.
One of the things that has me a bit confused is the best way to manage the stable of Austalian suburb and state data across multiple databases (this applies to any country as each country has a list like this).
For example in Australia you have Australian Postcode list that links all the postcodes to the suburbs and you can use that to create a dropdown for state, suburb and postcode etc.
An example of the CSV of australian postcodes can be found HERE.
So you can upload a csv file for example but the problem remains..
Whats the best way to hold this data.. its common to all databases where you have a person, client, employee etc..
Do you replcate it in each database? Is there a better way than having redundant stores of data..
Best way to implement it..
There are several options and considerations I would look at for this problem. Some considerations:
Number of address rows expected
Whether a client database is concerned with prefill/validated international addresses
Whether the client system is web connected or can operate in isolation
Are these databases/systems hosted by you or distributed to individual clients? (SaaS implies "Web" and "Hosted by You" to points 3 & 4)
How critical address integrity is.
For smaller systems, a simple option for address systems is to de-normalize the address data (state, postcode, suburb) and consider using a central lookup database/service, either under your own control or a third party. The denormalized address table would contain the text fields for the State, Postcode, Suburb etc. rather than FK values (stateId, suburbId, etc.) This avoids needing to store lookup tables in every client DB, just one Lookup DB or leave that to a 3rd party service.
The advantage of a third-party lookup is that keeping it up to date with new areas and changes is handled for you. Third party services would require a web connection, and you have to factor in the risk of their service being down or a web connection being unavailable. Larger systems with millions of addresses might benefit from normalizing the address table, so the "cost" of replicating suitable address lookup tables might be worthwhile. You can still a central service to look up addresses, then resolve whether the client DB already has a StateId, SuburbId etc. for the respective state/suburb for that post-code before inserting one if necessary. (Cutting down the number of rows each client DB needs to address values that are actually used)
In that last example you might have lookup tables for State and Suburb linked to PostCodes, linked to Country. Country would default to the target, maybe be an optional selection for international addresses. The user provides a post code to the service which returns suburbs, they select a suburb. The address validation service could go as far as to validate the street address. When you're happy an address is "valid" and ready to be saved, you search your local State, Suburb, (even Street) tables for matches for that PostCode, if found use those FKs, otherwise insert new entries and link the FK.
Using a separate service, or services would be my consideration especially if you need to support validating/storing international addresses. For instance if the client is in Australia but regularly has address information for New Zealand. Storing entire address validation tables could get rather large if clients could be resolving addresses for many countries. (I.e. European countries and neighbours) You can write a Façade service to support different 3rd party address validation providers and/or homemade implementations with a standard interface.
If a system has to operate in isolation of an internet connection then you'll probably be stuck with each database having one or more local data sources to resolve address information.
Data integrity of address information is a separate concern you might want to consider. In some systems you need to validate that an address is recognized and don't want to allow invalid combinations or detect unexpected changes. Services that validate a particular address can provide unique IDs for an address that you can store as part of your address information. (These often tie into geocoordinate solutions where you want to quickly direct a map service to a particular location) Alternatively, if you successfully look up an address then validate that the address information is valid, even if just the country, post code, and suburb, you can create and store a hash of those values to check for tampering. (I.e. someone or some system changed a field to make the address invalid, the combined address won't match the stored hash) Addresses can be checked before use and flagged if not valid.

CloudKit, join or efficient way to add an item to list

I'm using CloudKit to manage a list of messages (record type Message with a field title and body). All messages are public and I want to maintain which Message the user has read using the mobile app.
The app can have thousands of users and messages. And I use swift3.
I've think of different way to do it but they seems quite poor in term of performance:
add a field 'readers' to Message which is a list of string corresponding of user Id. The problem is that if I want to add a new user ID I must load all the list. This is problematic for a mobile app in case of a lot of users have read the message. Can I lazy fetch a list field and add a value to it without downloading all the list (like in classic Orm)?
add another record type 'Reader' which has two fields: a user ID and a message ID. I can't find a way to join Message and Reader in a predicate to download only Message that the user hasn't read. Is that possible?
As suggested by Matthew: add a record type ReadArticle in the private database that stores only a CKReference to a message. The problem is that we need to download all message ID before sending them in a NOT predicate.
I don't know how to solve this problem with a database like CloudKit.
Any advice ?
Readers field
This approach is not ideal for the exact reasons you pointed out, and it additionally is unsafe as a String could theoretically contain anything.
Reader object
Even if there was a way to get this to work you'd be storing user ids in the public database. That's probably not a privacy-conscious thing to do.
ReadMessage Record in Private Database
Suppose you had a Record Type called ReadArticle. This object would contain exactly one field, a CKReference to a Message record.
Then, when someone reads a Message, you take the recordId of that Message, create a CKReference from it, and place that reference in the "message" field of a new CKRecord object of type ReadMessage. Then, you save it to the user's private database. Because it's a CKReference, it won't actually take up hardly any space in iCloud because it's just a pointer, and because you'd use the user's private database there's no need to explicitly identify the user.
Then when you want all unread messages, fetch the ReadMessage record, and create a NOT predicate to receive all Message records where the record id is not any of the read ones. CloudKit definitely supports NOT predicates, but if it happens to not support NOT predicates specifying record ids, than you could use some other unique field on message instead.

Do Salesforce VF email templates require related object to be persisted?

When a new lead comes in, I want to use a before trigger and a Visualforce email template that contains lead field values to send an email using the SingleEmailMessage class. The email is being generated, but all of the lead fields are null even though (known via System.Debug) they do have values going into the call.
Since I'm passing the still-unsaved lead Id via the mail.setWhatId(lead.Id) method, I'm beginning to think that the mail class is using the Id value and trying to do a database look-up rather than as a reference to the still unsaved lead in memory.
Does anyone know if that's the case? My class works flawlessly when the lead already exists.
If it is the case that the Apex mail class does a DB read, any pattern suggestions for the case where one needs to send and email and update a lead field value before the lead is saved? I can't use the Workflow email notification because the email is being addressed to customers, and there's some additional Apex code that sorts out what address to fetch from existing Account records based on some Lead fields--hence I think the need for using VF email templates in the first place.
setWhatId (and pretty much any method that takes an ID value as an argument) definitely does expect the row to be persisted already. To get around this, you should be able to just do your field update in the before trigger, and add an after trigger to send the email.

International Registration Form

When designing an internation registration form how should I be asking for a user's information. Should name be [First, Last] or [Common, Surname] or simply [Name]. Does first and last name make sense for all names?
When asking for state and zipcode what would alternative terminology be, should I even be collecting this information for some countries?
We're recently getting into international users and methods and I'm hoping someone with experience can weigh in on the proper fields and labels that would be present in a solid international registration form.
I would reccomend just a single input for name, not all names are logically separated into just two parts, some people have just one name (such as Teller, and if used for addresses, the name may be a company name). Labeling is complicated since first and last name is not the same as given and family name, if names in your language is normally in which field should the first/family name go and the last/given name go?
For an address input you need:
Name/Company: <text box; no validation or munging>
Address: <Multiline textbox; no validation or munging>
Country: <text box or select-box if you're paranoid>
Making it more detailed and you're creating problems for foreigners.
The only thing your local post service need for a foreign address is the name of the country. The rest is country specific and may include several fields or in some cases something like "behind the church in the woods" etc.
Do not include zip code or anything country specific like that unless the user has chosen a country you have extensive knowledge about. People tend to know how to type their address in a free form text area, but they may actually mess it up if they have to force their address into foreign fields.
The worst you can do is silently discarding invalid fields. That happened to me when registering my address (in Norway) for a magazine subscription from USA. The norwegian addres format is:
<Name or company>
<Street name> <house number>
<post code> <city or suburb>
NORWAY
But when trying to force these fields into the closest equivalent in a very US-centric form they ended up mailing it to:
<my name> Oslo/OSLO//<And a lot of garbage letters and symbols>
/NORWAY
It eventually arrived, but it was a month delayed. It probably helped that I have a unique name, and that we're only about 5 million people in Norway. I doubt it would have arrived if I my name was "John Smith" and lived in New York and some web form had munged it to John Smith NY/New York;/ USA///
Sorry for the long rant ;)
I would suggest just the name, so that user could enter whatever seems right.
As for State, I quite don't understand what you need this information for. But I am not U.S. citizen, so...
Two address lines (without implied format), zip code (terminology is OK, but do not try to validate) and country name should be sufficient. The problem with "States" is, some countries do not use such concepts (either at all, or use different regional split concepts, i.e. in Poland it is voivodship, in England it would be county, etc.). Again, I wouldn't use such details.
How you accept the user data is based on what you want to do with it. Having said that
Name: This is also country specific but based on the suggestions I've got "family name" and "given name" are generic/common across any country. Some names have multiple "family name"'s.
Address: Different countries have different address formats. You can get different format information here
As a non-US resident, who has a very non-standard address (live on a boat, which requires additional address information, and very often does not have a Royal Mail standarised address) I do find that US-centric registration forms are very irritating. As far as name goes, I have found first and last name appropriate. For addresses I would consider either leaving labels off for most lines or labelling them innocuously as "address line 1", etc. In addition make sure that there are a number of such lines; the dialog box is long enough to accept multiple fields; and the dialogs will accept address delimiters such as comma. The only ones I would leave as they often displayed are Town/City and Country. Zipcodes should not be referred to as such - the minimum courtesy I would expect from a registration form is a request for Zip/Post code.
Though not directly related to registration forms, I have previously provided a (very long) answer to a question on validation/normalisation of international data. It is not a simple topic.