Skip to content

Commit 78f1523

Browse files
committed
Adding dynamic runtime created classes
1 parent ba9291d commit 78f1523

6 files changed

Lines changed: 240 additions & 55 deletions

File tree

ObjectiveKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
344553061DDA6C1B007852A1 /* RuntimeModification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344553051DDA6C1B007852A1 /* RuntimeModification.swift */; };
1011
344C277F1DD5CDCF008AA223 /* ObjectiveKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 344C27751DD5CDCF008AA223 /* ObjectiveKit.framework */; };
1112
344C27841DD5CDCF008AA223 /* ObjectiveKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344C27831DD5CDCF008AA223 /* ObjectiveKitTests.swift */; };
1213
344C27861DD5CDCF008AA223 /* ObjectiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 344C27781DD5CDCF008AA223 /* ObjectiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -25,6 +26,7 @@
2526
/* End PBXContainerItemProxy section */
2627

2728
/* Begin PBXFileReference section */
29+
344553051DDA6C1B007852A1 /* RuntimeModification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeModification.swift; sourceTree = "<group>"; };
2830
344C27751DD5CDCF008AA223 /* ObjectiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ObjectiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2931
344C27781DD5CDCF008AA223 /* ObjectiveKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjectiveKit.h; sourceTree = "<group>"; };
3032
344C27791DD5CDCF008AA223 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -78,6 +80,7 @@
7880
344C27781DD5CDCF008AA223 /* ObjectiveKit.h */,
7981
344C278F1DD5CFAB008AA223 /* ObjectiveClass.swift */,
8082
344C27931DD6F4E1008AA223 /* RuntimeClass.swift */,
83+
344553051DDA6C1B007852A1 /* RuntimeModification.swift */,
8184
344C27791DD5CDCF008AA223 /* Info.plist */,
8285
);
8386
path = ObjectiveKit;
@@ -206,6 +209,7 @@
206209
buildActionMask = 2147483647;
207210
files = (
208211
344C27901DD5CFAB008AA223 /* ObjectiveClass.swift in Sources */,
212+
344553061DDA6C1B007852A1 /* RuntimeModification.swift in Sources */,
209213
344C27941DD6F4E1008AA223 /* RuntimeClass.swift in Sources */,
210214
);
211215
runOnlyForDeploymentPostprocessing = 0;

