Eu armazeno um carimbo de data/hora da última modificação no banco de dados nos registros de dados principais no telefone e nas tabelas mysql no servidor.
O telefone procura tudo o que mudou desde a última sincronização e o envia para o servidor junto com um carimbo de data/hora da última sincronização, e o servidor responde com tudo o que mudou desde o carimbo de data/hora de sincronização fornecido.
O desempenho é um problema quando muitos registros foram alterados. Eu faço a sincronização em um NSOpeartion em segundo plano que possui seu próprio contexto de objeto gerenciado. Quando o thread em segundo plano termina de fazer alterações no contexto de objeto gerenciado, há uma API para mesclar todas as alterações no contexto de objeto gerenciado do thread principal - que pode ser configurado para simplesmente descartar todas as alterações se houver algum conflito causado por o usuário alterando os dados enquanto a sincronização está em andamento. Nesse caso, apenas espero alguns segundos e tento fazer uma sincronização novamente.
Em hardware mais antigo, mesmo após muitas otimizações, era necessário abortar totalmente a sincronização se o usuário começasse a fazer coisas no aplicativo. Estava simplesmente usando muitos recursos do sistema. Acho que os dispositivos iOS mais modernos provavelmente são rápidos o suficiente para que você não precise mais fazer isso.
(a propósito, quando eu disse "muitos registros mudaram", eu quis dizer 30.000 ou mais linhas sendo atualizadas ou inseridas no telefone)