Main Points of this paper

  • Analyze the entrustment mode and agent mode

  • Delegated Interface

  • Delegated Properties

  • Map Delegation

  • Lazy properties

  • Delegates. NotNull property:…

  • Monitoring after variable value update: Delegates. Observable

  • Delegates vetoable before variable value update

  • ViewBinding+ Attribute delegate

  • ViewModel+ property delegate

  • SP+ attribute delegate

1. Delegate vs. proxy

Both delegate pattern and proxy pattern belong to structural design pattern, which mainly summarizes some classical structures of classes or objects combined together, and these classical structures can solve the problems of specific application scenarios.

1.1 Proxy Pattern

Defining a proxy class for the primitive class without changing the interface of the primitive class is the primary purpose of controlling access rather than enhancing functionality, which is the biggest difference from the decorator pattern. Additional logic can be provided in the proxy class, such as adding caching operations to the proxy when operations on real objects are resource-intensive tasks, or doing some permission checking or checking behavior before calling real object operations. In general, we implement the proxy pattern by having the proxy class implement the same interface as the original class or by having the proxy class inherit from the original class.

1.2 Delegation Pattern

Delegates can be thought of as a variation on proxies, another design pattern that enables composition to reuse code like inheritance. The main goal is to combine delegate calls and code reuse without regard to logic such as control access. Processing a request in a delegate involves two objects, and the delegate hands off the operation to the delegate implementation, similar to a subclass deferring the request to its parent class. In general, delegation can also realize the delegation mode through interface constraint or the entrusted party inheriting the principal.

1.3 The legal level distinguishes between agency and entrustment

To deepen the understanding of agency and delegation, let’s look at the differences between agency and delegation at the legal level

  • The name of civil subject activity is different. Agency refers to a kind of legal system in which the principal acts independently with a third party in the name of the principal within the scope of agency authority, and the resulting legal effect is directly attributable to the principal, that is, the agent must act as agent in the name of the principal. Entrustment is an agreement whereby the principal entrusts the agent to handle certain affairs and the agent accepts the entrustment. The agent can act in the name of the principal or in his own name.

  • The scope of application is not the same: agency is only the agent within the scope of agency in the name of the principal and the third party’s civil legal acts; The trustee’s behavior of handling entrusted affairs can be civil legal behavior, economic behavior (such as sorting out accounting books) and simple factual behavior (such as copying documents).

  • The scope of effectiveness is different: agency involves three parties, namely principal, agent and third party; Entrustment belongs to the relationship between two parties, namely trustor, trustee.

1.4 Code to implement delegate and proxy

There are not too many differences between proxy mode and delegate mode in code implementation, their differences are still in the use of scenarios, in order to better understand the proxy mode and delegate mode, let’s look at how to achieve through code

The code is referenced from: Java-Design-Patterns

Delegation: Printer controller delegates printing tasks to different printers

// Two people: printer controller, printer (HP printer, Canon printer, Epson printer)
// There are three printer drivers on the computer, which respectively delegate the printing task to the corresponding printer to perform the specific printing operation
PrinterController hpPrinterController = new PrinterController(new HpPrinter());
PrinterController canonPrinterController = new PrinterController(new CanonPrinter());
PrinterController epsonPrinterController = new PrinterController(new EpsonPrinter());
hpPrinterController.print(MESSAGE_TO_PRINT);
canonPrinterController.print(MESSAGE_TO_PRINT);
epsonPrinterController.print(MESSAGE_TO_PRINT);

