ããã«ã¡ã¯ãäºæ¥éçºé¨ã®å²¡æ (@iceman5499) ã§ãã æ®æ®µã¯ã¯ãã¯ãããã¢ããªï¼iOSï¼ãéçºãã¦ãã¾ãã
å æ¥ãã¢ããªã±ã¼ã·ã§ã³ãç¹å®ã®æ¡ä»¶ã§æå³ãã¬ç¶æ ã«é¥ããã¢ããªã±ã¼ã·ã§ã³ãéããªã£ã¦ç«¯æ«ãçºç±ãããã¨ãããã°ãçºè¦ããã¾ããã 調æ»ã®çµæããã®ãã°ã¯ã¡ã¢ãªãªã¼ã¯ãåå ã§çºçãã¦ãã¾ããã ãã®åçãè¸ã¾ãã¡ã¢ãªãªã¼ã¯ãæ¤ç¥ãããã¹ããå°å ¥ãããããæ¬è¨äºã§ã¯ãã®äºä¾ãç´¹ä»ãããã¨æãã¾ãã
ï¼æ¬è¨äºã§ã¯ã¯ãã¯ãããã¢ããªã¨ã¯iOSçã®ãã¯ãã¯ããããã¢ããªã®ãã¨ãæããã®ã¨ãã¾ãï¼
ã¯ãã¯ãããã¢ããªã«ãããã¡ã¢ãªãªã¼ã¯ã®å½±é¿
ã¯ãã¯ãããã¢ããªã¯ã¬ã·ãã®æ¤ç´¢ãã³ã¢æ©è½ã¨ãã¦ãã¾ãã æ¤ç´¢ã¯éãå¦çã§ããAPIãéãã¦ãµã¼ãä¸ã§è¡ããããããã¢ããªã¯çµæã表示ããã ãã§ãããã®ããã¡ã¢ãªãå¤ãå¿ è¦ã¨ãã¾ããã ããã¾ã§ã«ãä½åº¦ãã¡ã¢ãªãªã¼ã¯ãçºçãã¦ããç¶æ³ã¯ããã¾ããããã¡ã¢ãªãå¤ãå¿ è¦ã¨ããªãããå¤å°ã®ç¡é§ããã£ã¦ãã¢ããªã®åä½ã«å½±é¿ãããã¾ããã§ããã
ã¯ãã¯ãããã¢ããªã§ç¨ãããã¦ããã¯ã©ã¹ã®å¤§åã¯èªåã§åããããªãã¨ã¯ãããRunLoopçã®ã¤ãã³ãã«ã¼ãã«ãã£ã¦åä½ãã¾ãã UIKitã使ç¨ãã¦ããã¨ã¤ã³ã¹ã¿ã³ã¹ã®RunLoopããã®é¤å»ã¯èªç¶ã«å®ç¾ã§ãããããã¡ã¢ãªãªã¼ã¯ãèµ·ãã£ã¦ããã®ã¤ã³ã¹ã¿ã³ã¹ã¯æ¢ã¾ã£ã¦ãã¦ãç¡å®³ãªç¶æ ã§ãããã¨ãå¤ãã§ãã
ããããªãããä»åã¯ä¸éã«ãåç¬ã§ã¤ãã³ãã«ã¼ããçºçãããã¤ã³ã¹ã¿ã³ã¹ãã¡ã¢ãªãªã¼ã¯ãã¦ãã¾ãã¾ããã æ¬æ¥ã¯ãã®ã¤ã³ã¹ã¿ã³ã¹ãã¡ã¢ãªãã解æ¾ãããã¿ã¤ãã³ã°ã§ã¤ãã³ãã«ã¼ããæ¢ã¾ãã¯ãã§ããããã¡ã¢ãªãã解æ¾ãããªãã£ããã¨ã«ããåæ¢ãããªãã¤ãã³ãã«ã¼ããæ°¸é ã«ç¡é§ãªå¦çãç¶ãã¦ãã¾ããã ãã®çµæããã®ã¤ã³ã¹ã¿ã³ã¹ãå¤æ°ã¡ã¢ãªãªã¼ã¯ãã¦ãã¾ãã¨ã¢ããªã±ã¼ã·ã§ã³ã®åä½ã«å½±é¿ããã»ã©è² è·ãããã£ã¦ãã¾ãã¾ããã
ã©ã®ããã«ãã¦ã¡ã¢ãªãªã¼ã¯ãèµ·ãã£ã¦ãã¾ãã®ã
iOSã¢ããªã±ã¼ã·ã§ã³éçºã®ç¾å ´ã§èµ·ããã¡ã¢ãªãªã¼ã¯ã¯å¤§æµã循ç°åç §ãåå ã¨ãªã£ã¦ãã¾ãã 循ç°åç §ã«ããã¡ã¢ãªãªã¼ã¯ã¯åç §ã«ã¦ã³ãæ¹å¼ã®ã¬ãã¼ã¸ã³ã¬ã¯ã·ã§ã³ç°å¢ã«ããã¦çºçãããåé¡ã§ãSwiftãObjective-Cã使ã£ã¦ããã¨èµ·ãããããã®ã§ããï¼ãªããããã§ã¯å¾ªç°åç §ãã©ã®ãããªç¶æ ã§ãããã®èª¬æã¯çç¥ãã¾ããï¼ ã¯ãã¯ãããã¢ããªã§ã¯RxSwiftã¨ããã©ã¤ãã©ãªãå¤ç¨ãã¦ãããããã¯ãã¼ã¸ã£ãçµç±ãã¦ãã£ããã¡ã¢ãªãªã¼ã¯ããå½¢ã®å¾ªç°åç §ãå¼ãèµ·ããã¦ãã¾ãã±ã¼ã¹ãå¤ãã§ãã
ãããã循ç°åç §ã®ä¾
å®éã«ã¯ãã¯ãããã¢ããªã§èµ·ãã£ã¦ãã循ç°åç §ã®å®è£ ã®ä¾ãç´¹ä»ãã¾ãã
èªèº«ãææããObservableã®observerã¨ãã¦èªèº«ãææããããã¿ã¼ã³
ã¡ãã£ã¨ã¿ã¤ãã«ããããããã§ãããæãã·ã³ãã«ãªã¿ã¤ãã®ãã®ã§ãã
// ViewController.swift let stream: PublishSubject<Void> let disposeBag = DisposeBag() func bind() { stream.subscribe(onNext: { self.doSomething() // selfããã£ããã£ããã¦ãã }) .disposed(by: disposeBag) }
RxSwiftã§ã¯ Observable
ãè³¼èªããã¨ãã®è³¼èªãobserverãªãã¸ã§ã¯ãã¨ã㦠Observable
ã«ä¿æããã¾ããï¼ä¾ã® PublishSubject
㯠Observable
ã®ä¸ç¨®ã§ããï¼
ãã®ä¾ã§ã¯observerã¯ããã« onNext:
ã«æ¸¡ããã¦ããã¯ãã¼ã¸ã£ãä¿æãã¾ããããã¦ããã«ã¯ãã¼ã¸ã£ã¯ self
ãä¿æãã¦ãã¾ãããã㧠self
ã¯ãã®å®è£
ãæ㤠ViewController
ã¯ã©ã¹ã§ããã¨ãã¾ãã
ãã®çµæã
self â stream â observer â ã¯ãã¼ã¸ã£ â self
ã¨ãã¦å¾ªç°åç
§ã¨ãªãã¾ãã
ãã®å¾ªç°åç
§ã®è§£æ±ºçã¨ãã¦ã¯ã次ã®ããã« self
ãå¼±åç
§ã§ãã£ããã£ããæ¹æ³ãæãããã¾ãã
stream.subscribe(onNext: { [weak self] in self?.doSomething() })
æé»ã¯ãã¼ã¸ã£æ¸¡ããã¿ã¼ã³
次ã®ä¾ã¯ã©ãã§ãããããããããã¾ã«ããä¾ã§ãæ°ã¥ããã¨ãé£ãã循ç°åç §ã§ãã
// ViewController.swift let stream: PublishSubject<Int> let disposeBag = DisposeBag() func bind() { stream.subscribe(onNext: doSomething) // ãã®å ´åãselfãå¼·åç §ããã¦ãã¾ã .disposed(by: disposeBag) } func doSomething(_ value: Int) { ... }
onNext:
ã«ã¯ãã¼ã¸ã£ã使ããã«é¢æ°ã渡ããã¨ã§ããã£ããã¨ãã表è¨ã«ãªã£ã¦ãã¾ãã
ã¨ããããã® doSomething
ãä¸è¦é¢æ°ãã¤ã³ã¿ã渡ãã¦ããããã«è¦ãã¾ãããå®éã¯ã³ã³ãã¤ã©ãè£å´ã§ self
ããã£ããã£ããã¯ãã¼ã¸ã£ãçæãã¦ããããã次ã®ã³ã¼ãã¨åãæå³ã«ãªã£ã¦ãã¾ãã
stream.subscribe(onNext: { self.doSomething($0) })
ããã¯å
ç¨ã¨åããã¿ã¼ã³ã®å¾ªç°åç
§ã¨ãªãã¾ãã
対å¦æ³ã¨ãã¦ã¯å
ç¨ã¨åãããã« [weak self]
ãç¨ããã®ãè¯ãã§ãããã
ãããã¯ã doSomething
ã®å¦çã self
ã«ä¾åãã¦ããªããªãã°ãããstaticé¢æ°ã«ãã¦ãã¾ãã¨ããæãããã¾ããï¼staticé¢æ°ã«ãªã£ãå ´åãã®é¢æ°ã®å®è¡ã« self
ãå¿
è¦ãªããªããããä¸ã§ç´¹ä»ããã³ã³ãã¤ã©ãè£å´ã§ããå¦çãç¡ããªãã¾ããï¼
func bind() { stream.subscribe(onNext: Self.doSomething) .disposed(by: disposeBag) } static func doSomething(_ value: Int) { ... }
è¤æ°ã¯ã©ã¹ã«ã¾ããã£ã¦å¾ªç°ãã¦ãããã¿ã¼ã³
// Presenter.swift class Presenter { let value: Observable<Int> init(view: View, interactor: Interactor) { value = interactor.stream .filter { view.isXXX } // ãã㧠view ãå¼·åç §ã§ãã£ããã£ãã¦ãã .map { ... } } } // View.swift class ViewController: View { var presenter: Presenter! var isXXX: Bool { ... } let disposeBag = DisposeBag() func bind(presenter: Presenter) { presenter.value.subscribe(...) .disposed(by: disposeBag) ... self.presenter = presenter } }
ããã¯è¤æ°ã®ã¯ã©ã¹ã«ã¾ãããä¾ã§ãã
Presenter
ãçæãã value
ã«ã¯ view
ããã£ããã£ããã¦ããããã®å·ã view
ã§ãã ViewController
ã¯ã©ã¹ãè³¼èªãã¾ãã
ã¤ã¾ã ViewController
ããã¿ã¦ã
self â presenter â value â filterã®å
é¨ã§ä½¿ããããªãã¸ã§ã¯ã â ã¯ãã¼ã¸ã£ â view(== self)
ã¨ãã¦åç
§é¢ä¿ãçºçãã循ç°åç
§ãæç«ãã¾ãã
self
ãã¯ãã¼ã¸ã£ã«ãã£ããã£ããã¦ããå ´åã¯ãªãã¨ãªãã¢ã³ãããåå¿ããããã®ã§ãããããã§ãªãã±ã¼ã¹ã¯ãã£ããè¦éããããããã§ãã
ããã®å¯¾å¿ãåæ§ã«å¼±åç
§ãç¨ãããã¨ã«ãªãã¾ãã
.filter { [weak view] _ in view?.isXXX == true }
ã¡ã¢ãªãªã¼ã¯ãæ¤ç¥ãããã¹ãã®å°å ¥
ããããã循ç°åç §ã®ä¾ããè¦ã¦ãããããã«ã循ç°åç §ã¯ãã£ããè¦éãããããã人ç®ã®ã¬ãã¥ã¼ãããæãã¦ãã¾ãã¾ãã ã¾ãã³ã³ãã¤ã©ã«ãã£ã¦æ¤ç¥ãããã¨ãã§ãã¾ããã
ããã§ãXCTAssertNoLeakã使ã£ã¦ãã¹ããæ¸ããã¨ã«ãã¾ããã
XCTAssertNoLeakã¯å¯¾è±¡ã®ã¤ã³ã¹ã¿ã³ã¹å ã§ã¡ã¢ãªãªã¼ã¯ãçºçãã¦ããããæ¤ç¥ããæ©è½ãæä¾ãããã¹ãç¨ã©ã¤ãã©ãªã§ãã 2019å¹´ã®try!Swiftã§çºè¡¨ãããã©ã¤ãã©ãªã§ãã¡ã¢ãªãªã¼ã¯ãæ¤ç¥ãããã¹ããæ¸ããã¨ãã§ãã¾ãã
https://github.com/tarunon/XCTAssertNoLeak ã®README.mdãã
ãã å¼æ°ã«ãªãã¸ã§ã¯ãã渡ãã ãã§ãç°¡åã«ãªã¼ã¯ãã¦ãããªãã¸ã§ã¯ãããªã¹ããã¦ãããç´ æµãªã©ã¤ãã©ãªã§ãã
XCTAssertNoLeakã®åä½åç
XCTAssertNoLeakã¯ã©ã®ããã«ãã¦ã¡ã¢ãªãªã¼ã¯ãæ¤ç¥ãã¦ããã®ã§ããããã
åºæ¬çãªæ¦ç¥ã¨ãã¦ã¯ãã¤ã³ã¹ã¿ã³ã¹ãweakãã¤ã³ã¿ã«æ ¼ç´ããã¹ã³ã¼ããå¤åããã¿ã¤ãã³ã°ã§ãã¤ã³ã¿ã®ä¸èº«ã nil
ã«ãªã£ã¦ãããã©ããã§å¤å®ããã¦ãã¾ãã
ã¤ã³ã¹ã¿ã³ã¹ãæã¤ããããã£ç¾¤ã«æ ¼ç´ãããåã¤ã³ã¹ã¿ã³ã¹ãããã®ããã«å«ã¤ã³ã¹ã¿ã³ã¹ãå
¨ã¦ç¢ºä¿ããããã«ã¯ Mirror
ãç¨ãããã¦ãã¾ãã
Mirrorã使ãå
¨ããããã£ãæ¢ç´¢ãã¦åç
§åã®å¤ãå
¨ã¦weakãã¤ã³ã¿ã«ç¢ºä¿ããã¹ã³ã¼ããæãããã¨ãã®weakãã¤ã³ã¿ãã¡ãã㨠nil
ã«ãªã£ã¦ããã確èªãããã¨ããæãã§ãã
ãã¹ãè¨è¿°ã«é¢ãã注æç¹
XCTAssertNoLeakã¯é常ã«ç°¡åã«å©ç¨ã§ããããã«ãªã£ã¦ãã¾ãããä»çµã¿ä¸ããã¤ãæ°ãã¤ããªãã¨ãããªãé¨åãããã¾ãã
ãã¼ã«ã«å¤æ°ã®ã¹ã³ã¼ãã«æ³¨æãã
XCTAssertNoLeak
ã¯å¼æ°ã«æ¸¡ãããªãã¸ã§ã¯ããæ¤æ»ããã¾ããããã¼ã«ã«å¤æ°ã«ãªãã¸ã§ã¯ããä¿æãã¦ãã¾ãã¨ãã®ãã¼ã«ã«å¤æ°ãåç
§ãæã¤ããã«ãã¹ãã«ç¨ããããweakãã¤ã³ã¿ã nil
ã«ãªãããã¡ã¢ãªãªã¼ã¯æ±ãã«ãªã£ã¦ãã¾ãã¾ãã
// NG let viewController = MyViewController() XCTAssertNoLeak(viewController) // faild!
åé¿ããããã«ã¯ XCTAssertNoLeak
ã®å¼æ°ã«ãªãã¸ã§ã¯ããå³è¾ºå¤ã¨ãã¦æ¸¡ãå¿
è¦ãããã¾ãã
ã¯ãã¯ãããã¢ããªã§ã¯æ¬¡ã®ããã«ãã¦ãã¹ããè¨è¿°ãã¦ãã¾ãã
func build() -> AnyObject { RecipeDetailsViewBuilder.build(....) // åæåå¦ç } XCTAssertNoLeak(build())
åæåå¦çããã¼ã«ã«é¢æ°ã«ã©ããããè¿ãå¤ããã®ã¾ã¾ XCTAssertNoLeak
ã«æ¾ãè¾¼ããã¨ã§ãã¼ã«ã«å¤æ°ã«ãã¹ã対象ã®ã¤ã³ã¹ã¿ã³ã¹ãä¿æããªãããã«ãã¦ãã¾ãã
ã·ã³ã°ã«ãã³ã¯ä¾å¤è¨å®
次ã®ãã¹ããè¦ã¦ã¿ã¾ãããã
NotificationCenterããªã¼ã¯ãã¦ããã¨æããã¦ãã¾ãã
ã·ã³ã°ã«ãã³ã¯éæ¾ãããªããããXCTAssertNoLeakããè¦ãã¨ã¡ã¢ãªãªã¼ã¯ãã¦ãããã®ã¨ãã¦å¤å®ããã¾ãã
ãã®ãããªç¶æ³ã«å¯¾å¿ããããã« CustomTraversable
ã¨ãããããã³ã«ãç¨æããã¦ãã¾ãã
extension NotificationCenter: CustomTraversable { var ignoreAssertion: Bool { true } }
ã¡ã¢ãªãªã¼ã¯ãã¦ããã¨å¤å®ãããã¯ã©ã¹ã«å¯¾ãã¦extension㧠ignoreAssertion: Bool
ãå®è£
ãããã¨ã§ããã®ã¨ã©ã¼ãç¡è¦ãããã¨ãã§ãã¾ãã
CustomTraversable
ã«ã¯ãã®æã®ã±ã¼ã¹ã«å¯¾å¿ããããã®å£ãããã¤ãç¨æããã¦ãã¾ãã
ã·ã³ã°ã«ãã³ãä¿æãããªãã¸ã§ã¯ã
ã·ã³ã°ã«ãã³ãç¡è¦è¨å®ããã¨ããã¾ã§ã¯è¯ãã£ãã§ããã ignoreAssertion
ããã ãã§ã¯ãã®ãªãã¸ã§ã¯ãã«é£ãªã£ã¦ãããªãã¸ã§ã¯ããããã«ãªã¼ã¯å¤å®ããã¦ãã¾ãã¾ããï¼ ignoreAssertion
ã¯ãã®ã¤ã³ã¹ã¿ã³ã¹ã®åããããã£ç¾¤ãã¨ç¡è¦ã¯ãã¾ããï¼
class AwesomeObject {} class MySingleton { static let shared = MySingleton() private let object = AwesomeObject() } extension MySingleton: CustomTraversable { var ignoreAssertion: Bool { true } } class MyViewController: UIViewController { let object = AwesomeObject() let dependency = MySingleton.shared } class NoLeakTests: XCTestCase { func testMyViewController() { // failed - 1 object occured memory leak. // - self.dependency.object XCTAssertNoLeak(MyViewController()) } }
ãã®ã³ã¼ãã®ä¾ã®å ´åã self.dependency.object
ããªã¼ã¯ããã¨ããå¤å®ã«ãªãã¾ãã
ãã¾ããã®ãããªã±ã¼ã¹ã¯ããã¾ããããç¾å®ã®ã¢ããªã±ã¼ã·ã§ã³ã¯è¤éã§ããããã¾ãã«ãã®ã±ã¼ã¹ã«ééãã¾ãã
ããã®å¯¾å¿ãèãããã¨ã¯é£ããã§ããä¾ãã°æ¬¡ã®ããã«ã·ã³ã°ã«ãã³ã«æããã AwesomeObject
ã®ã¿ä¾å¤è¨å®ãããã¨ãèãã¾ãã
extension AwesomeObject { var ignoreAssertion: Bool { self === MySingleton.shared.object // ã³ã³ãã¤ã«ã¨ã©ã¼: 'object' is inaccessible due to 'private' protection level } }
ãã®ããã«æ¸ãããã¨ããã§ãããã¢ã¯ã»ã¹ä¿®é£¾åã®é¢ä¿ã§ãã®å¦çã¯è¨è¿°ãããã¨ãã§ãã¾ããã 対象ã®ãªãã¸ã§ã¯ããã¢ããªã±ã¼ã·ã§ã³å ã«å®è£ ããã¦ããã°ããããã¯ããããããã¾ããããå¤é¨ã®ã©ã¤ãã©ãªãªã©ã¨ãªãã¨é£ãããªã£ã¦ãã¾ãã
ã¯ãã¯ãããã¢ããªã§ã¯ãã®ã±ã¼ã¹ã¯è«¦ã㦠AwesomeObject
ã® ignoreAssertion
ã¯å¸¸ã« true
ãè¿ãããã«ãã¦ãã¾ãã
ã¾ã¨ã
XCTAssertNoLeakã®ãããã§ãã¡ã¢ãªãªã¼ã¯ãæ¤ç¥ãããã¹ããå®ç¾ãããã¨ãã§ãã¾ããã ãã®ãã¹ããå®è£ ãã¦ãããåã®å¤æ´ã§å¾ªç°åç §ãå¼ãèµ·ããã¦ãã¾ããã¹ãã«æããã¦ãã¾ã£ãã®ã§æ©éå¹æãçºæ®ãã¾ããã ãã¹ããå°å ¥ããéç¨ã§è¦ã¤ãã£ãã¡ã¢ãªãªã¼ã¯ãããã¤ããããä»ã¾ã§è¦ã¤ãã£ã¦ãªãã£ãã¡ã¢ãªãªã¼ã¯ãæµ®ã彫ãã«ãããã¨ãã§ãã¾ããã
ãã®ããã«ãã¦iOSã¢ããªã®ã¡ã¢ãªãªã¼ã¯ã¯è§£æ¶ãã¾ããããã¢ãã¤ã«ã¢ããªã®å質å®å®ã«ã¯ã¾ã ã¾ã æã足ãã¦ããªãç¶æ³ã§ãã ã¯ãã¯ãããã§ã¯ã¢ãã¤ã«ã¢ããªã®å質ãå®å®ããããiOS/Androidã¨ã³ã¸ãã¢ãåéãã¦ãã¾ãã https://info.cookpad.com/careers/