1. 程式人生 > >Realm原始碼分析之copyToRealm與copyToRealmOrUpdate

Realm原始碼分析之copyToRealm與copyToRealmOrUpdate

createObject

  1. Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。

  2. createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者

    createObject(Class<E> clazz)
    
    createObject(Class<E> clazz, Object primaryKeyValue)
  3. 注意:每次使用createObject都會建立新的Model例項。

copyToRealm與copyToRealmOrUpdate

  1. 先上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
    .age = age; } }
  2. 使用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);
            }
        });
    }
  3. 開始追蹤copyToRealm,如下原始碼:

    public <E extends RealmModel> E copyToRealm(E object) {
        checkNotNullObject(object);
        return copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>());
    }
  4. 上面程式碼先做了物件合法性判斷,然後呼叫了copyOrUpdate這個靜態方法,如下程式碼:

    private <E extends RealmModel> E copyOrUpdate(E object, boolean update, Map<RealmModel, RealmObjectProxy> cache) {
        checkIfValid();
        return configuration.getSchemaMediator().copyOrUpdate(this, object, update, cache);
    }
  5. 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);
    }
  6. 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);
    }
  7. 上述程式碼,先是做了合法性判斷,然後從快取中取例項並返回,取不到就呼叫如下的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;
    }
  8. 上面方法再次取快取並返回,取不到就呼叫createObjectInternal方法建立例項,並未屬性賦值。而createObjectInternal已經在Realm原始碼分析之Writes中已經分析過了,此處不再展開了。

  9. 至此可以得出copyToRealm與createObject的區別:copyToRealm會複用Realm中快取的Model例項。

  10. 繼續使用上述的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)
  11. 上面的log很明顯了,目前可以確定:copyToRealmOrUpdate方法是給指明瞭主鍵的model使用的。

  12. 讓我們來揭開其神奇面紗,程式碼如下:

    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());
        }
    }
  13. 上述程式碼先是物件判空,然後進行主鍵判斷,最後呼叫的是copyOrUpdate方法。到目前為止,copyToRealm與copyToRealmOrUpdate唯一區別是呼叫copyOrUpdate這個靜態方法傳遞的第二個引數不同,如下:

    //copyToRealm
    copyOrUpdate(object, false, new HashMap<RealmModel, RealmObjectProxy>())
    
    //copyToRealmOrUpdate
    copyOrUpdate(object, true, new HashMap<RealmModel, RealmObjectProxy>())
  14. 緊接著回去看6中的DogRealmProxy的copyOrUpdate與copy方法,可以發現這個boolean引數實際是沒有使用的。因此,可以得出:對於未指明主鍵的model,其copyToRealm與copyToRealmOrUpdate底層實現是一致的。

  15. 接下來修改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;
        }
    }
  16. 發現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);
        }
    }
  17. 相比沒有加主鍵的方法,在取不到快取之後它增加了去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;
    }
  18. 上面的update方法其實賦值操作,而copyToRealm與copyToRealmOrUpdate就到此為止了。

結論

  1. copyToRealm與copyToRealmOrUpdate:前者是給未指明主鍵的model使用的,後者則是給指明主鍵的model使用。

  2. copyToRealm對應無主鍵引數的createObject,區別是copyToRealm會複用Realm快取的model示例,不會每次都建立新的model例項。

  3. copyToRealmOrUpdate對應有主鍵引數的createObject,區別是copyToRealmOrUpdate會複用Realm快取的model示例,沒有快取例項就要去表中查詢是否有匹配這個主鍵存在的記錄,來決定是新建物件還是更新model例項。