Realm原始碼分析之copyToRealm與copyToRealmOrUpdate
createObject
在Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。
createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者:
createObject(Class<E> clazz) createObject(Class<E> clazz, Object primaryKeyValue)
注意:每次使用createObject都會建立新的Model例項。
copyToRealm與copyToRealmOrUpdate
先上Model示例程式碼,注意是沒有指明主鍵的:
public class Dog extends RealmObject { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this
使用copyToRealm建立model示例,如下程式碼:
public void copyToRealm() { realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myDog = new Dog(); myDog.setName("Copy"); myDog.setAge(6); realm.copyToRealm(myDog); } }); }
開始追蹤copyToRealm,如下原始碼:
public <E extends RealmModel> E copyToRealm(E object) { checkNotNullObject(object); return copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>()); }
上面程式碼先做了物件合法性判斷,然後呼叫了copyOrUpdate這個靜態方法,如下程式碼:
private <E extends RealmModel> E copyOrUpdate(E object, boolean update, Map<RealmModel, RealmObjectProxy> cache) { checkIfValid(); return configuration.getSchemaMediator().copyOrUpdate(this, object, update, cache); }
在Realm原始碼分析之初始化中已經分析過這個Mediator例項是DefaultRealmModuleMediator,它是有註解處理器自動生成,如下程式碼:
public <E extends RealmModel> E copyOrUpdate(Realm realm, E obj, boolean update, Map<RealmModel, RealmObjectProxy> cache) { // This cast is correct because obj is either // generated by RealmProxy or the original type extending directly from RealmObject @SuppressWarnings("unchecked") Class<E> clazz = (Class<E>) ((obj instanceof RealmObjectProxy) ? obj.getClass().getSuperclass() : obj.getClass()); if (clazz.equals(com.example.app.org.realm.demo.Dog.class)) { return clazz.cast(io.realm.DogRealmProxy.copyOrUpdate(realm, (com.example.app.org.realm.demo.Dog) obj, update, cache)); } throw getMissingProxyClassException(clazz); }
DefaultRealmModuleMediator只是做了中轉,而是由DogRealmProxy負責實現,如下程式碼:
public static com.example.app.org.realm.demo.Dog copyOrUpdate(Realm realm, com.example.app.org.realm.demo.Dog object, boolean update, Map<RealmModel,RealmObjectProxy> cache) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (com.example.app.org.realm.demo.Dog) cachedRealmObject; } return copy(realm, object, update, cache); }
上述程式碼,先是做了合法性判斷,然後從快取中取例項並返回,取不到就呼叫如下的copy方法:
public static com.example.app.org.realm.demo.Dog copy(Realm realm, com.example.app.org.realm.demo.Dog newObject, boolean update, Map<RealmModel,RealmObjectProxy> cache) { RealmObjectProxy cachedRealmObject = cache.get(newObject); if (cachedRealmObject != null) { return (com.example.app.org.realm.demo.Dog) cachedRealmObject; } // rejecting default values to avoid creating unexpected objects from RealmModel/RealmList fields. com.example.app.org.realm.demo.Dog realmObject = realm.createObjectInternal(com.example.app.org.realm.demo.Dog.class, false, Collections.<String>emptyList()); cache.put(newObject, (RealmObjectProxy) realmObject); DogRealmProxyInterface realmObjectSource = (DogRealmProxyInterface) newObject; DogRealmProxyInterface realmObjectCopy = (DogRealmProxyInterface) realmObject; realmObjectCopy.realmSet$name(realmObjectSource.realmGet$name()); realmObjectCopy.realmSet$age(realmObjectSource.realmGet$age()); return realmObject; }
上面方法再次取快取並返回,取不到就呼叫createObjectInternal方法建立例項,並未屬性賦值。而createObjectInternal已經在Realm原始碼分析之Writes中已經分析過了,此處不再展開了。
至此可以得出copyToRealm與createObject的區別:copyToRealm會複用Realm中快取的Model例項。
繼續使用上述的Dog,使用copyToRealmOrUpdate來建立如下程式碼:
public void copyToRealmOrUpdate() { realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myDog = new Dog(); myDog.setName("Copy Update"); myDog.setAge(6); realm.copyToRealmOrUpdate(myDog); } }); }
直接執行的話會crash,log如下:
Caused by: java.lang.IllegalArgumentException: A RealmObject with no @PrimaryKey cannot be updated: class com.example.app.org.realm.demo.Dog at io.realm.Realm.checkHasPrimaryKey(Realm.java:1615) at io.realm.Realm.copyToRealmOrUpdate(Realm.java:1030) at com.example.app.org.realm.demo.RealmActivity$5.execute(RealmActivity.java:123) at io.realm.Realm$1.run(Realm.java:1508) at io.realm.internal.async.BgPriorityRunnable.run(BgPriorityRunnable.java:34) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:818)
上面的log很明顯了,目前可以確定:copyToRealmOrUpdate方法是給指明瞭主鍵的model使用的。
讓我們來揭開其神奇面紗,程式碼如下:
public <E extends RealmModel> E copyToRealmOrUpdate(E object) { checkNotNullObject(object); checkHasPrimaryKey(object.getClass()); return copyOrUpdate(object, true, new HashMap<RealmModel, RealmObjectProxy>()); }
private void checkHasPrimaryKey(Class<? extends RealmModel> clazz) { if (!schema.getTable(clazz).hasPrimaryKey()) { throw new IllegalArgumentException("A RealmObject with no @PrimaryKey cannot be updated: " + clazz.toString()); } }
上述程式碼先是物件判空,然後進行主鍵判斷,最後呼叫的是copyOrUpdate方法。到目前為止,copyToRealm與copyToRealmOrUpdate唯一區別是呼叫copyOrUpdate這個靜態方法傳遞的第二個引數不同,如下:
//copyToRealm copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>()) //copyToRealmOrUpdate copyOrUpdate(object, true, new HashMap<RealmModel, RealmObjectProxy>())
緊接著回去看6中的DogRealmProxy的copyOrUpdate與copy方法,可以發現這個boolean引數實際是沒有使用的。因此,可以得出:對於未指明主鍵的model,其copyToRealm與copyToRealmOrUpdate底層實現是一致的。
接下來修改model並指明主鍵,如下程式碼:
public class Dog extends RealmObject { @PrimaryKey private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
發現DogRealmProxy中的copyOrUpdate方法的實現已經改變了,如下程式碼:
public static com.example.app.org.realm.demo.Dog copyOrUpdate(Realm realm, com.example.app.org.realm.demo.Dog object, boolean update, Map<RealmModel,RealmObjectProxy> cache) { if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().threadId != realm.threadId) { throw new IllegalArgumentException("Objects which belong to Realm instances in other threads cannot be copied into this Realm instance."); } if (object instanceof RealmObjectProxy && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm() != null && ((RealmObjectProxy) object).realmGet$proxyState().getRealm$realm().getPath().equals(realm.getPath())) { return object; } final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); RealmObjectProxy cachedRealmObject = cache.get(object); if (cachedRealmObject != null) { return (com.example.app.org.realm.demo.Dog) cachedRealmObject; } com.example.app.org.realm.demo.Dog realmObject = null; boolean canUpdate = update; if (canUpdate) { Table table = realm.getTable(com.example.app.org.realm.demo.Dog.class); long pkColumnIndex = table.getPrimaryKey(); String value = ((DogRealmProxyInterface) object).realmGet$name(); long rowIndex = Table.NO_MATCH; if (value == null) { rowIndex = table.findFirstNull(pkColumnIndex); } else { rowIndex = table.findFirstString(pkColumnIndex, value); } if (rowIndex != Table.NO_MATCH) { try { objectContext.set(realm, table.getUncheckedRow(rowIndex), realm.schema.getColumnInfo(com.example.app.org.realm.demo.Dog.class), false, Collections.<String> emptyList()); realmObject = new io.realm.DogRealmProxy(); cache.put(object, (RealmObjectProxy) realmObject); } finally { objectContext.clear(); } } else { canUpdate = false; } } if (canUpdate) { return update(realm, realmObject, object, cache); } else { return copy(realm, object, update, cache); } }
相比沒有加主鍵的方法,在取不到快取之後它增加了去Table中查詢主鍵的過程,如果在Table找到了匹配的主鍵就會新建一個DogRealmProxy物件並快取起來,最後執行update操作,其原始碼如下:
static com.example.app.org.realm.demo.Dog update(Realm realm, com.example.app.org.realm.demo.Dog realmObject, com.example.app.org.realm.demo.Dog newObject, Map<RealmModel, RealmObjectProxy> cache) { DogRealmProxyInterface realmObjectTarget = (DogRealmProxyInterface) realmObject; DogRealmProxyInterface realmObjectSource = (DogRealmProxyInterface) newObject; realmObjectTarget.realmSet$age(realmObjectSource.realmGet$age()); return realmObject; }
上面的update方法其實賦值操作,而copyToRealm與copyToRealmOrUpdate就到此為止了。
結論
copyToRealm與copyToRealmOrUpdate:前者是給未指明主鍵的model使用的,後者則是給指明主鍵的model使用。
copyToRealm對應無主鍵引數的createObject,區別是copyToRealm會複用Realm快取的model示例,不會每次都建立新的model例項。
copyToRealmOrUpdate對應有主鍵引數的createObject,區別是copyToRealmOrUpdate會複用Realm快取的model示例,沒有快取例項就要去表中查詢是否有匹配這個主鍵存在的記錄,來決定是新建物件還是更新model例項。