ObjectiveKit.xcodeproj/xcuserdata/marmelroy.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,99 @@
1919
landmarkType = "3">
2020
</BreakpointContent>
2121
</BreakpointProxy>
22+
<BreakpointProxy
23+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
24+
<BreakpointContent
25+
shouldBeEnabled = "Yes"
26+
ignoreCount = "0"
27+
continueAfterRunningActions = "No"
28+
filePath = "ObjectiveKitTests/ObjectiveKitTests.swift"
29+
timestampString = "500853461.858655"
30+
startingColumnNumber = "9223372036854775807"
31+
endingColumnNumber = "9223372036854775807"
32+
startingLineNumber = "75"
33+
endingLineNumber = "75">
34+
<Locations>
35+
<Location
36+
shouldBeEnabled = "Yes"
37+
ignoreCount = "0"
38+
continueAfterRunningActions = "No"
39+
symbolName = "ObjectiveKitTests.ObjectiveKitTests.testIntrospection () -&gt; ()"
40+
moduleName = "ObjectiveKitTests"
41+
usesParentBreakpointCondition = "Yes"
42+
urlString = "file:///Users/marmelroy/Documents/Projects/OpenSource/ObjectiveKit/ObjectiveKitTests/ObjectiveKitTests.swift"
43+
timestampString = "500851062.5806"
44+
startingColumnNumber = "9223372036854775807"
45+
endingColumnNumber = "9223372036854775807"
46+
startingLineNumber = "67"
47+
endingLineNumber = "67"
48+
offsetFromSymbolStart = "734">
49+
</Location>
50+
<Location
51+
shouldBeEnabled = "Yes"
52+
ignoreCount = "0"
53+
continueAfterRunningActions = "No"
54+
symbolName = "ObjectiveKitTests.ObjectiveKitTests.(testIntrospection () -&gt; ()).(implicit closure #4)"
55+
moduleName = "ObjectiveKitTests"
56+
usesParentBreakpointCondition = "Yes"
57+
urlString = "file:///Users/marmelroy/Documents/Projects/OpenSource/ObjectiveKit/ObjectiveKitTests/ObjectiveKitTests.swift"
58+
timestampString = "500851062.582614"
59+
startingColumnNumber = "9223372036854775807"
60+
endingColumnNumber = "9223372036854775807"
61+
startingLineNumber = "67"
62+
endingLineNumber = "67"
63+
offsetFromSymbolStart = "16">
64+
</Location>
65+
</Locations>
66+
</BreakpointContent>
67+
</BreakpointProxy>
68+
<BreakpointProxy
69+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
70+
<BreakpointContent
71+
shouldBeEnabled = "Yes"
72+
ignoreCount = "0"
73+
continueAfterRunningActions = "No"
74+
filePath = "ObjectiveKit/ObjectiveClass.swift"
75+
timestampString = "500850921.851736"
76+
startingColumnNumber = "9223372036854775807"
77+
endingColumnNumber = "9223372036854775807"
78+
startingLineNumber = "39"
79+
endingLineNumber = "39"
80+
landmarkName = "allIvars()"
81+
landmarkType = "7">
82+
<Locations>
83+
<Location
84+
shouldBeEnabled = "Yes"
85+
ignoreCount = "0"
86+
continueAfterRunningActions = "No"
87+
symbolName = "ObjectiveKit.ObjectiveClass.allIvars () -&gt; Swift.Array&lt;Swift.String&gt;"
88+
moduleName = "ObjectiveKit"
89+
usesParentBreakpointCondition = "Yes"
90+
urlString = "file:///Users/marmelroy/Documents/Projects/OpenSource/ObjectiveKit/ObjectiveKit/ObjectiveClass.swift"
91+
timestampString = "500851062.585455"
92+
startingColumnNumber = "9223372036854775807"
93+
endingColumnNumber = "9223372036854775807"
94+
startingLineNumber = "39"
95+
endingLineNumber = "39"
96+
offsetFromSymbolStart = "1220">
97+
</Location>
98+
<Location
99+
shouldBeEnabled = "Yes"
100+
ignoreCount = "0"
101+
continueAfterRunningActions = "No"
102+
symbolName = "ObjectiveKit.ObjectiveClass.allIvars () -&gt; Swift.Array&lt;Swift.String&gt;"
103+
moduleName = "ObjectiveKit"
104+
usesParentBreakpointCondition = "Yes"
105+
urlString = "file:///Users/marmelroy/Documents/Projects/OpenSource/ObjectiveKit/ObjectiveKit/ObjectiveClass.swift"
106+
timestampString = "500851062.587205"
107+
startingColumnNumber = "9223372036854775807"
108+
endingColumnNumber = "9223372036854775807"
109+
startingLineNumber = "39"
110+
endingLineNumber = "39"
111+
offsetFromSymbolStart = "1278">
112+
</Location>
113+
</Locations>
114+
</BreakpointContent>
115+
</BreakpointProxy>
22116
</Breakpoints>
23117
</Bucket>

ObjectiveKit/ObjectiveClass.swift

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ import Foundation
1111
typealias ImplementationBlock = @convention(block) () -> Void
1212