// Printer controller
public class PrinterController implements Printer {
  private final Printer printer;
  public PrinterController(Printer printer) {
    this.printer = printer;
  }
  /** * This method starts with {@linkPrinter} implementation, which calls the delegate's print method passed through the constructor when provided. * This means that once the delegate relationship is established, the caller doesn't care about the implementation class, just the printer controller it owns. * /
  @Override
  public void print(String message) { printer.print(message); }}// Three printers also implement Printer interface and provide specific printing function, code can refer to the link above
Copy the code

Proxy: The ivory Tower can only be accessed by proxy and only the first three wizards can enter.

// Agent trio: Wizard tower agent, Ivory Tower, wizard
WizardTowerProxy proxy = new WizardTowerProxy(new IvoryTower());
proxy.enter(new Wizard("Red wizard"));
proxy.enter(new Wizard("White wizard"));
proxy.enter(new Wizard("Black wizard"));
proxy.enter(new Wizard("Green wizard"));
proxy.enter(new Wizard("Brown wizard"));

// Agent: Tower Agent
public class WizardTowerProxy implements WizardTower {
  private static final int NUM_WIZARDS_ALLOWED = 3;
  private final WizardTower tower;
  private int numWizards;
  // passed by the agent through the constructor
  public WizardTowerProxy(WizardTower tower) {
    this.tower = tower;
  }
  // Proxy for access to ivory Tower. Use the Enter method to enter the third-party interface
  @Override
  public void enter(Wizard wizard) {
    // No entry is allowed for more than three
    if (numWizards < NUM_WIZARDS_ALLOWED) {
      // The agent authorizes the operation
      tower.enter(wizard);
      numWizards++;
    } else {
      System.out.println("{} is not allowed to enter!"+ wizard); }}}// Principal: tower
public class IvoryTower implements WizardTower {
  @Override
  public void enter(Wizard wizard) {
    System.out.println("{} enters the tower."+ wizard); }}// Third party: wizard
public class Wizard {
  private String name;
  public Wizard(String wizard) { this.name = wizard; }
  @Override
  public String toString(a) { returnname; }}Copy the code

2. Delegated Interface

Let’s look at how Kotlin built in interface delegates, and let’s look at the traditional way of implementing delegate patterns, okay

interface Api {
    fun a(a)
    fun b(a)
    fun c(a)
}
/ / the delegate
class ApiImpl : Api {
    override fun a(a) { println("ApiImpl-a")}override fun b(a) { println("ApiImpl-b")}override fun c(a) { println("ApiImpl-c")}}/ / the entrusted party
class ApiWrapper(private val api: Api) : Api {
    override fun a(a) {
        println("ApiWrapper-a")
        api.a()
    }
    override fun b(a) {
        println("ApiWrapper-b")
        api.b()
    }
    override fun c(a) {
        println("ApiWrapper-c")
        api.b()
    }
}
Copy the code

Take a look at Kotlin’s handy way of doing this with the by keyword

// The variable API implements the API instead of the ApiWrapperWithDelegate, and the ApiWrapperWithDelegate can flexibly override the desired function
class ApiWrapperWithDelegate(private val api: Api) : Api by api {
    override fun a(a) {
        println("ApiWrapperWithDelegate-a")}}// Decompilated Java code is the same as the ApiWrapper
public final class ApiWrapperWithDelegate implements Api {
   private final Api api;
   public void a() {
      String var1 = "ApiWrapperWithDelegate-a";
      boolean var2 = false;
      System.out.println(var1);
   }
   public ApiWrapperWithDelegate(@NotNull Api api) {
      Intrinsics.checkNotNullParameter(api, "api");
      super(a);this.api = api;
   }
   public void b() {
      this.api.b();
   }
   public void c() {
      this.api.c(); }}Copy the code

As you can see, Kotlin’s built-in interface delegate is the compiler that generates the code for us

Let’s look at another example in action: implementing a super collection that integrates a Map and a List using an interface proxy

SupperArrayWithDelegate implements MutableList, MutableMap
class SupperArrayWithDelegate<E>(
    private vallist: MutableList<E? > = mutableListOf(),private valmap: MutableMap<String, E> = mutableMapOf() ) : MutableList<E? >by list, MutableMap<String, E> by map {
    // The compiler does not know which interface to execute, so these methods must be overridden
    override fun clear(a) {
        list.clear()
        map.clear()
    }
    override fun isEmpty(a): Boolean {
        return list.isEmpty() && map.isEmpty()
    }
    override val size: Int get() = list.size + map.size
    override fun set(index: Int, element: E?).: E? {
        if (index <= list.size) {
            repeat(index - list.size - 1) {
                list.add(null)}}return list.set(index, element)
    }
    override fun toString(a): String {
        return "list:$list,map:$map"}}Copy the code

3. Delegated Properties

3.1 properties (property)

Let’s first explore the internal implementation of property in KT by comparing Java Field and Kotlin Property

//PersonKotlin is written as Java as possible to compare what is done behind the Kotlin attribute
class PersonKotlin {
    constructor(age: Int, name: String) {
        this.age = age
        this.name = name
    }
    private var age: Int? = null
        The get/set method for the //Redundant getter property is automatically generated by the compiler
        get() {
            return field // backing field = backing field
        }
        set(value) {
            field = value
        }

    private var name: String? = null
        get(a) {
            return field
        }
        set(value) {
            field = value
        }
}

// Decompile the Java code
public final class PersonKotlin {
   private Integer age; //field
   private String name; //field
   private final Integer getAge(a) {
      return this.age;
   }
   private final void setAge(Integer value) {
      this.age = value;
   }
   private final String getName(a) {
      return this.name;
   }
   private final void setName(String value) {
      this.name = value;
   }
   public PersonKotlin(int age, @NotNull String name) {
      Intrinsics.checkNotNullParameter(name, "name");
      super(a);this.setAge(age);
      this.setName(name); }}Copy the code

property(kotlin)=field(java)+getField()+setField()

Backing field, get, and set are the backing roles for the age attribute
private var age: Int? = null
// Equivalent to the following code
@Nullable
private Integer age;
@Nullable
public final Integer getAge(a) {
   return this.age;
}
public final void setAge(@Nullable Integer var1) {
   this.age = var1;
}
Copy the code

Property Reference

Property references can be used to better understand the get, set and proxy information behind a property

val ageRef: KMutableProperty1<PersonKotlin, Int? > = PersonKotlin::age//PersonKotlin::age Class name gets an attribute reference that does not contain receiver, which is passed to the operator
val personKotlin = PersonKotlin(18."Jay")
ageRef.set(personKotlin, 22)
println(ageRef.get(personKotlin))
/ / 22
//public actual fun set(receiver: T, value: V)
//public actual fun get(receiver: T): V
//receiver - Receiver used to get attribute values. For example, if this is a member attribute of the class, it should be an instance of the class, and if this is a top-level extension attribute, it should be an extension sink

// Get the attribute delegate information after testing the custom attribute delegate
valnameRef: KProperty0<String? > = personKotlin::name println("personKotlin: " + personKotlin.hashCode())
println("nameRef: " + nameRef.hashCode())
println("personKotlin.name: " + personKotlin.name.hashCode())

// fetch: is KMutableProperty -> javaField? .isAccessible ? : true && javaGetter? .isAccessible ? : true &&javaSetter? .isAccessible ? : true
// save: is KMutableProperty -> {javaField? .isAccessible = value javaGetter? .isAccessible = value javaSetter? .isAccessible = value }
This attribute needs to be imported separately to the Kotlin-Reflect library
nameRef.isAccessible = true
// Returns the value of the delegate if it is a delegate property, or null if it is not delegate
val nameDelegate = nameRef.getDelegate()
println("NameDelegate:$nameDelegate") // Return the delegate information
println(nameRef.getter.invoke()) // call the get method
//personKotlin: 1751075886
//nameRef: -954731934
//thisRef:1751075886
//property:-954731934
//personKotlin.name: 88377
// You can see that the attribute reference class and its receiver are the same in the delegate class as in hashCode here

//com.jay.lib_kotlin.delegate.MyDelegate@5a63f509
//YYY

// Test the lazy attribute proxy
valsexRef: KProperty0<String? > = personKotlin::sex personKotlin.sex sexRef.isAccessible =true
println(sexRef.getDelegate())
// The agent information obtained is the value in the lazy block: sex is male


// Tests the type of attribute referenced
val kMutableProperty0: KMutableProperty0<Int> = ::sex // Sex is the top-level attribute
val s = kMutableProperty0 as CallableReference
println(s.owner) //file class com.jay.lib_kotlin.property.PersonKotlinKt
// The type of attribute reference is CallableReference
Copy the code

Receiver: Receiver for the attribute value. For example, if this is a member attribute of the class, it should be an instance of the class, and if this is a top-level extension attribute, it should be an extension sink

CallableReference: is a superclass of all classes generated by the Kotlin compiler for CallableReference classes

3.2 Implementation principle of attribute delegate

public open class FooBy {
    // As long as the by keyword is followed by a delegate object, this object does not need to implement a specific interface, as long as it contains getValue/setValue methods, it can be used as a proxy property.
    val y by MyDelegate()
    var w: String by MyDelegate()
}

class MyDelegate {
    var value: String = "YYY"
    // The todo delegate class must provide a getValue method, or extend it
    operator fun getValue(thisRef: Any, property: KProperty< * >): String {
        return value
    }
    operator fun setValue(thisRef: Any, property: KProperty<*>, s: String) {
        value = s
    }
}
Copy the code

Decompiled Java code

public class FooBy {
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(FooBy.class, "y"."getY()Ljava/lang/String;".0)), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(FooBy.class, "w"."getW()Ljava/lang/String;".0))};
   @NotNull
   private final MyDelegate y$delegate = new MyDelegate();
   @NotNull
   private final MyDelegate w$delegate = new MyDelegate();
   @NotNull
   public final String getY(a) {
      return this.y$delegate.getValue(this, $$delegatedProperties[0]);
   }
   @NotNull
   public final String getW(a) {
      return this.w$delegate.getValue(this, $$delegatedProperties[1]);
   }

   public final void setW(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.w$delegate.setValue(this, $$delegatedProperties[1], var1); }}Copy the code

When the following code is called, the get method of fooby. y is called

val foo = FooBy()
println(foo.y)
Copy the code

If you look at the decompiled getY method,

@NotNull
public final String getY(a) {
   return this.y$delegate.getValue(this, $$delegatedProperties[0]);
}
Copy the code

Let’s see what y$delegate is, which is essentially our delegate class and was initialized when the FooBy class was built, okay

@NotNull
private final MyDelegate y$delegate = new MyDelegate();
Copy the code

Look again at the getValue method in MyDelegate, which we must provide in the proxy class

//Java code
@NotNull
public final String getValue(@NotNull Object thisRef, @NotNull KProperty property) {
   Intrinsics.checkNotNullParameter(thisRef, "thisRef");
   Intrinsics.checkNotNullParameter(property, "property");
   return this.value;
}
Copy the code

So now we can see the delegate process

  • When a property in a class is delegated, Kotlin adds an instance of the delegate class to the current class and instantiates the delegate class (Y $Delegate) when the current class is instantiated. The $$delegatedProperties array is also created at class initialization and reflects the class information from all the properties
  • When a delegate property is retrieved, its GET method is called, which returns the proxy class’s getValue method
  • The getValue method is our own implementation, and eventually the proxy property will be assigned a value via getValue
  • SetValue also passes the backing field of the property

3.3 PropertyReferenceImpl

ThisRef: Any property: KProperty<*> thisRef: Any property: KProperty<*

ThisRef is the argument that the business class itself can see passed this when the getValue method is called

Property is the delegate attribute description class KProperty, which takes $$delegatedProperties[0] from the array that Kotlin automatically generates when building the business class and holds the KProperty type describing the attributes of the class

Reflection. Property1 is a factory function that returns the passed argument

The parent class of PropertyReference1Impl also indirectly implements the KProperty interface, so it can be strong here

//$$delegatedProperties
static final KProperty[] $$delegatedProperties =
  new KProperty[]{(KProperty)Reflection.property1(
  new PropertyReference1Impl(FooBy.class, "y"."getY()Ljava/lang/String;".0)), (KProperty)Reflection.mutableProperty1(
  new MutablePropertyReference1Impl(FooBy.class, "w"."getW()Ljava/lang/String;".0))};

Copy the code

Look again at the property reference construction parameter that implements the class PropertyReference1Impl

The constructor of the PropertyReference1Impl class will eventually call its parent, CallableReference

CallableReference: is a superclass of all classes generated by the Kotlin compiler for CallableReference classes.

@SinceKotlin(version = "1.4")
public PropertyReference1Impl(Class owner, String name, String signature, int flags) {
    super(NO_RECEIVER, owner, name, signature, flags);
}
//NO_RECEIVER A NO_RECEIVER is added by default if the property has no receiver construction
@SinceKotlin(version = "1.1")
public static final Object NO_RECEIVER = NoReceiver.INSTANCE;
@SinceKotlin(version = "1.2")
private static class NoReceiver implements Serializable {
    private static final NoReceiver INSTANCE = new NoReceiver();

    private Object readResolve() throws ObjectStreamException {
        returnINSTANCE; }}//CallableReference
@SinceKotlin(version = "1.4")
protected CallableReference(Object receiver, Class owner, String name, String signature, boolean isTopLevel) {
    this.receiver = receiver; // A receiver for the property values of callable objects. Example: class instance
    this.owner = owner; // The class or package of the callable object
    this.name = name; // The Kotlin name of the callable, as declared in the source code
    this.signature = signature; // The JVM signature of the callable. If this is a property reference, return the JVM signature of its getter, such as "getFoo(LjavalangString;)." I ".
    this.isTopLevel = isTopLevel; // Whether high type (file or class), 0 false; 1 true
}
Copy the code

A simple summary of the principles behind the Kotlin attribute delegate using Java implementation

class Person {
  static final Field[] delegatedProperties = Person.class.getFields();
  private final NameDelegate nameDelegate = new NameDelegate();
  public final String getName(a) {
    return this.nameDelegate.getValue(this, delegatedProperties[0]); }}class NameDelegate {
  String getValue(Person thisRef, Field property) {
    return "Jay"; }}Copy the code

3.4 Simplifies the built-in interfaces for attribute delegates

Kotlin’s built-in attribute delegate function is attribute delegate class. It cannot constrain interactive methods and types through interfaces or integration like ordinary delegate mode, and cannot achieve two-party constraints. However, it can constrain the delegate class through generics + interfaces, which can also achieve the effect of partial constraints.

Three interfaces are provided in the Kotlin library to simplify the implementation of delegate classes

/ / val attribute
public fun interface ReadOnlyProperty<in T, out V>
/ / var attribute
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V>
// Create a factory interface for the delegate class
public fun interface PropertyDelegateProvider<in T, out D>
//T: The type of the object with the delegate attribute.
V: the type of the attribute value.
//D: delegate class type
Copy the code

Take a look at the interface and method signatures for the three interfaces

public fun interface ReadOnlyProperty<in T, out V> {
    public operator fun getValue(thisRef: T, property: KProperty< * >): V
}
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty< * >): V
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
@SinceKotlin("1.4")
public fun interface PropertyDelegateProvider<in T, out D> {
    public operator fun provideDelegate(thisRef: T, property: KProperty< * >): D
}
// Use the first two PropertyDelegateProvider scenarios
private val provider = PropertyDelegateProvider<FooBy, MyDelegate> { thisRef, property ->
    if (thisRef.y == "YYY") {
        MyDelegate()
    } else {
        MyDelegate2() //MyDelegate2:MyDelegate}}Copy the code

Take a look at their several comprehensive use of the situation, but also can see the kt voice is powerful, the same function, the code can be from a dozen lines to three lines and then to a line. yyds!!!

val provider1 = object : PropertyDelegateProvider<FooReadWrite, ReadWriteProperty<FooReadWrite, Int> > {override fun provideDelegate(
            thisRef: FooReadWrite,
            property: KProperty< * >): ReadWriteProperty<FooReadWrite, Int> {
            return object : ReadWriteProperty<FooReadWrite, Int> {
                var result=1024
                override fun getValue(thisRef: FooReadWrite, property: KProperty< * >): Int {
                    return result
                }
                override fun setValue(thisRef: FooReadWrite, property: KProperty<*>, value: Int) {
                    result=value
                }
            }
        }
    }

// Lambda simplified version
val provider2: PropertyDelegateProvider<FooReadWrite, ReadOnlyProperty<FooReadWrite, Int>> =
    PropertyDelegateProvider<FooReadWrite, ReadOnlyProperty<FooReadWrite, Int>> { pThisRef: Any? , pProperty: KProperty<*> ->ReadOnlyProperty<Any? .Int> { thisRef, property -> 1025 }
    
// A simplified version of intelligent type derivation
private valprovider3 =PropertyDelegateProvider { _: Any? , _ -> ReadOnlyProperty<Any? .Int> {_, _ ->1026}}val delegate1: Int by provider1
val delegate2: Int by provider2
val delegate3: Int by provider3
Copy the code

4. Application of attribute delegate in Kotlin Api

Several delegates are provided in the Kotlin library

  • Map Delegation
  • Lazy properties: its value is only evaluated on first access;
  • Observable Properties: Listeners receive notifications about changes to this property.
  • Delegates. NotNull property:…

4.1 Map Delegation

Take a look at an example of map as a property proxy

class User {
    / / the entrusted val
    valmap: Map<String, Any? > = mapOf("name2" to "Jay"."age" to 18)

    // Mutable map can delegate to val and var
    valmap2: MutableMap<String, Any? > = mutableMapOf("name2" to "Jay"."age" to 18)
    val name: String by map
    var age: Int by map2

    // To update the age value, MutableMap is also updated synchronously
    fun setValues(age: Int) {
        this.age = age
    }
}
Copy the code

The key in the map must contain the attribute name, otherwise the following error is reported

Exception in thread "main" java.util.NoSuchElementException: Key name2 is missing in the map.
Copy the code

So when using this feature, we should avoid map-based attribute delegation unless we are completely sure of the structure that supports the mapping, or the delegate class may fail and throw an exception

When value is null, only the fourth case will occur: NullPointerException. Please refer to the official bug report for this problem

valmap: HashMap<String, Any? > = hashMapOf("name" to null."age" to null)
val name: String by map
valName: String?by map
var age: Int? by map
var age: Int by map
Copy the code

Here’s another peek at Map Delegation’s delegation principle

// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(User.class, "name"."getName()Ljava/lang/String;".0)), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(User.class, "age"."getAge()Ljava/lang/Integer;".0))};
@Nullable
private final Map age$delegate;
@Nullable
public final Integer getAge(a) {
   Map var1 = (Map)this.age$delegate;
   KProperty var3 = $$delegatedProperties[1];
   boolean var4 = false;
   return (Integer)MapsKt.getOrImplicitDefaultNullable(var1, var3.getName());
}
public final void setAge(@Nullable Integer var1) {
   Map var2 = (Map)this.age$delegate;
   KProperty var4 = $$delegatedProperties[1];
   boolean var5 = false;
   var2.put(var4.getName(), var1);
}
Copy the code

As you can see, the Kotlin compiler also generates $$delegatedProperties of type KProperty[] and age Delegate of type Map and instantiates AgedEleGate at construct time and agedElegate at construct time, And instantiate Agedelegate at construct time

Map-related delegate methods are required in the MapAccessors class

//Map
@kotlin.internal.InlineOnly
public inline operator fun <V, V1 : V> Map<in String, @Exact V>.getValue(thisRef: Any? , property:KProperty< * >): V1 =@Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
//MutableMap
@kotlin.jvm.JvmName("getVar")
@kotlin.internal.InlineOnly
public inline operator fun <V, V1 : V> MutableMap<in String, out @Exact V>.getValue(thisRef: Any? , property:KProperty< * >): V1 = @Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)
//MutableMap
@kotlin.internal.InlineOnly
public inline operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any? , property:KProperty<*>, value: V) {
    this.put(property.name, value)
}
Copy the code

During a visit to the age of the get called when entrust the Map (Integer) MapsKt. GetOrImplicitDefaultNullable (var1, var3 getName ());

Call the delegate Map’s PUT method directly when accessing age’s set

The following is getOrImplicitDefaultNullable function

//JvmName This annotation specifies the name of the Java class or method to be generated from this element.
// The extension method is compiled and passed the method's reciver as its first argument
@kotlin.jvm.JvmName("getOrImplicitDefaultNullable")
@PublishedApi
internal fun <K, V> Map<K, V>.getOrImplicitDefault(key: K): V {
    if (this is MapWithDefault)return this.getOrImplicitDefault(key)
    return getOrElseNullable(key, { throw NoSuchElementException("Key $key is missing in the map.")})}Copy the code

How do map put and get operations relate to delegate getValue and setValue and map getValue(thisRef: Any? , property: KProperty<*>) is inline. This refers to a Kotlin 1.4 optimization of the delegate attribute, which will be explained later when parsing the lazy principle.

One of Map Delegation’s practices is to encapsulate push messages and notify apps

override fun onNotificationReceivedInApp(
    context: Context,
    title: String,
    summary: String,
    extraMap: Map<String, String>,
) {
    val data = extraMap.withDefault { "" }
    val params = NotificationParams(data)
    EventBus.getDefault().post(params)
}

class NotificationParams(val map: Map<String, String>) {
    val title: String by map
    val content: String by map
}
Copy the code

4.2 Lazy Properties

Take a look at a simple use of lazy

val x: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("XXX - lazy")
    "XXX -${index++}"
}
val y: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
    println(Yyy - "lazy")
    "Yyy -${index++}"
}
val z: String by lazy(LazyThreadSafetyMode.NONE) {
    println("Z - lazy")
    The "z -${index++}"
}
Copy the code
val fooLazy = FooLazy()
for (i in 1.100.) {
    val thread = thread(true) {
        Thread.sleep(100)
        println(Thread.currentThread().name)
        println("=" + fooLazy.x)
        println("= = = =" + fooLazy.y)
        println("= = = = = = = = =" + fooLazy.z)
    }
}

// XXX -- lazy and yyy -- lazy are executed only once, x=0, y=1
// ZZZ -- The lazy and z values cannot be determined how many times they will be executed
Copy the code

LazyThreadSafetyMode has three modes that specify how Lazy instances are initialized synchronously across threads.

  • SYNCHRONIZED: A lock that ensures that only one thread can initialize an instance of [Lazy].
  • PUBLICATION: When accessing an uninitialized instance value of [Lazy] concurrently, the Initializer function can be called multiple times, but only the first returned value will be used as the value of the [Lazy] instance.
  • NONE: No lock is used to synchronize access to [Lazy] instance values; Thread-safety issues can occur if the instance is accessed from more than one thread. This pattern should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.

Analysis of lazy principle

The entrusted object is Lazy

@NotNull
private final Lazy x$delegate;
Copy the code

The entrusted object is instantiated in the delegate constructor

public FooLazy() {
   this.x$delegate = LazyKt.lazy(LazyThreadSafetyMode.SYNCHRONIZED, (Function0)(new Function0() {
      // $FF: synthetic method
      // $FF: bridge method
      public Object invoke() {
         return this.invoke();
      }
      @NotNull
      public final String invoke() {
         String var1 = "XXX - lazy";
         boolean var2 = false;
         System.out.println(var1);
         StringBuilder var10000 = (new StringBuilder()).append("XXX,");
         FooLazy var10001 = FooLazy.this;
         int var3;
         var10001.index = (var3 = var10001.index) + 1;
         returnvar10000.append(var3).toString(); }})); }Copy the code

You can see that x$delegate is instantiated using the lazykt.lazy () method with two parameters, thread-safe mode type and an interface callback

GetValue (thisRef: Any?); thisRef: Any? , property: KProperty<*>)

public final String getX(a) {
   Lazy var1 = this.x$delegate;
   return (String)var1.getValue();
}
Copy the code

How does lazy define the delegate method getValue

@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any? , property:KProperty< * >): T = value
Copy the code

Notice here that lazy uses attribute delegates differently

  • No property array is automatically generatedKProperty[] $$delegatedProperties
  • Nor is it called when getX finally returnsgetValue(thisRef: Any? , property: KProperty<*>)
  • A lazygetValue(thisRef: Any? , property: KProperty<*>)Method is to useinlineDecorated and added@kotlin.internal.InlineOnlyAnnotation, map delegate does the same thing

This is actually an optimization done by Kotlin 1.4 when certain delegate attributes do not use KProperty. It is unnecessary for them to generate a KProperty object in $$delegatedProperties. Kotlin version 1.4 will optimize this situation. If the attribute operator of the delegate is inline and the KProperty parameter is not used, the corresponding reflection object will not be generated. If a delegate attribute is inline, the resulting $$delegatedProperties array will also generate its own reflection object, as explained in the official blog post

What to Expect in Kotlin 1.4 and Beyond | Optimized delegated properties

How does inlining actually work?

Roughly speaking, inlining takes the bytecode of the function being inlined and inserts it into the call, so the inline function declaration need not be visible at the call.

The role of @ kotlin. Internal. InlineOnly annotation?

InlineOnly means that the Java method corresponding to this Kotlin function is marked private, so Java code cannot access it (which is the only way to call an inline function without actually inlining it). The comment is not well verified and officials are currently only using it internally, but it is likely to be made public later.

So the lazy delegate and the map delegate are both optimized in Kotlin 4.1. The lazy delegate actually calls the getter for the value of the lazy. When the map property calls the getter/setter, it actually ends up calling the MAP’s GET/PUT methods.

Take a look at the lazy signature

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
Copy the code

SynchronizedLazyImpl

SynchronizedLazyImpl uses DCL to ensure thread safety

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile // Use memory visibility to check if it was initialized in another thread, and also disallow instruction reordering to prevent _value from getting incomplete instances
    private var _value: Any? = UNINITIALIZED_VALUE
    // The instance uses itself to synchronize
    private vallock = lock ? :this
    // The value attribute of the Lazy interface is used to get the Lazy initialization value of the current Lazy instance. Once initialized, it cannot change for the rest of the life of this Lazy instance.
    val value: T
        // Override get to ensure lazy loading and only execute the function when it is used
        get() {
            // Local variables can improve performance by more than 25%
            val _v1 = _value
            // Check if the singleton instance is initialized. Returns the instance if it is initialized.
            if(_v1 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            // It hasn't been initialized yet, but we can't be sure because multiple threads might have initialized it at the same time.
            // Just in case, add a mutex to ensure that only one thread instantiates the instance object.
            return synchronized(lock) {
                // Assign the instance to the local variable again to check if it was initialized by another thread, and the current thread is prevented from entering the locked region.
                val _v2 = _value
                If it has already been initialized by another thread, the current thread is aware of its existence and returns the instance directly
                if(_v2 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
                    (_v2 as T)
                } else {
                    // At this point the instance is still uninitialized, so we can safely create an instance (no other thread can enter this area).
                    valtypedValue = initializer!! (a)// Invoke the Function object and cache its return value
                    _value = typedValue //_value assignment tells other threads not to come in
                    initializer = null // Initializer is no longer available for the current class instance
                    typedValue // Return the final result to value}}}}Copy the code

If you don’t like the DCL, you can go back to the traditional way

val value: T
    get() {
        Joshua Bloch "Effective Java, Second Edition", p. 3. 283-284.
        var _v1 = _value
        if (_v1 == UNINITIALIZED_VALUE) {
            synchronized(lock) {
                // Assign the instance to the local variable again to check if it was initialized by another thread, and the current thread is prevented from entering the locked region.
                // If it is initialized, the current thread is aware of its presence.
                _v1 = _value
                if (_v1 == UNINITIALIZED_VALUE) {
                    // The instance is still uninitialized, so we can safely (no other thread can enter the zone) create an instance and assign it to our singleton reference._v1 = initializer!! () initializer =null}}}@Suppress("UNCHECKED_CAST")
        return _v1 as T
    }
Copy the code

SafePublicationLazyImpl

AtomicReferenceFieldUpdater: atomic updater is based on the reflection of the tools, used to a certain class, be atomic updates by volatile modified field.

By calling the AtomicReferenceFieldUpdater static newUpdater method can create the instance, this method is to receive three parameters: contains the fields in the class, the type of object will be updated and will be updated the name of the field

CompareAndSet If the expected value is equal to the current value of the field, the current value can be updated. Returns true and sets the field atomically to the given updated value.

GetAndSet atomically sets the field of the given object managed by this updater to the given value and returns the old value.

There are more stringent conditions for the use of atomic updater as follows

  • The fields of the operation cannot be static.
  • Operation fields cannot be final because final cannot be changed at all.
  • Fields must be volatile, meaning that the data itself is read consistent.
  • Property must be visible to the region where the current Updater is located. You can’t use private, protected subclasses to operate on their parent class unless the atomic Updater is inside the current class. The modifier must be protect and above. If it is in the same package, it must have default permission or above, that is to say, the visibility between the operating class and the operated class should be guaranteed at all times.

The java.util.concurrent package is completely built on CAS. Without CAS, there would be no such package, which shows the importance of CAS. Most current processors support CAS, but different vendors implement it differently. CAS has three operands: the memory value V, the old expected value A, and the value to be modified B. Change the memory value to B and return true if and only if the expected value A and the memory value V are the same, otherwise do nothing and return false.

Unsafe, a class in the JDK that provides hardware-level atomic operations.

The compareAndSet method calls the process


private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();

public final boolean compareAndSet(T obj, V expect, V update) {
    accessCheck(obj);
    valueCheck(update);
    return U.compareAndSwapObject(obj, offset, expect, update);
}

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

Copy the code

Look at an example to understand the AtomicReferenceFieldUpdater is the use of the way

public class AtomicReferenceFieldUpdaterTest {
  public static void main(String[] args) throws Exception {
    // T: The type of the object holding the updatable field
    // V: field type
    AtomicReferenceFieldUpdater<Dog, String> updater =
        // The class containing the field, the class of the object to be updated, and the name of the field to be updated
        AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
    Dog dog = new Dog();

    // If the expected value is equal to the current value of the field, indicating that the current value is the latest to be updated, then the field is set atomically to the given updated value.
    / / parameters:
    // obj: the object of the field
    // expect - expect
    // update - New value
    // Returns: true on success
    System.out.println(dog.name); // Dog1 default value
    boolean result = updater.compareAndSet(dog, "dog1"."dog2");
    System.out.println(result); // true The modification succeeds
    System.out.println(dog.name); // The modified value of dog2
    boolean result2 = updater.compareAndSet(dog, "dog1"."dog3");
    System.out.println(result2); // false Failed to modify
    System.out.println(dog.name); // Dog2 is still the same value

    // Atomically sets the field of the given object managed by this updater to the given value and returns the old value.
    / / parameters:
    // obj -- Update the field object
    // newValue - a newValue
    // Return: the previous value
    String result3 = updater.getAndSet(dog, "dog4");
    System.out.println(result3); // The original value of dog2
    System.out.println(dog.name); // The modified value of dog4}}class Dog {
  volatile String name = "dog1";
}
Copy the code

SafePublicationLazyImpl use AtomicReferenceFieldUpdater to ensure _value attribute atomic operation. Multiple thread calls are supported at the same time and can be initialized on all or part of the threads simultaneously. If a value has been initialized by another thread, the value is returned without initialization.

private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    @Volatile private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    private val final: Any = UNINITIALIZED_VALUE
    override val value: T
        get() {
            val value = _value
            if(value ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
                return value as T
            }
            val initializerValue = initializer
            // If the initial value is already null, the value has already been set by another thread
            if(initializerValue ! =null) {
                val newValue = initializerValue() // Invoke the Function object and atomize the return value of the Function to _value
                // If the value of _value is UNINITIALIZED_VALUE, the thread has not initialized it yet, so you can set newValue to _value
              	if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null
                    return newValue // Only one thread will return from here
                }
              // If the value of _value is not UNINITIALIZED_VALUE, another thread has initialized, and the current thread simply returns _value
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

		// If a serialized class contains the Object writeReplace() method, the actual serialized Object will be the Object returned by the writeReplace method.
    private fun writeReplace(a): Any = InitializedLazyImpl(value)
    companion object {
      	// Initialize an atomic updater: make sure the atomic operation field is _value
        private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
            SafePublicationLazyImpl::class.java,
            Any::class.java,
            "_value")}}Copy the code

UnsafeLazyImpl

internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    private var _value: Any? = UNINITIALIZED_VALUE
    override val value: T
        get() {
          	// Normal lazy loading is initialized only once, but in multi-threaded environments it is not guaranteed to be executed only once
            if(_value === UNINITIALIZED_VALUE) { _value = initializer!! (a)// Null pointer exception may occur in multithreaded concurrency
                initializer = null
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }
  	// If a serialized class contains the Object writeReplace() method, the actual serialized Object will be the Object returned by the writeReplace method.
    private fun writeReplace(a): Any = InitializedLazyImpl(value)
}
Copy the code

4.3 NotNullVar

NotNull can return a non-null checked property value but the property value is not initialized and an exception will result if setValue attempts to read the property later before assigning the initial value. This is how returning a non-null property works

In general, attributes declared as non-empty types must be initialized in constructors. However, this is usually inconvenient. For example, properties can be initialized through dependency injection or in the setup method of a unit test. In this case, you cannot provide non-null initializers in the constructor, but you still want to avoid null checking when referencing properties in the body of the class.

Lateinit does not support primitive types and can only be used with mutable attributes. Var notNull creates a delegate class NotNullVar for each attribute

Use and principles of notNull

    var name2: String by Delegates.notNull()
    val age2: Int by Delegates.notNull() // notNull creates a delegate class NotNullVar for each attribute
//lateinit var age3: Int lateinit does not support primitive types
    lateinit var name3: String // Lateinit can only be used with var


public fun <T : Any> notNull(a): ReadWriteProperty<Any? , T> = NotNullVar()private class NotNullVar<T : Any>() : ReadWriteProperty<Any? , T> {private var value: T? = null

    public override fun getValue(thisRef: Any? , property:KProperty< * >): T {
        returnvalue ? :throw IllegalStateException("Property ${property.name} should be initialized before get.")}public override fun setValue(thisRef: Any? , property:KProperty<*>, value: T) {
        this.value = value
    }
}
Copy the code

4.4 ObservableProperty

public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any? , V> {private var value = initialValue
    protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true
    protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}
    public override fun getValue(thisRef: Any? , property:KProperty< * >): V {
        return value
    }
    public override fun setValue(thisRef: Any? , property:KProperty<*>, value: V) {
        val oldValue = this.value
    //beforeChange: Callback called before attempting to change a property value. The value of this property has not changed when this callback is called. If the callback returns true, the value of the property is set to the new value, if the callback returns false, the new value is discarded and the property keeps its old value
        if(! beforeChange(property, oldValue, value)) {return
        }
        this.value = value
    //afterChange: callback called after a property change has been made. When this callback is called, the value of this property has changed.
        afterChange(property, oldValue, value)
    }
}
Copy the code

Observable listens after the value is updated

public inline fun <T> observable(initialValue: T.crossinline onChange: (property: KProperty< * >,oldValue: T.newValue: T) - >Unit): ReadWriteProperty<Any? , T> =object : ObservableProperty<T>(initialValue) {
        override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
    }
Copy the code

Vetoable variable value before the interception

public inline fun <T> vetoable(initialValue: T.crossinline onChange: (property: KProperty< * >,oldValue: T.newValue: T) - >Boolean): ReadWriteProperty<Any? , T> =object : ObservableProperty<T>(initialValue) {
        override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
    }
Copy the code

The test code

var items: List<String> by Delegates.observable(mutableListOf()) { property, oldValue, newValue ->
    println("${property.name} : $oldValue -> $newValue")}var nameAfter: String by Delegates.observable("no") { prop, old, new ->
    println("$old -> $new")}var nameBefore: String by Delegates.vetoable("no") { prop, old, new ->
    println("$old -> $new")
    true // Return true to indicate that setValue succeeded, otherwise the original value cannot be overwritten
}

private fun <T> onChange(property: KProperty<*>, oldValue: T, newValue: T) {
    println("${property.name} : $oldValue -> $newValue")}var age: Int by Delegates.observable(18, ::onChange)

// Run the result
no -> first
first -> second
no -> 11111
11111 -> 2222
age : 18 -> 33
age : 33 -> 55
items : [] -> [new val]
items : [new val] -> [new val, new 111]
Copy the code

5. Application of attribute delegate on Android

5.1 ViewBinding

//1. Delegate + reflect VB's inflate method with the lazy attribute
private val binding: ActivityMainBinding by vb()
//2. Delegate with the lazy attribute + pass the inflate method reference
private val binding: ActivityMainBinding by vb(ActivityMainBinding::inflate)
Copy the code

VBHelper

@MainThread
inline fun <reified T : ViewBinding> ComponentActivity.vb(noinline inflateMethodRef: ((LayoutInflater) - >T)? = null): Lazy<T> =
    ActivityVBLazy(this, T::class.inflateMethodRef)


class ActivityVBLazy<T : ViewBinding>(
    private val activity: ComponentActivity,
    private val kClass: KClass<*>,
    private val inflateMethodRef: ((LayoutInflater) -> T)?
) : Lazy<T> {
    private var cachedBinding: T? = null
    override val value: T
        get() {
            var viewBinding = cachedBinding
            if (viewBinding == null) {
                viewBinding = if(inflateMethodRef ! =null) {
                    // Delegate with the lazy attribute + pass the inflate method reference
                    inflateMethodRef.invoke(activity.layoutInflater)
                } else {
                    // Bind the inflate method of your class with the lazy delegate attribute + reflection
                    @Suppress("UNCHECKED_CAST")
                    kClass.java.getMethod(METHOD_INFLATE, LayoutInflater::class.java)
                        .invoke(null, activity.layoutInflater) as T
                }
                activity.setContentView(viewBinding.root)
                cachedBinding = viewBinding
            }
            return viewBinding
        }

    override fun isInitialized(a)= cachedBinding ! =null
}
Copy the code

5.2 the ViewModel

// Delegate with lazy + ViewModelProvider
val model: MyViewModel by viewModels()
Copy the code

ActivityViewModelLazy

@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    factory: ViewModelProvider.Factory? = null
): Lazy<VM> = ActivityViewModelLazy(this, VM::class.factory)

/** * An implementation of [Lazy] used by [ComponentActivity.viewModels] tied to the given [activity], * [viewModelClass], [factory] */
class ActivityViewModelLazy<VM : ViewModel>(
    private val activity: ComponentActivity,
    private val viewModelClass: KClass<VM>,
    private val factory: ViewModelProvider.Factory?
) : Lazy<VM> {
    private var cached: VM? = null
    override val value: VM
        get() {
            var viewModel = cached
            if (viewModel == null) {
                valapplication = activity.application ? :throw IllegalArgumentException(
                        "ViewModel can be accessed " +
                                "only when Activity is attached"
                    )
                valresolvedFactory = factory ? : AndroidViewModelFactory.getInstance(application) viewModel = ViewModelProvider(activity, resolvedFactory).get(viewModelClass.java)
                cached = viewModel
            }
            return viewModel
        }

    override fun isInitialized(a)= cached ! =null
}
Copy the code

FragmentViewModelLazy

@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(factory: Factory? = null): Lazy<VM> =
    FragmentViewModelLazy(this, VM::class.factory)

/** * An implementation of [Lazy] used by [Fragment.viewModels] tied to the given [fragment], * [viewModelClass], [factory] */
class FragmentViewModelLazy<VM : ViewModel>(
    private val fragment: Fragment,
    private val viewModelClass: KClass<VM>,
    private val factory: Factory?
) : Lazy<VM> {
    private var cached: VM? = null
    override val value: VM
        get() {
            var viewModel = cached
            if (viewModel == null) {
                valapplication = fragment.activity? .application ? :throw IllegalArgumentException(
                        "ViewModel can be accessed " +
                                "only when Fragment is attached"
                    )
                valresolvedFactory = factory ? : AndroidViewModelFactory.getInstance(application) viewModel = ViewModelProvider(fragment, resolvedFactory).get(viewModelClass.java)
                cached = viewModel
            }
            return viewModel
        }

    override fun isInitialized(a)= cached ! =null
}
Copy the code

SP 5.3 delegates

fun SharedPreferences.int(def: Int = 0, key: String? = null) =
    delegate(def, key, SharedPreferences::getInt, SharedPreferences.Editor::putInt)

fun SharedPreferences.long(def: Long = 0, key: String? = null) =
    delegate(def, key, SharedPreferences::getLong, SharedPreferences.Editor::putLong)

fun SharedPreferences.string(def: String = "", key: String? = null) =
    delegate(def, key, SharedPreferences::getString, SharedPreferences.Editor::putString)


private inline fun <T> SharedPreferences.delegate(
    defaultValue: T,
    key: String? .crossinline getter: SharedPreferences. (String.T) - >T.crossinline setter: SharedPreferences.Editor. (String.T) - >SharedPreferences.Editor
) = object : ReadWriteProperty<Any, T> {
    override fun getValue(thisRef: Any, property: KProperty< * >)= getter(key ? : property.name, defaultValue)@SuppressLint("CommitPrefEdits")
    override fun setValue(thisRef: Any, property: KProperty<*>, value: T)= edit().setter(key ? : property.name, value).apply() }Copy the code

The test code

class TokenHolder(prefs: SharedPreferences) {
    var token: String by prefs.string()
        private set
    var count by prefs.int()
        private set
    fun saveToken(newToken: String) {
        token = newToken
        count++
    }
    override fun toString(a): String {
        return "TokenHolder(token='$token', count=$count)"}}class UserHolder(prefs: SharedPreferences) {
    var name: String by prefs.string()
        private set
    var pwd: String by prefs.string()
        private set
    fun saveUserAccount(name: String, pwd: String) {
        this.name = name
        this.pwd = pwd
    }
    override fun toString(a): String {
        return "UserHolder(name='$name', pwd='$pwd')"}}val prefs = getSharedPreferences("sp_app_jay", Context.MODE_PRIVATE)

// Token caching scenario
val tokenHolder = TokenHolder(prefs)
Log.d("Jay"."tokenHolder:$tokenHolder")
tokenHolder.saveToken("token_one")
tokenHolder.saveToken("token_second")

// The scenario of caching login information
val userHolder = UserHolder(prefs)
Log.d("Jay"."userHolder:$userHolder")
userHolder.saveUserAccount("jay"."123456")
Copy the code

6. Summary

In this paper, the principle of Kotlin Delegation (including attribute Delegation and interface Delegation) is described by focusing on Kotlin’s Delegation property and combining with code practice. In particular, the realization principle of attribute Delegation from attribute to delegate is described in detail.

Then comes the practice part. First, Kotlin standard library encapsulates many simple apis for us by using attribute delegation, such as Map, lazy, notNull, Observable, etc. Then it is some practice of Kotlin attribute delegation on Android, including VB, VM, SP, etc., using attribute delegation can basically complete a line of code to achieve set/ GET. Kotlin delegates can obviously play a powerful role in eliminating boilerplate code. But behind each of these attributes is a delegate class, so performance needs to be considered when used in large quantities.

Reference 7.

The official document | entrusted

The official document | property entrusted

Lesson for net | new Kotlin from entry to the master

This article thoroughly understands the delegation in Kotlin

Wikipedia | Delegation pattern

Wikipedia | Proxy pattern

Medium | Kotlin Delegates in Android