-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from thoughtbot/objc-runtime-swift
Reimplement ObjectiveC runtime code in Swift
- Loading branch information
Showing
12 changed files
with
193 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#if canImport(ObjectiveC) | ||
import ObjectiveC.runtime | ||
|
||
struct ClassHierarchy: Sequence { | ||
struct Iterator: IteratorProtocol { | ||
var `class`: AnyClass? | ||
|
||
init(class: AnyClass) { | ||
self.class = `class` | ||
} | ||
|
||
mutating func next() -> AnyClass? { | ||
guard let next = `class` else { return nil } | ||
`class` = class_getSuperclass(next) | ||
return next | ||
} | ||
} | ||
|
||
var `class`: AnyClass | ||
|
||
func makeIterator() -> Iterator { | ||
Iterator(class: `class`) | ||
} | ||
} | ||
|
||
extension ClassHierarchy { | ||
init?(object: Any) { | ||
guard let `class` = object_getClass(object) else { return nil } | ||
self.init(class: `class`) | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#if canImport(ObjectiveC) | ||
import ObjectiveC.runtime | ||
|
||
struct MethodList { | ||
private let buffer: UnsafeBufferPointer<Method> | ||
|
||
init?(class: AnyClass) { | ||
var count = UInt32(0) | ||
guard let list = class_copyMethodList(`class`, &count) else { return nil } | ||
self.buffer = UnsafeBufferPointer(start: list, count: Int(count)) | ||
} | ||
} | ||
|
||
extension MethodList: RandomAccessCollection { | ||
var startIndex: Int { | ||
buffer.startIndex | ||
} | ||
|
||
var endIndex: Int { | ||
buffer.endIndex | ||
} | ||
|
||
subscript(position: Int) -> Method { | ||
buffer[position] | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
#if canImport(ObjectiveC) | ||
import ObjectiveC.runtime | ||
import Foundation | ||
|
||
private let _UIViewController: AnyClass? = NSClassFromString("UIViewController") | ||
private var _isHookedKey = UInt8(0) | ||
|
||
private typealias ViewDidLoadBlock = @convention(block) (Any) -> Void | ||
private typealias ViewDidLoadFunction = @convention(c) (Any, Selector) -> Void | ||
|
||
func combinevm_isHooked(_ object: Any) -> Bool { | ||
objc_getAssociatedObject(object, &_isHookedKey) as? Bool == true | ||
} | ||
|
||
private func combinevm_setIsHooked(_ object: Any, _ isHooked: Bool) { | ||
objc_setAssociatedObject(object, &_isHookedKey, isHooked, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) | ||
} | ||
|
||
#if canImport(UIKit) | ||
import class UIKit.UIViewController | ||
|
||
extension UIViewController { | ||
@nonobjc static let viewDidLoadNotification = Notification.Name("CombineViewModelViewDidLoad") | ||
|
||
@nonobjc func hookViewDidLoad() { | ||
guard !combinevm_isHooked(type(of: self)) else { return } | ||
|
||
var originalIMP: IMP! | ||
let `class`: AnyClass | ||
let method: Method | ||
let selector = #selector(self.viewDidLoad) | ||
|
||
(method, `class`) = object_getInstanceMethod(self, name: selector)! | ||
|
||
let block: ViewDidLoadBlock = { `self` in | ||
let viewDidLoad = unsafeBitCast(originalIMP!, to: ViewDidLoadFunction.self) | ||
viewDidLoad(self, selector) | ||
NotificationCenter.default.post(name: UIViewController.viewDidLoadNotification, object: self) | ||
} | ||
|
||
originalIMP = method_setImplementation(method, imp_implementationWithBlock(block)) | ||
|
||
combinevm_setIsHooked(`class`, true) | ||
} | ||
} | ||
#endif | ||
|
||
private func object_getInstanceMethod(_ object: Any, name: Selector) -> (method: Method, class: AnyClass)? { | ||
guard var hierarchy = ClassHierarchy(object: object)?.makeIterator() else { return nil } | ||
var stop = false | ||
|
||
while !stop, let `class` = hierarchy.next() { | ||
if `class` === _UIViewController { | ||
stop = true | ||
} | ||
|
||
guard let methods = MethodList(class: `class`) else { continue } | ||
|
||
if let method = methods.first(where: { method_getName($0) == name }) { | ||
return (method, `class`) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
private func object_isHooked(_ object: Any) -> Bool { | ||
guard let hierarchy = ClassHierarchy(object: object) else { return false } | ||
|
||
for `class` in hierarchy { | ||
if combinevm_isHooked(`class`) { | ||
return true | ||
} | ||
|
||
if `class` === _UIViewController { | ||
break | ||
} | ||
} | ||
|
||
return false | ||
} | ||
#endif |
1 change: 0 additions & 1 deletion
1
Sources/CombineViewModel/UIViewController+ViewDidLoadPublisher.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
#if canImport(UIKit) | ||
import Combine | ||
import CombineViewModelObjC | ||
import UIKit | ||
|
||
extension UIViewController { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
16 changes: 0 additions & 16 deletions
16
Sources/CombineViewModelObjC/include/CombineViewModelObjC.h
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#import "ObjCTestSupport.h" | ||
|
||
#ifdef COMBINEVM_HAS_UIKIT | ||
|
||
@implementation TestObjCViewController | ||
|
||
- (void)viewDidLoad { | ||
[super viewDidLoad]; | ||
|
||
_viewDidLoadSelector = _cmd; | ||
} | ||
|
||
@end | ||
|
||
#endif // COMBINEVM_HAS_UIKIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#if defined(__has_include) | ||
# if __has_include(<UIKit/UIKit.h>) | ||
# define COMBINEVM_HAS_UIKIT | ||
# endif | ||
#endif | ||
|
||
#ifdef COMBINEVM_HAS_UIKIT | ||
#import <UIKit/UIKit.h> | ||
|
||
@interface TestObjCViewController : UIViewController | ||
|
||
@property (nonatomic, readonly, nullable) SEL viewDidLoadSelector; | ||
|
||
@end | ||
|
||
#endif // COMBINEVM_HAS_UIKIT |