1313
/// An object that allows you to introspect and modify classes through the ObjC runtime.
14-
public class ObjectiveClass <T: NSObject> {
14+
public class ObjectiveClass <T: NSObject>: ObjectiveKitRuntimeModification {
1515

16-
private var internalClass: AnyClass
16+
public var internalClass: AnyClass
1717

18-
init() {
18+
// MARK: Lifecycle
19+
20+
/// Init
21+
public init() {
1922
self.internalClass = T.classForCoder()
2023
}
2124

2225
// MARK: Introspection
2326

24-
2527
/// Get all instance variables implemented by the class.
2628
///
2729
/// - Returns: An array of instance variables.
@@ -93,55 +95,6 @@ public class ObjectiveClass <T: NSObject> {
9395
return properties
9496
}
9597

96-
// MARK: Runtime modification
97-
98-
/// Add a selector that is implemented on another object to the current class.
99-
///
100-
/// - Parameters:
101-
/// - selector: Selector.
102-
/// - object: Object implementing the selector.
103-
public func addSelectorToClass(_ selector: Selector, fromClass originalClass: AnyClass) {
104-
guard let method = class_getInstanceMethod(originalClass, selector), let implementation = method_getImplementation(method), let typeEncoding = method_getTypeEncoding(method) else {
105-
return
106-
}
107-
let string = String(cString: typeEncoding)
108-
class_addMethod(internalClass, selector, implementation, string)
109-
}
110-
111-
/// Add a custom method to the current class.
112-
///
113-
/// - Parameters:
114-
/// - identifier: Selector name.
115-
/// - implementation: Implementation as a closure.
116-
public func addMethodToClass(_ identifier: String, implementation: ImplementationBlock) {
117-
let blockObject = unsafeBitCast(implementation, to: AnyObject.self)
118-
let implementation = imp_implementationWithBlock(blockObject)
119-
let selector = NSSelectorFromString(identifier)
120-
let encoding = "v@:f"
121-
class_addMethod(internalClass, selector, implementation, encoding)
122-
}
123-
124-
/// Exchange selectors implemented in the current class.
125-
///
126-
/// - Parameters:
127-
/// - aSelector: Selector.
128-
/// - otherSelector: Selector.
129-
public func exchangeSelector(_ aSelector: Selector, with otherSelector: Selector) {
130-
let method = class_getInstanceMethod(internalClass, aSelector)
131-
let swizzledMethod = class_getInstanceMethod(internalClass, otherSelector)
132-
method_exchangeImplementations(method, swizzledMethod)
133-
}
134-
135-
}
136-
137-
public extension NSObject {
138-
139-
/// A convenience method to perform selectors by identifier strings.
140-
///
141-
/// - Parameter identifier: Selector name.
142-
public func performMethod(_ identifier: String) {
143-
perform(NSSelectorFromString(identifier))
144-
}
14598
}
14699

147100

ObjectiveKit/RuntimeClass.swift

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
import Foundation
1010

1111
/// A class created at runtime.
12-
public class RuntimeClass: NSObject {
12+
public class RuntimeClass: NSObject, ObjectiveKitRuntimeModification {
13+
14+
public var internalClass: AnyClass
1315

14-
private var internalClass: AnyClass
1516
private var registered: Bool = false
1617

18+
// MARK: Lifecycle
19+
1720
/// Init
1821
///
1922
/// - Parameter superclass: Superclass to inherit from.
@@ -22,6 +25,23 @@ public class RuntimeClass: NSObject {
2225
self.internalClass = objc_allocateClassPair(superclass, name, 0)
2326
}
2427

28+
// MARK: Dynamic class creation
29+
30+
/// Add ivar to the newly created class. You can only add ivars before a class is registered.
31+
///
32+
/// - Parameters:
33+
/// - name: Ivar name.
34+
/// - type: Ivar type.
35+
public func addIvar(_ name: String, type: ObjectiveType) {
36+
assert(registered == false, "You can only add ivars before a class is registered")
37+
let rawEncoding = type.encoding()
38+
var size: Int = 0
39+
var alignment: Int = 0
40+
NSGetSizeAndAlignment(rawEncoding, &size, &alignment)
41+
class_addIvar(self.internalClass, name, size, UInt8(alignment), rawEncoding)
42+
}
43+
44+
2545
/// Register class. Required before usage. Happens automatically on allocate.
2646
public func registerClass() {
2747
if registered == false {
@@ -39,3 +59,34 @@ public class RuntimeClass: NSObject {
3959
}
4060

4161
}
62+
63+
64+
/// Objective Type
65+
///
66+
/// - NSString: NSString
67+
/// - NSObject: NSObject
68+
/// - Float: Float
69+
/// - Int: Int
70+
/// - Double: Double
71+
/// - Void: Void
72+
public enum ObjectiveType: Int {
73+
case NSString
74+
case NSObject
75+
case Float
76+
case Int
77+
case Double
78+
case Void
79+
80+
func encoding() -> String {
81+
switch self {
82+
case .NSString: return "@"
83+
case .NSObject: return "@"
84+
case .Float: return "f"
85+
case .Int: return "i"
86+
case .Double: return "d"
87+
case .Void: return "v"
88+
}
89+
}
90+
91+
}
92+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// RuntimeModification.swift
3+
// ObjectiveKit
4+
//
5+
// Created by Roy Marmelstein on 14/11/2016.
6+
// Copyright © 2016 Roy Marmelstein. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public protocol ObjectiveKitRuntimeModification {
12+
13+
var internalClass: AnyClass { get }
14+
15+
// MARK: Runtime modification
16+
17+
/// Add a selector that is implemented on another object to the current class.
18+
///
19+
/// - Parameters:
20+
/// - selector: Selector.
21+
/// - originalClass: Object implementing the selector.
22+
func addSelectorToClass(_ selector: Selector, fromClass originalClass: AnyClass)
23+
24+
/// Add a custom method to the current class.
25+
///
26+
/// - Parameters:
27+
/// - identifier: Selector name.
28+
/// - implementation: Implementation as a closure.
29+
func addMethodToClass(_ identifier: String, implementation: ImplementationBlock)
30+
31+
/// Exchange selectors implemented in the current class.
32+
///
33+
/// - Parameters:
34+
/// - aSelector: Selector.
35+
/// - otherSelector: Selector.
36+
func exchangeSelector(_ aSelector: Selector, with otherSelector: Selector)
37+
38+
}
39+
40+
extension ObjectiveKitRuntimeModification {
41+
42+
public func addSelectorToClass(_ selector: Selector, fromClass originalClass: AnyClass) {
43+
guard let method = class_getInstanceMethod(originalClass, selector), let implementation = method_getImplementation(method), let typeEncoding = method_getTypeEncoding(method) else {
44+
return
45+
}
46+
let string = String(cString: typeEncoding)
47+
class_addMethod(internalClass, selector, implementation, string)
48+
}
49+
50+
public func addMethodToClass(_ identifier: String, implementation: ImplementationBlock) {
51+
let blockObject = unsafeBitCast(implementation, to: AnyObject.self)
52+
let implementation = imp_implementationWithBlock(blockObject)
53+
let selector = NSSelectorFromString(identifier)
54+
let encoding = "v@:f"
55+
class_addMethod(internalClass, selector, implementation, encoding)
56+
}
57+
58+
public func exchangeSelector(_ aSelector: Selector, with otherSelector: Selector) {
59+
let method = class_getInstanceMethod(internalClass, aSelector)
60+
let otherMethod = class_getInstanceMethod(internalClass, otherSelector)
61+
method_exchangeImplementations(method, otherMethod)
62+
}
63+
64+
}
65+
66+
public extension NSObject {
67+
68+
/// A convenience method to perform selectors by identifier strings.
69+
///
70+
/// - Parameter identifier: Selector name.
71+
public func performMethod(_ identifier: String) {
72+
perform(NSSelectorFromString(identifier))
73+
}
74+
}
75+

ObjectiveKitTests/ObjectiveKitTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ import MapKit
4949
XCTAssert(view.responds(to: #selector(testSelector)))
5050
}
5151

52+
func testRuntimeClass() {
53+
let runtimeClass = RuntimeClass()
54+
runtimeClass.addIvar("test", type: .Float)
55+
let runtimeObject = runtimeClass.allocate()
56+
runtimeObject.setValue(4.0, forKey: "test")
57+
XCTAssert(runtimeObject.value(forKey: "test") as? Float == 4.0)
58+
}
59+
5260
func testIntrospection() {
5361
let objectiveView = ObjectiveClass<MKMapView>()
5462
let ivars = objectiveView.allIvars()

0 commit comments

Comments
 (0)