рдпрд╣ рдХреНрдпрд╛ рд╣реИ
рдХрд┐рд╕рдиреЗ рдЕрднреА рддрдХ рдкреНрд░рд▓реЗрдЦрди рдирд╣реАрдВ рдкрдврд╝рд╛ рд╣реИ - рдореИрдВ рдЖрдкрдХреЛ рдЦреБрдж рдХреЛ рдкрд░рд┐рдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрддреНрдпрдзрд┐рдХ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВред
рдЬреЗрдЯрдмреНрд░реЗрди рдХреНрдпрд╛ рд▓рд┐рдЦрддрд╛ рд╣реИ:
рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдЕрдВрджрд░ рд╕рднреА рдЬрдЯрд┐рд▓рддрд╛рдУрдВ рдХреЛ рдЫреЛрдбрд╝рддреЗ рд╣реБрдП, рдХреЛрд░рд╛рдЙрдЯреАрди рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рдХреЛ рд╕рд░рд▓ рдмрдирд╛рддреЗ рд╣реИрдВред рдХрд╛рд░реНрдпрдХреНрд░рдо рдХреЗ рддрд░реНрдХ рдХреЛ рдХреЛрд░рдЯрд╛рдЗрдиреНрд╕ рдореЗрдВ рдХреНрд░рдорд┐рдХ рд░реВрдк рд╕реЗ рд╡реНрдпрдХреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рдЖрдзрд╛рд░ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдЗрд╕реЗ рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд░реВрдк рд╕реЗ рд▓рд╛рдЧреВ рдХрд░реЗрдЧрд╛ред рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕рдВрдмрдВрдзрд┐рдд рдШрдЯрдирд╛рдУрдВ рдХреА рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрдиреЗ рд╡рд╛рд▓реЗ рдХреЙрд▓рдмреИрдХ рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛрдб рдХреЗ рд╕рдВрдмрдВрдзрд┐рдд рднрд╛рдЧреЛрдВ рдХреЛ рд▓рдкреЗрдЯ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдереНрд░реЗрдб (рдпрд╛ рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдорд╢реАрдиреЛрдВ рдХреЗ рд▓рд┐рдП рдирд┐рд╖реНрдкрд╛рджрди рдХреЛ рднреА рдкреНрд░реЗрд╖рдг) рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдХреЛрдб рдЙрддрдирд╛ рд╣реА рд╕рд░рд▓ рд░рд╣реЗрдЧрд╛ рдЬрд┐рддрдирд╛ рдХрд┐ рд╕рдЦреНрддреА рд╕реЗ рдХреНрд░рдо рд╕реЗ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред
рд╕рд░рд▓ рд╢рдмреНрджреЛрдВ рдореЗрдВ, рдпрд╣ рд╕рд┐рдВрдХреНрд░реЛрдирд╕ / рдПрд╕рд┐рдВрдХреНрд░реЛрдирд╕ рдХреЛрдб рдирд┐рд╖реНрдкрд╛рджрди рдХреЗ рд▓рд┐рдП рдПрдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╣реИред
рдХреНрдпреЛрдВ?
рдХреНрдпреЛрдВрдХрд┐ RxJava рдЕрдм рдлреИрд╢рди рдореЗрдВ рдирд╣реАрдВ рд╣реИ (рд╕рд┐рд░реНрдл рдордЬрд╛рдХ рдХрд░ рд░рд╣рд╛ рд╣реИ)ред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдореИрдВ рдХреБрдЫ рдирдпрд╛ рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛, рдФрд░ рджреВрд╕рд░реА рдмрд╛рдд, рдореИрдВ рдПрдХ рд▓реЗрдЦ - рдХреЛрд░реБрдЯрд┐рди рдХреА рдЧрддрд┐ рдФрд░ рдЕрдиреНрдп рддрд░реАрдХреЛрдВ рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рдЖрдпрд╛ рдерд╛ред
рдЙрджрд╛рд╣рд░рдг
рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рдкреГрд╖реНрдарднреВрдорд┐ рдореЗрдВ рдПрдХ рдСрдкрд░реЗрд╢рди рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
рдЖрд░рдВрдн рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП - corutin рдкрд░ рд╣рдорд╛рд░реЗ build.gradle рдирд┐рд░реНрднрд░рддрд╛ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ:
| dependencies { |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
| .... |
| } |
рд╣рдо рдЕрдкрдиреЗ рдХреЛрдб рдореЗрдВ рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
рдЬрд╣рд╛рдВ рд╕рдВрджрд░реНрдн рдореЗрдВ - рд╣рдо рдЙрд╕ рдереНрд░реЗрдб рдкреВрд▓ рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рддреЗ рд╣реИрдВ рдЬрд┐рд╕рдХреА рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ - рд╕рд░рд▓ рдорд╛рдорд▓реЛрдВ рдореЗрдВ, рдпреЗ рдЖрдИрдУ, рдореБрдЦреНрдп рдФрд░ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд╣реИрдВ
IO - рдПрдкреАрдЖрдИ рдХреЗ рд╕рд╛рде рд╕рд░рд▓ рд╕рдВрдЪрд╛рд▓рди рдХреЗ рд▓рд┐рдП, рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд▓рди, рд╕рд╛рдЭрд╛ рдкреНрд░рд╛рдердорд┐рдХрддрд╛рдПрдВ рдЖрджрд┐ред
рдореБрдЦреНрдп - рдпреВрдЖрдИ рдзрд╛рдЧрд╛, рдЬрд╣рд╛рдВ рд╕реЗ рд╣рдо рджреГрд╢реНрдп рддрдХ рдкрд╣реБрдВрдЪ рд╕рдХрддреЗ рд╣реИрдВ
рдбрд┐рдлрд╝реЙрд▓реНрдЯ - рдЙрдЪреНрдЪ рд╕реАрдкреАрдпреВ рд▓реЛрдб рдХреЗ рд╕рд╛рде рднрд╛рд░реА рд╕рдВрдЪрд╛рд▓рди рдХреЗ рд▓рд┐рдП
(рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдФрд░ рдЕрдзрд┐рдХ)
рдмреНрд▓реЙрдХ - рд▓рд╛рдВрдмрд╛ рдЬрд┐рд╕реЗ рд╣рдо рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ
| var result = 1.0 |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| Log.d("coroutines example", "result = $result") |
рд╕рд┐рджреНрдзрд╛рдВрдд рд░реВрдк рдореЗрдВ, рдпрд╣ рд╕рдм, рд╣рдореЗрдВ 1 рд╕реЗ 1000 рддрдХ рд╡рд░реНрдЧреЛрдВ рдХреЗ рдпреЛрдЧ рдХрд╛ рдкрд░рд┐рдгрд╛рдо рдорд┐рд▓рддрд╛ рд╣реИ рдФрд░ рд╕рд╛рде рд╣реА рд╣рдо рдореБрдЦреНрдп рдзрд╛рдЧреЗ рдХреЛ рдмреНрд▓реЙрдХ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рдЬрд┐рд╕рдХрд╛ рдЕрд░реНрде рд╣реИ рдХрд┐ рдХреЛрдИ ANR рдирд╣реАрдВ
рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЕрдЧрд░ рд╣рдорд╛рд░реА рдХреЛрд░рдЯрд╛рдЗрди рдХреЛ 20 рд╕реЗрдХрдВрдб рдХреЗ рд▓рд┐рдП рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдЗрд╕ рд╕рдордп рдХреЗ рджреМрд░рд╛рди рд╣рдордиреЗ рдбрд┐рд╡рд╛рдЗрд╕ рдХреЗ 2 рдореЛрдбрд╝ рдмрдирд╛рдП рд╣реИрдВ, рддреЛ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ 3 рдПрдХ рд╕рд╛рде рдЪрд▓рдиреЗ рд╡рд╛рд▓реЗ рдмреНрд▓реЙрдХ рд╣реЛрдВрдЧреЗред рдЙрдлрд╝ред
рдФрд░ рдЕрдЧрд░ рд╣рдордиреЗ рдмреНрд▓реЙрдХ рдореЗрдВ рдЧрддрд┐рд╡рд┐рдзрд┐ рдХреЗ рд▓рд┐рдП рдПрдХ рд▓рд┐рдВрдХ рдкрд╛рд░рд┐рдд рдХрд┐рдпрд╛ - рдПрдХ рд░рд┐рд╕рд╛рд╡ рдФрд░ рдкреБрд░рд╛рдиреЗ рдмреНрд▓реЙрдХреЛрдВ рдореЗрдВ рджреГрд╢реНрдп рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд▓рди рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдХреА рдХрдореАред рджреЛ рдмрд╛рд░ рдЙрдлрд╝ред
рддреЛ рдХреНрдпрд╛ рдХрд░реЗрдВ?
рдмреЗрд╣рддрд░ рдХрд░ рд░рд╣реЗ рд╣реИрдВ
| private var viewModelJob = Job() |
| private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
| |
| fun doWork() { |
| var result = 1.0 |
| viewModelScope.launch { |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| } |
| Log.d("coroutines example", " result = $result") |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
рдЗрд╕ рдкреНрд░рдХрд╛рд░, рд╣рдо рд╕реНрдЯреНрд░реАрдо рдореЗрдВ рдЕрдкрдиреЗ рдХреЛрдб рдХреЗ рдирд┐рд╖реНрдкрд╛рджрди рдХреЛ рд░реЛрдХрдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рдереЗ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЬрдм рд╕реНрдХреНрд░реАрди рдХреЛ рдШреБрдорд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
CoroutineScope рдиреЗ рд╕рднреА рдиреЗрд╕реНрдЯреЗрдб рдХреЙрд░рдЖрдЙрдЯреНрд╕ рдХреЗ рджрд╛рдпрд░реЗ рдХреЛ рд╕рдВрдпреЛрдЬрд┐рдд рдХрд░рдирд╛ рд╕рдВрднрд╡ рдмрдирд╛рдпрд╛ рдФрд░, рдЬрдм job.cancel () рдХреЛ рдХреЙрд▓ рдХрд┐рдпрд╛, рддреЛ рдЙрдирдХреЗ рдирд┐рд╖реНрдкрд╛рджрди рдХреЛ рд░реЛрдХ рджрд┐рдпрд╛ред
рдпрджрд┐ рдЖрдк рдирд┐рд╖реНрдкрд╛рджрди рдХреЛ рд░реЛрдХрдиреЗ рдХреЗ рдмрд╛рдж рдЧреБрдВрдЬрд╛рдЗрд╢ рдХрд╛ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдпреЛрдЬрдирд╛ рдмрдирд╛рддреЗ рд╣реИрдВ, рддреЛ рдЖрдкрдХреЛ job.cancelChildren () рдХреЗ рдмрдЬрд╛рдп job.cancel () рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЯрд┐рдкреНрдкрдгреА рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж Neikist
рдЙрд╕реА рд╕рдордп, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдЕрднреА рднреА рдкреНрд░рд╡рд╛рд╣ рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рдиреЗ рдХрд╛ рдЕрд╡рд╕рд░ рд╣реИ:
| fun doWork() { |
| var result = 1.0 |
| var result2 = 1.0 |
| viewModelScope.launch { |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| withContext(Default) { |
| for (i in 1..1000) { |
| result2 += i * i |
| } |
| } |
| } |
| Log.d("coroutines example", "running result = $result, result 2 = $result2") |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
рд╣рдо рд░реЗрдЯреНрд░реЛрдлрд╝рд┐рдЯ 2 рдХрдиреЗрдХреНрдЯ рдХрд░рддреЗ рд╣реИрдВ
рдУрд▓реЛрдВ рдХреЗ рд▓рд┐рдП рдирд┐рд░реНрднрд░рддрд╛ рдЬреЛрдбрд╝реЗрдВ:
| |
| dependencies { |
| implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" |
| implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion" |
| implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion" |
| ... |
| } |
рд╣рдо рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд░реВрдк рдореЗрдВ рдХрд▓рдо рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ https://my-json-server.typicode.com/typicode/demo/posts
рд╣рдо рд░реЗрдЯреНрд░реЛрдлрд┐рдЯ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВ:
| interface RetrofitPosts { |
| |
| @GET("posts") |
| fun getPosts(): Deferred<Response<List<Post>>> |
| |
| } |
рд▓реМрдЯреЗ рдкреЛрд╕реНрдЯ рдореЙрдбрд▓ рдХрд╛ рд╡рд░реНрдгрди рдХрд░реЗрдВ:
| data class Post(val id: Int, val title: String) |
рд╣рдорд╛рд░рд╛ рдмреЗрд╕рд░реЗрдкреЛрд╕рд┐рдЯрд░реА:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
PostRepository рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди:
| class PostsRepository : |
| BaseRepository<PostsRepository.Params, PostsRepository.Result>() { |
| |
| override suspend fun doWork(params: Params): Result { |
| val retrofitPosts = Retrofit |
| .Builder() |
| .baseUrl("https://jsonplaceholder.typicode.com") |
| .addConverterFactory(MoshiConverterFactory.create()) |
| .addCallAdapterFactory(CoroutineCallAdapterFactory()) |
| .build() |
| .create(RetrofitPosts::class.java) |
| val result = retrofitPosts |
| .getPosts() |
| .await() |
| |
| return Result(result.body()) |
| } |
| |
| class Params |
| data class Result(val posts: List<Post>?) |
| } |
рд╣рдорд╛рд░рд╛ рдмреЗрд╕рдЙрд╕реЗрдХреЗрд╕:
| abstract class BaseUseCase<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
GetPostsListUseCase рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди:
| class GetListOfPostsUseCase |
| : BaseUseCase<GetListOfPostsUseCase.Params, GetListOfPostsUseCase.Result>() { |
| |
| override suspend fun doWork(params: Params): Result { |
| return Result( |
| PostsRepository() |
| .doWork(PostsRepository.Params()) |
| .response |
| .posts |
| ) |
| } |
| |
| class Params |
| class Result(val posts: List<Post>?) |
| } |
рдпрд╣рд╛рдБ рдкрд░рд┐рдгрд╛рдо рд╣реИ:
| fun doWork() { |
| val useCase = GetListOfPostsUseCase() |
| viewModelScope.launch { |
| withContext(Dispatchers.IO) { |
| |
| val result = useCase.doWork( |
| GetListOfPostsUseCase.Params() |
| ) |
| Log.d("coroutines example", "get list of posts = ${result.posts}") |
| } |
| } |
| |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
рдЗрд╕реЗ рдмреЗрд╣рддрд░ рдмрдирд╛рдирд╛
рдореИрдВ рдПрдХ рдЖрд▓рд╕реА рдкреНрд░рд╛рдгреА рд╣реВрдВ рдФрд░ рд╣рд░ рдмрд╛рд░ рдЬрдм рдореИрдВ рдХреЛрдб рдХреА рдкреВрд░реА рд╢реАрдЯ рдХреЛ рдмрд╛рд╣рд░ рдирд╣реАрдВ рдирд┐рдХрд╛рд▓рдирд╛ рдЪрд╛рд╣рддрд╛, рддреЛ рдореИрдВрдиреЗ BaseViewModel рдореЗрдВ рдЖрд╡рд╢реНрдпрдХ рддрд░реАрдХреЗ рдмрдирд╛рдП:
| abstract class BaseViewModel : ViewModel() { |
| |
| private var viewModelJob = Job() |
| private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
| private var isActive = true |
| |
| // Do work in IO |
| fun <P> doWork(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| doCoroutineWork(doOnAsyncBlock, viewModelScope, IO) |
| } |
| |
| // Do work in Main |
| // doWorkInMainThread {...} |
| fun <P> doWorkInMainThread(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| doCoroutineWork(doOnAsyncBlock, viewModelScope, Main) |
| } |
| |
| // Do work in IO repeately |
| // doRepeatWork(1000) {...} |
| // then we need to stop it calling stopRepeatWork() |
| fun <P> doRepeatWork(delay: Long, doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| isActive = true |
| viewModelScope.launch { |
| while (this@BaseViewModel.isActive) { |
| withContext(IO) { |
| doOnAsyncBlock.invoke(this) |
| } |
| if (this@BaseViewModel.isActive) { |
| delay(delay) |
| } |
| } |
| } |
| } |
| |
| fun stopRepeatWork() { |
| isActive = false |
| } |
| |
| override fun onCleared() { |
| super.onCleared() |
| isActive = false |
| viewModelJob.cancel() |
| } |
| |
| private inline fun <P> doCoroutineWork( |
| crossinline doOnAsyncBlock: suspend CoroutineScope.() -> P, |
| coroutineScope: CoroutineScope, |
| context: CoroutineContext |
| ) { |
| coroutineScope.launch { |
| withContext(context) { |
| doOnAsyncBlock.invoke(this) |
| } |
| } |
| } |
| } |
рдЕрдм рдбрд╛рдХ рд╕реВрдЪреА рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:
| class PostViewModel : BaseViewModel() { |
| |
| val lengthOfPostsList = MutableLiveData<String>() |
| |
| fun getListOfPosts() { |
| doWork { |
| val result = GetListOfPostsUseCase() |
| .doWork(GetListOfPostsUseCase.Params()) |
| Log.d("coroutines example", "get list of posts = ${result.posts}") |
| lengthOfPostsList.postValue(result.posts?.size.toString()) |
| } |
| } |
рдирд┐рд╖реНрдХрд░реНрд╖
рдореИрдВрдиреЗ рдЙрддреНрдкрд╛рджреЛрдВ рдореЗрдВ рдХреЛрд░рд╛рдЙрдЯрд╛рдЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдФрд░ рдХреЛрдб рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХреНрд▓реАрдирд░ рдФрд░ рдЕрдзрд┐рдХ рдкрдардиреАрдп рдирд┐рдХрд▓рд╛ред
рдпреБрдкреАрдбреА:
рд░реЗрдЯреНрд░реЛрдлрд╝рд┐рдЯ рдЕрдкрд╡рд╛рдж рд╣реИрдВрдбрд▓рд┐рдВрдЧ рд╡рд┐рд╡рд░рдг рдЯрд┐рдкреНрдкрдгреА рджреЗрдЦреЗрдВ