рд╣рд╛рдп - рдпрд╣ рдЕрднреА рдПрдХ рджреЛрд╕реНрдд (cc @mgrebenets) рдХреЗ рд╕рд╛рде рдЪрд░реНрдЪрд╛ рдХреЗ рджреМрд░рд╛рди рд╕рд╛рдордиреЗ рдЖрдпрд╛
рдореЗрд░реЗ рдкрд╛рд╕ UserManager
рдЬреЛ рдиреЗрдЯрд╡рд░реНрдХ рд╕реЗ User
рдСрдмреНрдЬреЗрдХреНрдЯ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИ рдФрд░ UserManager
рдкрд░ рдПрдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЛ рдЙрдЬрд╛рдЧрд░ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реИ:
func getUsers() -> SignalProducer<User, ErrorType>
(рдореИрдВ SignalProducer
рд╡рд╛рдкрд╕ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдХреНрдпреЛрдВрдХрд┐ рдореИрдВ flatMap
рдЗрд╕ рдиреЗрдЯрд╡рд░реНрдХ рдЕрдиреБрд░реЛрдз рдХреЛ рдЕрдиреНрдп рдиреЗрдЯрд╡рд░реНрдХ рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ)ред
рдкрдХрдбрд╝ рдпрд╣ рд╣реИ рдХрд┐ рдореИрдВ рдХреЗрд╡рд▓ start
рдиреЗрдЯрд╡рд░реНрдХ рдЕрдиреБрд░реЛрдз рдХреЛ SignalProducer
рдореЗрдВ рд▓рдкреЗрдЯрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдпрджрд┐ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рдХреЛрдИ рдЕрдиреБрд░реЛрдз рдирд╣реАрдВ рд╣реИред
рдЗрд╕реЗ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореЗрд░рд╛ рдЕрдВрддрд░реНрдЬреНрдЮрд╛рди рдЗрд╕ рдкреНрд░рдХрд╛рд░ рдерд╛:
class UserManager {
static let sharedManager = UserManager()
private var requestSignal: Signal<User, NSError>? = nil
func getUsers() -> SignalProducer<User, NSError> {
if let requestSignal = requestSignal {
print("There is already a request in progress so simply return a reference to its signal")
return SignalProducer(signal: requestSignal)
} else {
let requestSignalProducer = SignalProducer<User, NSError> { observer, disposable in
let url = NSURL(string:"http://jsonplaceholder.typicode.com/users/1")!
let task = NSURLSession.sharedSession()
.dataTaskWithURL(url) { (data, response, error) in
if error != nil {
observer.sendFailed(NSError(domain:"", code:5, userInfo:nil))
} else {
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions())
let user = User(JSON: json)
print("Completed user request at \(NSDate())")
observer.sendNext(user)
observer.sendCompleted()
}
}
print("Started user request at \(NSDate())")
task.resume()
}
requestSignalProducer.startWithSignal{ [weak self] (signal, disposable) in
guard let `self` = self else { return }
`self`.requestSignal = signal
signal.observeCompleted {
print("Completing the user request signal")
`self`.requestSignal = nil
}
}
return SignalProducer(signal: self.requestSignal!)
}
}
}
requestSignal
рдореЗрдВ рдкрд╛рд░рд╕реНрдкрд░рд┐рдХ рд╕реНрдерд┐рддрд┐ рдХреЛ рдмреБрдпреБрдЯ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ "рдЕрди-рдЖрд░рдПрд╕реА" рдорд╣рд╕реВрд╕ рдХрд░рддрд╛ рд╣реИред рдХрд┐рд╕реА рднреА рд╕рдВрдХреЗрдд рдХреА рд╕рд░рд╛рд╣рдирд╛ рдХрд░реЗрдВрдЧреЗ рдЬреЛ рдЖрдк рдПрдХ рд╕рдорд╛рдзрд╛рди рдкрд░ рджреЗ рд╕рдХрддреЗ рд╣реИрдВ рдЬреЛ рдЖрд░рдПрд╕реА рд╕рд┐рджреНрдзрд╛рдВрддреЛрдВ рдХреЗ рдЕрдиреБрд░реВрдк рд╣реИред
рдЪрд┐рдпрд░реНрд╕! ЁЯША
replayLazily
рдХрд░рдирд╛ рдЪреАрдЬреЛрдВ рдХреЛ рдереЛрдбрд╝рд╛ рд╕рд░рд▓ рдХрд░рддрд╛ рд╣реИ - рдлрд┐рд░ рдЖрдк рд╕рд┐рдЧреНрдирд▓ рдХреЛ рд░реАрдбрд╛рдпрд░реЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рдмрдЬрд╛рдп SignalProducer
рд╕реАрдзреЗ рдмрдЪрд╛ рд╕рдХрддреЗ рд╣реИрдВред
рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдпрд╣рд╛рдВ рдПрдХ рджреМрдбрд╝ рдХреА рд╕реНрдерд┐рддрд┐ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдЖрдк RAC рдХреЗ Atomic
рдкреНрд░рдХрд╛рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣ рд╕рдХрддреЗ рд╣реИрдВред (рдпрджрд┐ 2 рдзрд╛рдЧреЗ рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ getUsers
рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк 2 рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд╕рдорд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред)
рдореИрдВ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдПрдкреАрдЖрдИ рдХреЛрдб рдХреЛ рдПрдХ рдЕрд▓рдЧ рд╡рд┐рдзрд┐ рдпрд╛ рд╡рд░реНрдЧ рдореЗрдВ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░рдиреЗ рдХреА рднреА рд╕рд▓рд╛рд╣ рджреВрдВрдЧрд╛ред рдЗрд╕рд╕реЗ рдХреЛрдб рдХреЛ рдкрдврд╝рдиреЗ рдореЗрдВ рдереЛрдбрд╝реА рдЖрд╕рд╛рдиреА рд╣реЛрдЧреА рдФрд░ рдЪрд┐рдВрддрд╛рдУрдВ рдХреЛ рдереЛрдбрд╝рд╛ рдмреЗрд╣рддрд░ рдврдВрдЧ рд╕реЗ рдЕрд▓рдЧ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХреЗрдЧрд╛ред
struct API {
static func getUsers() -> SignalProducer<User, NSError> {
return SignalProducer<User, NSError> { observer, disposable in
let url = NSURL(string:"http://jsonplaceholder.typicode.com/users/1")!
let task = NSURLSession.sharedSession()
.dataTaskWithURL(url) { (data, response, error) in
if error != nil {
observer.sendFailed(NSError(domain:"", code:5, userInfo:nil))
} else {
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions())
let user = User(JSON: json)
print("Completed user request at \(NSDate())")
observer.sendNext(user)
observer.sendCompleted()
}
}
print("Started user request at \(NSDate())")
task.resume()
}
}
}
class UserManager {
static let sharedManager = UserManager()
private var requestProducer: Atomic<SignalProducer<User, NSError>?> = Atomic(nil)
func getUsers() -> SignalProducer<User, NSError> {
return requestProducer.modify { producer in
if let producer = producer {
print("There is already a request in progress so simply return a reference to its signal")
return producer
}
return API.getUsers()
.on(completed: {
self.requestProcuder.modifify { _ in nil }
})
.replayLazily()
}
}
}
тЪая╕П рдореИрдВрдиреЗ рдЗрд╕реЗ рд╕реАрдзреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЯрд╛рдЗрдк рдХрд┐рдпрд╛ рдФрд░ рдпрд╣ рдЕрдкреНрд░рдпреБрдХреНрдд рд╣реИред тЪая╕П
рдЕрднреА рднреА рд░рд╛рдЬреНрдп рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рдереЛрдбрд╝рд╛ рдмреЗрд╣рддрд░ рд╣реИред
RAC-iest рддрд░реАрдХрд╛ рдпрд╣ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдирдпрд╛ рдСрдкрд░реЗрдЯрд░ рдЬреЛрдбрд╝рдирд╛ рд╣реЛрдЧрд╛ рдЬрд┐рд╕рдореЗрдВ рд░рд╛рдЬреНрдп рд╢рд╛рдорд┐рд▓ рд╣реИред
extension SignalProducer {
func replayIfStarted() -> SignalProducer<Value, Error> {
let active: Atomic<SignalProducer<Value, Error>?> = nil
return active.modify { producer in
if let producer = producer {
return producer
}
return self
.on(completed: {
active.modify { _ in nil }
})
.replayLazily()
}
}
}
рдореБрдЭреЗ рдпрдХреАрди рд╣реИ рдХрд┐ рдЖрд░рдПрд╕реА рдХреЛрдбрдмреЗрд╕ рдХреЗ рдЦрд┐рд▓рд╛рдл рджреЛрд╣рд░реА рдЬрд╛рдВрдЪ рд╣реЛрдЧреА, рдФрд░ рдирд┐рд░реНрдорд╛рддрд╛ рдЬреАрд╡рди рднрд░ рдХреБрдЫ рдкрд░реАрдХреНрд╖рдг рднреА рд▓рд┐рдЦреЗрдВрдЧреЗ, рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдореИрдВрдиреЗ рдЗрд╕реЗ рдареАрдХ рд╕реЗ рд▓рд┐рдЦрд╛ рд╣реИред тШ║я╕П
рдЕрдЪреНрдЫрд╛ рд▓рдЧрд╛! рдФрд░ рдореЗрд░реА рдУрд░ рд╕реЗ рдХреЛрдб-рдмрд╛рд░рдл рдХреЗ рд▓рд┐рдП рдорд╛рдлреА, рдореБрдЭреЗ рдЙрд╕ "рдкрд╣рд▓реЗ рд╕реЗ рдХреЛрдб рдирд╣реАрдВ рд╣реИ" рд╕рдВрджреЗрд╢ рдХреЗ рд╕рд╛рде рдмреНрд▓реЙрдХ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП
рдЬрдм рдореИрдВ Xcode рдХреЗ рд╕рд╛рдордиреЗ рд╣реВрдБ, рддреЛ рдореИрдВ рдЗрд╕реЗ рджреЗрдЦ рд▓реВрдВрдЧрд╛; рдЖрдкрдХреЗ рд╕рднреА рдЕрдВрддрд░реНрджреГрд╖реНрдЯрд┐ рдХреА рд╕рд░рд╛рд╣рдирд╛ рдХрд░рддреЗ рд╣реИрдВред
рдореИрдВ рдЗрд╕реЗ рдмрдВрдж рдХрд░рдиреЗ рдЬрд╛ рд░рд╣рд╛ рд╣реВрдВред рдЕрдЧрд░ рдЖрдкрдХреЛ рдЗрд╕реЗ рджреЗрдЦрдиреЗ рдХреЗ рдмрд╛рдж рдХреЛрдИ рд╕рд╡рд╛рд▓ рд╣реИ рддреЛ рдлрд┐рд░ рд╕реЗ рдЖрдЬрд╝рд╛рдж рд╣реЛрдВ!
ЁЯСН рдзрдиреНрдпрд╡рд╛рдж рдореИрдЯ
рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдиреЗ рдХрд╛ рдПрдХ рдФрд░ рддрд░реАрдХрд╛ рд╣реИ: Property
!
final class UserManager {
/// The public API is as easy as observing this.
public let users: AnyProperty<[User]>
private let usersMutableProperty = MutableProperty<[User]>([])
init() {
self.users = AnyProperty(self.usersMutableProperty)
}
private func getUsers() -> SignalProducer<User, NSError> { }
public func requestUsers() {
/// This is not idea because multiple calls to this method would step onto one-another, just doing it like this for simplicity
self.usersMutableProperty <~ self.usersRequest()
}
}