├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── ObjCDump
│ ├── Extension
│ │ └── ObjCType+.swift
│ ├── Model
│ │ ├── ObjCCategoryInfo.swift
│ │ ├── ObjCClassInfo.swift
│ │ ├── ObjCIvarInfo.swift
│ │ ├── ObjCMethodInfo.swift
│ │ ├── ObjCPropertyInfo.swift
│ │ └── ObjCProtocolInfo.swift
│ └── ObjCDump.swift
└── ObjCTypeDecodeKit
│ ├── Extension
│ └── String+.swift
│ ├── Model
│ ├── Node.swift
│ ├── ObjCField.swift
│ ├── ObjCMethodType.swift
│ ├── ObjCModifier.swift
│ ├── ObjCPropertyAttribute.swift
│ ├── ObjCType.swift
│ └── types.swift
│ ├── ObjCMethodTypeDecoder.swift
│ ├── ObjCPropertyTypeDecoder.swift
│ ├── ObjCTypeDecoder.swift
│ └── Protocol
│ └── ObjCTypeCodable.swift
└── Tests
├── ObjCDumpTests
└── ObjCDumpTests.swift
└── ObjCTypeDecodeKitTests
├── ObjCMethodTypeDecodeTests.swift
├── ObjCTypeDecodeKitTests.swift
└── ObjCTypeEncodeTests.swift
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - README.md
9 | - LICENSE
10 | pull_request:
11 | paths-ignore:
12 | - README.md
13 | - LICENSE
14 | workflow_dispatch:
15 |
16 | permissions:
17 | contents: read
18 |
19 | env:
20 | DEVELOPER_DIR: /Applications/Xcode_15.4.app
21 |
22 | jobs:
23 | build:
24 | name: Build & Test
25 | runs-on: macos-14
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 |
30 | - name: Select Xcode 15
31 | run: sudo xcode-select -s /Applications/Xcode_15.4.app
32 |
33 | - name: Build
34 | run: swift build
35 |
36 | - name: Test
37 | run: swift test
38 |
39 | linux-build:
40 | name: Linux Test
41 | runs-on: ubuntu-latest
42 | steps:
43 | - name: Install Swift
44 | # WORKAROUND:https://github.com/swift-actions/setup-swift/pull/680
45 | uses: swift-actions/setup-swift@bb83339d1e8577741bdc6c65ba551ce7dc0fb854
46 | with:
47 | swift-version: '5.10.1'
48 |
49 | - uses: actions/checkout@v4
50 |
51 | - name: Test
52 | run: swift test
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 p-x9
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "ObjCDump",
7 | products: [
8 | .library(
9 | name: "ObjCDump",
10 | targets: ["ObjCDump"]
11 | ),
12 | ],
13 | targets: [
14 | .target(
15 | name: "ObjCDump",
16 | dependencies: [
17 | "ObjCTypeDecodeKit"
18 | ]
19 | ),
20 | .target(
21 | name: "ObjCTypeDecodeKit"
22 | ),
23 | .testTarget(
24 | name: "ObjCDumpTests",
25 | dependencies: ["ObjCDump"]
26 | ),
27 | .testTarget(
28 | name: "ObjCTypeDecodeKitTests",
29 | dependencies: ["ObjCTypeDecodeKit"]
30 | ),
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swift-objc-dump
2 |
3 | A Swift Library for dumping objective-c class/protocol/method/property/ivar informations.
4 |
5 |
6 |
7 | [](https://github.com/p-x9/swift-objc-dump/issues)
8 | [](https://github.com/p-x9/swift-objc-dump/network/members)
9 | [](https://github.com/p-x9/swift-objc-dump/stargazers)
10 | [](https://github.com/p-x9/swift-objc-dump/)
11 |
12 | ## Usage
13 |
14 | Information on Objective-C class/protocol/method/property/ivar can be obtained as follows.
15 |
16 | ```swift
17 | import ObjCDump
18 |
19 | // Class
20 | let info = classInfo(of: NSObject.self)
21 | let methods = info.methods
22 | /* or */
23 | let methods = methods(of: NSObject.self, isInstance: true)
24 |
25 | // Protocol
26 | let `prtocol` = NSProtocolFromString("NSCoding")!
27 | let info = protocolInfo(of: `protocol`)
28 | let methods = info.optionalMethods
29 | /* or */
30 | let methods = methods(of: `protocol`, isRequired: false, isInstance: true)
31 |
32 | // Method
33 | let method = class_getInstanceMethod(NSObject.self, NSSelectorFromString("init"))!
34 | let info = ObjCMethodInfo(method)
35 |
36 | // Property
37 | let property = class_getProperty(NSObject.self, "classDescription")!
38 | let info = ObjCPropertyInfo(method)
39 |
40 | // Ivar
41 | let ivar: Ivar = /**/
42 | let info = ObjCIvarInfo(ivar)
43 | ```
44 |
45 | ### header string
46 |
47 | It is possible to output Objective-C headers as follows.
48 |
49 | ```swift
50 | import ObjCDump
51 |
52 | let info = classInfo(of: NSObject.self)
53 | print(info.headerString)
54 |
55 | /*
56 | @interface NSObject {
57 | Class isa;
58 | }
59 |
60 | @property(class, readonly) BOOL accessInstanceVariablesDirectly;
61 |
62 | @property(readonly, copy) NSClassDescription *classDescription;
63 | @property(readonly, copy) NSArray *attributeKeys;
64 |
65 | /* ...... */
66 |
67 | + (BOOL)xr_object:(id)arg0 isEqual:(id)arg1;
68 | + (void)dealloc;
69 | + (id)description;
70 | + (void)doesNotRecognizeSelector:(SEL)arg0;
71 | + (id)init;
72 | + (id)instanceMethodSignatureForSelector:(SEL)arg0;
73 | + (void)load;
74 | + (id)methodSignatureForSelector:(SEL)arg0;
75 | + (id)__allocWithZone_OA:(struct _NSZone { } *)arg0;
76 |
77 | /* ...... */
78 |
79 | - (id)performSelector:(SEL)arg0 withObject:(id)arg1 withObject:(id)arg2;
80 | - (BOOL)respondsToSelector:(SEL)arg0;
81 | - (id)retain;
82 | - (unsigned long long)retainCount;
83 | - (BOOL)retainWeakReference;
84 | - (id)self;
85 | - (Class)superclass;
86 | - (struct _NSZone { } *)zone;
87 |
88 | @end
89 | */
90 | ```
91 |
92 | ### Objective-C type encoding
93 |
94 | The type information obtained at ObjC line time is encoded.
95 | This is the same conversion that can be obtained with the @encode directive in Objective-C.
96 |
97 | [Reference](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100)
98 |
99 | As there are no published functions for decoding this encoded type information, a new decoder has been implemented.
100 | It can be used as follows
101 |
102 | ```swift
103 | import ObjCTypeDecodeKit
104 |
105 | let type = ObjCTypeDecoder.decode("(?=iI{?=i{CGRect={CGPoint=dd}{CGSize=dd}}})")
106 |
107 | print(type.decoded())
108 | /*
109 | union {
110 | int x0;
111 | unsigned int x1;
112 | struct {
113 | int x0;
114 | struct CGRect {
115 | struct CGPoint {
116 | double x0;
117 | double x1;
118 | } x0;
119 | struct CGSize {
120 | double x0;
121 | double x1;
122 | } x1;
123 | } x1;
124 | } x2;
125 | }
126 | */
127 | ```
128 |
129 | ## License
130 |
131 | swift-objc-dump is released under the MIT License. See [LICENSE](./LICENSE)
132 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Extension/ObjCType+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCType+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/23
6 | //
7 | //
8 |
9 | import Foundation
10 | import ObjCTypeDecodeKit
11 |
12 | extension ObjCType {
13 | var decodedStringForArgument: String {
14 | switch self {
15 | case .struct(let name, let field):
16 | if let name {
17 | return name
18 | } else {
19 | let type: ObjCType = .struct(name: nil, fields: field)
20 | return type.decoded(tab: "")
21 | .components(separatedBy: .newlines)
22 | .joined(separator: " ")
23 | }
24 | case .union(let name, let field):
25 | if let name {
26 | return name
27 | } else {
28 | let type: ObjCType = .union(name: nil, fields: field)
29 | return type.decoded(tab: "")
30 | .components(separatedBy: .newlines)
31 | .joined(separator: " ")
32 | }
33 | // Objective-C BOOL types may be represented by signed char or by C/C++ bool types.
34 | // This means that the type encoding may be represented as C(c) or as B.
35 | // [reference](https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc.h#L61-L86)
36 | case .char: fallthrough
37 | case .uchar:
38 | return "BOOL"
39 | default:
40 | break
41 | }
42 |
43 | return decoded(tab: "")
44 | .components(separatedBy: .newlines)
45 | .joined(separator: " ")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCCategoryInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCCategoryInfo.swift
3 | // ObjCDump
4 | //
5 | // Created by p-x9 on 2024/12/11
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | /// Structure for representing objc category information.
12 | public struct ObjCCategoryInfo {
13 | /// Name of the category
14 | public let name: String
15 |
16 | /// Name of the target class name
17 | public let className: String
18 |
19 | /// List of protocols to which the class conforms.
20 | public let protocols: [ObjCProtocolInfo]
21 |
22 | /// List of class properties held by the category.
23 | public let classProperties: [ObjCPropertyInfo]
24 | /// List of instance properties held by the category.
25 | public let properties: [ObjCPropertyInfo]
26 |
27 | /// List of class methods held by the category.
28 | public let classMethods: [ObjCMethodInfo]
29 | /// List of instance methods held by the category.
30 | public let methods: [ObjCMethodInfo]
31 |
32 | /// Initializes a new instance of `ObjCCategoryInfo`.
33 | /// - Parameters:
34 | /// - name: Name of the category
35 | /// - className: Name of the target class name
36 | /// - protocols: List of protocols to which the class conforms.
37 | /// - classProperties: List of class properties held by the category.
38 | /// - properties: List of instance properties held by the category.
39 | /// - classMethods: List of class methods held by the category.
40 | /// - methods: List of instance methods held by the category.
41 | public init(
42 | name: String,
43 | className: String,
44 | protocols: [ObjCProtocolInfo],
45 | classProperties: [ObjCPropertyInfo],
46 | properties: [ObjCPropertyInfo],
47 | classMethods: [ObjCMethodInfo],
48 | methods: [ObjCMethodInfo]
49 | ) {
50 | self.name = name
51 | self.className = className
52 | self.protocols = protocols
53 | self.classProperties = classProperties
54 | self.properties = properties
55 | self.classMethods = classMethods
56 | self.methods = methods
57 | }
58 | }
59 |
60 | extension ObjCCategoryInfo {
61 | public var headerString: String {
62 | var decl = "@interface \(className) (\(name))"
63 |
64 | if !protocols.isEmpty {
65 | let protocols = protocols.map(\.name)
66 | .joined(separator: ", ")
67 | decl += " <\(protocols)>"
68 | }
69 |
70 | var lines = [decl]
71 |
72 | if !classProperties.isEmpty {
73 | lines.append("")
74 | lines += classProperties.map(\.headerString)
75 | }
76 | if !properties.isEmpty {
77 | lines.append("")
78 | lines += properties.map(\.headerString)
79 | }
80 |
81 | if !classMethods.isEmpty {
82 | lines.append("")
83 | lines += classMethods.map(\.headerString)
84 | }
85 | if !methods.isEmpty {
86 | lines.append("")
87 | lines += methods.map(\.headerString)
88 | }
89 |
90 | lines += ["", "@end"]
91 |
92 | return lines.joined(separator: "\n")
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCClassInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCClassInfo.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/24
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | /// Structure for representing objc class information.
12 | public struct ObjCClassInfo {
13 | /// Name of the class
14 | public let name: String
15 | /// Version of the class
16 | public let version: Int32
17 | /// Name of the dynamic library the class originated from.
18 | public let imageName: String?
19 |
20 | /// Size of instances of the class.
21 | public let instanceSize: Int
22 |
23 | /// Super class name of the class
24 | public let superClassName: String?
25 |
26 | /// List of protocols to which the class conforms.
27 | public let protocols: [ObjCProtocolInfo]
28 |
29 | /// List of instance variables held by the class.
30 | public let ivars: [ObjCIvarInfo]
31 |
32 | /// List of class properties held by the class.
33 | public let classProperties: [ObjCPropertyInfo]
34 | /// List of instance properties held by the class.
35 | public let properties: [ObjCPropertyInfo]
36 |
37 | /// List of class methods held by the class.
38 | public let classMethods: [ObjCMethodInfo]
39 | /// List of instance methods held by the class.
40 | public let methods: [ObjCMethodInfo]
41 |
42 |
43 | /// Initializes a new instance of `ObjCClassInfo`.
44 | /// - Parameters:
45 | /// - name: Name of the class
46 | /// - version: Version of the class
47 | /// - imageName: Name of the dynamic library the class originated from.
48 | /// - instanceSize: Size of instances of the class.
49 | /// - superClassName: Super class name of the class
50 | /// - protocols: List of protocols to which the class conforms.
51 | /// - ivars: List of instance variables held by the class.
52 | /// - classProperties: List of class properties held by the class.
53 | /// - properties: List of instance properties held by the class.
54 | /// - classMethods: List of class methods held by the class.
55 | /// - methods: List of instance methods held by the class.
56 | public init(
57 | name: String,
58 | version: Int32,
59 | imageName: String?,
60 | instanceSize: Int,
61 | superClassName: String?,
62 | protocols: [ObjCProtocolInfo],
63 | ivars: [ObjCIvarInfo],
64 | classProperties: [ObjCPropertyInfo],
65 | properties: [ObjCPropertyInfo],
66 | classMethods: [ObjCMethodInfo],
67 | methods: [ObjCMethodInfo]
68 | ) {
69 | self.name = name
70 | self.version = version
71 | self.imageName = imageName
72 | self.instanceSize = instanceSize
73 | self.superClassName = superClassName
74 | self.protocols = protocols
75 | self.ivars = ivars
76 | self.classProperties = classProperties
77 | self.properties = properties
78 | self.classMethods = classMethods
79 | self.methods = methods
80 | }
81 |
82 | #if canImport(ObjectiveC)
83 | /// Initializes a new instance of `ObjCClassInfo`.
84 | /// - Parameter cls: Class of the target for which information is to be obtained.
85 | public init(_ cls: AnyClass) {
86 | // Name
87 | let name = NSStringFromClass(cls)
88 |
89 | // Version
90 | let version = class_getVersion(cls)
91 |
92 | // imageName
93 | let _imageName = class_getImageName(cls)
94 | let imageName: String? = if let _imageName {
95 | String(cString: _imageName)
96 | } else { nil }
97 |
98 | // instance Size
99 | let instanceSize = class_getInstanceSize(cls)
100 |
101 | // super class
102 | let superCls: AnyClass? = class_getSuperclass(cls)
103 | let superClassName: String? = if let superCls {
104 | NSStringFromClass(superCls)
105 | } else { nil }
106 |
107 | self.init(
108 | name: name,
109 | version: version,
110 | imageName: imageName,
111 | instanceSize: instanceSize,
112 | superClassName: superClassName,
113 | protocols: Self.protocols(of: cls),
114 | ivars: Self.ivars(of: cls),
115 | classProperties: Self.properties(of: cls, isInstance: false),
116 | properties: Self.properties(of: cls, isInstance: true),
117 | classMethods: Self.methods(of: cls, isInstance: false),
118 | methods: Self.methods(of: cls, isInstance: true)
119 | )
120 | }
121 | #endif
122 | }
123 |
124 | extension ObjCClassInfo {
125 | public var headerString: String {
126 | var decl = "@interface \(name)"
127 | if let superClassName {
128 | decl += " : \(superClassName)"
129 | }
130 |
131 | if !protocols.isEmpty {
132 | let protocols = protocols.map(\.name)
133 | .joined(separator: ", ")
134 | decl += " <\(protocols)>"
135 | }
136 |
137 | var lines = [decl]
138 |
139 | if !ivars.isEmpty {
140 | lines[0] += " {"
141 | lines += ivars
142 | .map(\.headerString)
143 | .map {
144 | $0.components(separatedBy: .newlines)
145 | .map { " \($0)" }
146 | .joined(separator: "\n")
147 | }
148 | lines.append("}")
149 | }
150 |
151 | if !classProperties.isEmpty {
152 | lines.append("")
153 | lines += classProperties.map(\.headerString)
154 | }
155 | if !properties.isEmpty {
156 | lines.append("")
157 | lines += properties.map(\.headerString)
158 | }
159 |
160 | if !classMethods.isEmpty {
161 | lines.append("")
162 | lines += classMethods.map(\.headerString)
163 | }
164 | if !methods.isEmpty {
165 | lines.append("")
166 | lines += methods.map(\.headerString)
167 | }
168 |
169 | lines += ["", "@end"]
170 |
171 | return lines.joined(separator: "\n")
172 | }
173 | }
174 |
175 | #if canImport(ObjectiveC)
176 | extension ObjCClassInfo {
177 | public static func protocols(
178 | of cls: AnyClass
179 | ) -> [ObjCProtocolInfo] {
180 | var count: UInt32 = 0
181 | let start = class_copyProtocolList(cls, &count)
182 | guard let start else { return [] }
183 |
184 | defer {
185 | free(.init(start))
186 | }
187 | return UnsafeBufferPointer(start: start, count: Int(count))
188 | .compactMap {
189 | ObjCProtocolInfo($0)
190 | }
191 | }
192 |
193 | public static func ivars(
194 | of cls: AnyClass
195 | ) -> [ObjCIvarInfo] {
196 | var count: UInt32 = 0
197 | let start = class_copyIvarList(cls, &count)
198 | guard let start else { return [] }
199 |
200 | defer {
201 | free(.init(start))
202 | }
203 | return UnsafeBufferPointer(start: start, count: Int(count))
204 | .compactMap {
205 | ObjCIvarInfo($0)
206 | }
207 | }
208 |
209 | public static func properties(
210 | of cls: AnyClass,
211 | isInstance: Bool
212 | ) -> [ObjCPropertyInfo] {
213 | var cls: AnyClass = cls
214 | if !isInstance {
215 | cls = objc_getMetaClass(NSStringFromClass(cls)) as! AnyClass
216 | }
217 |
218 | var count: UInt32 = 0
219 | let start = class_copyPropertyList(cls, &count)
220 | guard let start else { return [] }
221 |
222 | defer {
223 | free(.init(start))
224 | }
225 | return UnsafeBufferPointer(start: start, count: Int(count))
226 | .compactMap {
227 | ObjCPropertyInfo($0, isClassProperty: !isInstance)
228 | }
229 | }
230 |
231 | public static func methods(
232 | of cls: AnyClass,
233 | isInstance: Bool
234 | ) -> [ObjCMethodInfo] {
235 | var cls: AnyClass = cls
236 | if !isInstance {
237 | cls = objc_getMetaClass(NSStringFromClass(cls)) as! AnyClass
238 | }
239 |
240 | var count: UInt32 = 0
241 | let start = class_copyMethodList(cls, &count)
242 | guard let start else { return [] }
243 |
244 | defer {
245 | free(.init(start))
246 | }
247 | return UnsafeBufferPointer(start: start, count: Int(count))
248 | .compactMap {
249 | ObjCMethodInfo($0, isClassMethod: !isInstance)
250 | }
251 | }
252 | }
253 | #endif
254 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCIvarInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCIvarInfo.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/23
6 | //
7 | //
8 |
9 | import Foundation
10 | import ObjCTypeDecodeKit
11 |
12 | /// Structure for representing objc instance variable information.
13 | public struct ObjCIvarInfo {
14 | /// Name of the Ivar
15 | public let name: String
16 | /// Encoded type of the Ivar
17 | public let typeEncoding: String
18 | /// Offset of Ivar
19 | public let offset: Int
20 |
21 | /// Initializes a new instance of `ObjCIvarInfo`.
22 | /// - Parameters:
23 | /// - name: Name of the Ivar
24 | /// - typeEncoding: Encoded type of the Ivar
25 | /// - offset: Offset of Ivar
26 | public init(
27 | name: String,
28 | typeEncoding: String,
29 | offset: Int
30 | ) {
31 | self.name = name
32 | self.typeEncoding = typeEncoding
33 | self.offset = offset
34 | }
35 |
36 | #if canImport(ObjectiveC)
37 | /// Initializes a new instance of `ObjCIvarInfo`.
38 | /// - Parameter ivar: Ivar of the target for which information is to be obtained.
39 | public init?(_ ivar: Ivar) {
40 | guard let _name = ivar_getName(ivar),
41 | let _typeEncoding = ivar_getTypeEncoding(ivar) else {
42 | return nil
43 | }
44 |
45 | self.init(
46 | name: String(cString: _name),
47 | typeEncoding: String(cString: _typeEncoding),
48 | offset: ivar_getOffset(ivar)
49 | )
50 | }
51 | #endif
52 | }
53 |
54 | extension ObjCIvarInfo {
55 | /// Type of Ivar
56 | public var type: ObjCType? {
57 | ObjCTypeDecoder.decode(typeEncoding)
58 | }
59 | }
60 |
61 | extension ObjCIvarInfo {
62 | public var headerString: String {
63 | if let type, case let .bitField(width) = type {
64 | let field = ObjCField(
65 | type: .int,
66 | name: name,
67 | bitWidth: width
68 | )
69 | return field.decodedForHeader(fallbackName: name)
70 | } else {
71 | if [.char, .uchar].contains(type) {
72 | return "BOOL \(name)"
73 | }
74 | let type = type?.decoded()
75 | if let type, type.last == "*" {
76 | return "\(type)\(name);"
77 | }
78 | return "\(type ?? "unknown") \(name);"
79 | }
80 | }
81 | }
82 |
83 | extension ObjCField {
84 | func decodedForHeader(
85 | fallbackName: String, tab: String = " "
86 | ) -> String {
87 | if [.char, .uchar].contains(type) {
88 | return "BOOL \(name ?? fallbackName);"
89 | }
90 | return decoded(fallbackName: fallbackName, tab: tab)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCMethodInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCMethodInfo.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/23
6 | //
7 | //
8 |
9 | import Foundation
10 | import ObjCTypeDecodeKit
11 |
12 | /// Structure for representing objc method information.
13 | @dynamicMemberLookup
14 | public struct ObjCMethodInfo {
15 | /// Name of the method
16 | public let name: String
17 | /// Encoded method type of the method
18 | public let typeEncoding: String
19 | /// A boolean value that indicates whatever the method is class method or not.
20 | public let isClassMethod: Bool
21 |
22 | /// Initializes a new instance of `ObjCMethodInfo`.
23 | /// - Parameters:
24 | /// - name: Name of the method
25 | /// - typeEncoding: Encoded method type of the method
26 | /// - isClassMethod: A boolean value that indicates whatever the method is class method or not.
27 | public init(
28 | name: String,
29 | typeEncoding: String,
30 | isClassMethod: Bool
31 | ) {
32 | self.name = name
33 | self.typeEncoding = typeEncoding
34 | self.isClassMethod = isClassMethod
35 | }
36 |
37 | #if canImport(ObjectiveC)
38 | /// Initializes a new instance of `ObjCMethodInfo`.
39 | /// - Parameters:
40 | /// - method: Method of the target for which information is to be obtained.
41 | /// - isClassMethod: A boolean value that indicates whatever the method is class method or not.
42 | public init?(
43 | _ method: Method,
44 | isClassMethod: Bool
45 | ) {
46 | guard let _typeEncoding = method_getTypeEncoding(method) else {
47 | return nil
48 | }
49 | let _name = method_getName(method)
50 | self.init(
51 | name: NSStringFromSelector(_name),
52 | typeEncoding: String(cString: _typeEncoding),
53 | isClassMethod: isClassMethod
54 | )
55 | }
56 |
57 | /// Initializes a new instance of `ObjCMethodInfo`.
58 | /// - Parameters:
59 | /// - description: Method description of the target for which information is to be obtained.
60 | /// - isClassMethod: A boolean value that indicates whatever the method is class method or not.
61 | public init?(
62 | _ description: objc_method_description,
63 | isClassMethod: Bool
64 | ) {
65 | guard let _name = description.name,
66 | let _typeEncoding = description.types else {
67 | return nil
68 | }
69 |
70 | self.init(
71 | name: NSStringFromSelector(_name),
72 | typeEncoding: String(cString: _typeEncoding),
73 | isClassMethod: isClassMethod
74 | )
75 | }
76 | #endif
77 | }
78 |
79 | extension ObjCMethodInfo {
80 | /// Method type of the method
81 | ///
82 | /// It includes the type of the arguments and the type of the return value.
83 | public var type: ObjCMethodType? {
84 | ObjCMethodTypeDecoder.decode(typeEncoding)
85 | }
86 |
87 | public subscript(dynamicMember keyPath: KeyPath) -> V? {
88 | type?[keyPath: keyPath]
89 | }
90 | }
91 |
92 | extension ObjCMethodInfo {
93 | public var headerString: String {
94 | let prefix = isClassMethod ? "+" : "-"
95 | let type: ObjCMethodType? = self.type
96 |
97 | // return type
98 | let returnType = type?.returnType.decodedStringForArgument ?? "unknown"
99 |
100 | // arguments
101 | let numberOfArguments = name.filter({ $0 == ":" }).count
102 | guard numberOfArguments > 0 else {
103 | return "\(prefix) (\(returnType))\(name);"
104 | }
105 |
106 | let nameAndLabels = name.split(separator: ":")
107 |
108 | let argumentInfos = type?.argumentInfos ?? []
109 | let argumentTypes = argumentInfos.map(\.type.decodedStringForArgument)
110 |
111 | var result = "\(prefix) (\(returnType))"
112 |
113 | zip(nameAndLabels, argumentTypes).enumerated()
114 | .forEach { (i, nameWithType) in
115 | let (name, type) = nameWithType
116 | var entry = "\(name):(\(type))arg\(i)"
117 | if i != 0 { entry = " \(entry)" }
118 | result += entry
119 | }
120 |
121 | result += ";"
122 |
123 | return result
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCPropertyInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCPropertyInfo.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/24
6 | //
7 | //
8 |
9 | import Foundation
10 | import ObjCTypeDecodeKit
11 |
12 | /// Structure for representing objc property information.
13 | public struct ObjCPropertyInfo {
14 | /// Name of the property
15 | public let name: String
16 | /// Attributes string of the property
17 | public let attributesString: String
18 | /// A boolean value that indicates whatever the property is class property or not.
19 | public let isClassProperty: Bool
20 |
21 | /// Initializes a new instance of `ObjCPropertyInfo`.
22 | /// - Parameters:
23 | /// - name: Name of the property
24 | /// - attributes: Attributes string of the property
25 | /// - isClassProperty: A boolean value that indicates whatever the property is class property or not.
26 | public init(
27 | name: String,
28 | attributes: String,
29 | isClassProperty: Bool
30 | ) {
31 | self.name = name
32 | self.attributesString = attributes
33 | self.isClassProperty = isClassProperty
34 | }
35 |
36 | #if canImport(ObjectiveC)
37 | /// Initializes a new instance of `ObjCPropertyInfo`.
38 | /// - Parameters:
39 | /// - property: Property of the target for which information is to be obtained.
40 | /// - isClassProperty: A boolean value that indicates whatever the property is class property or not.
41 | public init?(
42 | _ property: objc_property_t,
43 | isClassProperty: Bool
44 | ) {
45 | guard let _attributes = property_getAttributes(property) else {
46 | return nil
47 | }
48 | let _name = property_getName(property)
49 | self.init(
50 | name: String(cString: _name),
51 | attributes: String(cString: _attributes),
52 | isClassProperty: isClassProperty
53 | )
54 | }
55 | #endif
56 | }
57 |
58 | extension ObjCPropertyInfo {
59 | /// Attribute list of the property
60 | public var attributes: [ObjCPropertyAttribute] {
61 | ObjCPropertyTypeDecoder.decode(attributesString)
62 | }
63 | }
64 |
65 | extension ObjCPropertyInfo {
66 | public var headerString: String {
67 | let attributes = self.attributes
68 | // Type
69 | let type = attributes.compactMap {
70 | if case let .type(type) = $0, let type { return type }
71 | return nil
72 | }.first
73 | let typeString = type?.decodedStringForArgument ?? "unknown"
74 |
75 | // Attributes
76 | var _attributes: [String] = []
77 | // class
78 | if isClassProperty {
79 | _attributes.append("class")
80 | }
81 |
82 | // getter
83 | if let getter = attributes.compactMap({
84 | if case let .getter(name) = $0 { return name }
85 | return nil
86 | }).first {
87 | _attributes.append("getter=\(getter)")
88 | }
89 | // setter
90 | if let setter = attributes.compactMap({
91 | if case let .setter(name) = $0 { return name }
92 | return nil
93 | }).first {
94 | _attributes.append("setter=\(setter)")
95 | }
96 |
97 | // readonly
98 | if attributes.contains(.readonly) {
99 | _attributes.append("readonly")
100 | }
101 |
102 | // weak
103 | if attributes.contains(.weak) {
104 | _attributes.append("weak")
105 | }
106 | // copy
107 | if attributes.contains(.copy) {
108 | _attributes.append("copy")
109 | }
110 | // retain
111 | if attributes.contains(.retain) {
112 | _attributes.append("retain")
113 | }
114 |
115 | // nonatomic
116 | if attributes.contains(.nonatomic) {
117 | _attributes.append("nonatomic")
118 | }
119 |
120 | // Comments
121 | var comments: [String] = []
122 | if attributes.contains(.dynamic) {
123 | comments.append("@dynamic \(name)")
124 | }
125 | if let ivar = attributes.compactMap({
126 | if case let .ivar(name) = $0 { return name }
127 | return nil
128 | }).first {
129 | if ivar == name {
130 | comments.append("@synthesize \(ivar)")
131 | } else {
132 | comments.append("@synthesize \(name)=\(ivar)")
133 | }
134 | }
135 |
136 | var result = "@property"
137 | if !_attributes.isEmpty {
138 | result += "("
139 | result += _attributes.joined(separator: ", ")
140 | result += ")"
141 | }
142 | result += " \(typeString)"
143 | if typeString.last == "*" {
144 | result += "\(name);"
145 | } else {
146 | result += " \(name);"
147 | }
148 | if !comments.isEmpty {
149 | for comment in comments {
150 | result += " // \(comment)"
151 | }
152 | }
153 |
154 | return result
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/Model/ObjCProtocolInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCProtocolInfo.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/26
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | /// Structure for representing objc protocol information.
12 | public struct ObjCProtocolInfo {
13 | /// Name of the protocol
14 | public let name: String
15 |
16 | /// List of protocols to which the protocol conforms.
17 | public let protocols: [ObjCProtocolInfo]
18 |
19 | /// List of required class properties.
20 | public let classProperties: [ObjCPropertyInfo]
21 | /// List of required instance properties.
22 | public let properties: [ObjCPropertyInfo]
23 | /// List of required class methods.
24 | public let classMethods: [ObjCMethodInfo]
25 | /// List of required instance methods.
26 | public let methods: [ObjCMethodInfo]
27 |
28 | /// List of optional class properties.
29 | public let optionalClassProperties: [ObjCPropertyInfo]
30 | /// List of optional instance properties.
31 | public let optionalProperties: [ObjCPropertyInfo]
32 | /// List of optional class methods.
33 | public let optionalClassMethods: [ObjCMethodInfo]
34 | /// List of optional instance methods.
35 | public let optionalMethods: [ObjCMethodInfo]
36 |
37 | /// Initializes a new instance of `ObjCProtocolInfo`.
38 | /// - Parameters:
39 | /// - name: Name of the protocol
40 | /// - protocols: List of protocols to which the protocol conforms.
41 | /// - classProperties: List of required class properties.
42 | /// - properties: List of required instance properties.
43 | /// - classMethods: List of required class methods.
44 | /// - methods: List of required instance methods.
45 | /// - optionalClassProperties: List of optional class properties.
46 | /// - optionalProperties: List of optional instance properties.
47 | /// - optionalClassMethods: List of optional class methods.
48 | /// - optionalMethods: List of optional instance methods.
49 | ///
50 | /// Objective-C protocol does not currently support optional properties.
51 | /// [reference](https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L5255)
52 | public init(
53 | name: String,
54 | protocols: [ObjCProtocolInfo],
55 | classProperties: [ObjCPropertyInfo],
56 | properties: [ObjCPropertyInfo],
57 | classMethods: [ObjCMethodInfo],
58 | methods: [ObjCMethodInfo],
59 | optionalClassProperties: [ObjCPropertyInfo] = [],
60 | optionalProperties: [ObjCPropertyInfo] = [],
61 | optionalClassMethods: [ObjCMethodInfo],
62 | optionalMethods: [ObjCMethodInfo]
63 | ) {
64 | self.name = name
65 | self.protocols = protocols
66 | self.classProperties = classProperties
67 | self.properties = properties
68 | self.classMethods = classMethods
69 | self.methods = methods
70 | self.optionalClassProperties = optionalClassProperties
71 | self.optionalProperties = optionalProperties
72 | self.optionalClassMethods = optionalClassMethods
73 | self.optionalMethods = optionalMethods
74 | }
75 |
76 | #if canImport(ObjectiveC)
77 | /// Initializes a new instance of `ObjCProtocolInfo`.
78 | /// - Parameter `protocol`: Protocol of the target for which information is to be obtained.
79 | public init(_ `protocol`: Protocol) {
80 | // Name
81 | let _name = protocol_getName(`protocol`)
82 | let name = String(cString: _name)
83 |
84 | self.init(
85 | name: name,
86 | protocols: Self.protocols(
87 | of: `protocol`
88 | ),
89 | classProperties: Self.properties(
90 | of: `protocol`,
91 | isRequired: true, isInstance: false
92 | ),
93 | properties: Self.properties(
94 | of: `protocol`,
95 | isRequired: true, isInstance: true
96 | ),
97 | classMethods: Self.methods(
98 | of: `protocol`,
99 | isRequired: true, isInstance: false
100 | ),
101 | methods: Self.methods(
102 | of: `protocol`,
103 | isRequired: true, isInstance: true
104 | ),
105 | optionalClassProperties: Self.properties(
106 | of: `protocol`,
107 | isRequired: false, isInstance: false
108 | ),
109 | optionalProperties: Self.properties(
110 | of: `protocol`,
111 | isRequired: false,
112 | isInstance: true)
113 | ,
114 | optionalClassMethods: Self.methods(
115 | of: `protocol`,
116 | isRequired: false, isInstance: false
117 | ),
118 | optionalMethods: Self.methods(
119 | of: `protocol`,
120 | isRequired: false, isInstance: true
121 | )
122 | )
123 | }
124 | #endif
125 | }
126 |
127 | extension ObjCProtocolInfo {
128 | public var headerString: String {
129 | var decl = "@protocol \(name)"
130 | if !protocols.isEmpty {
131 | let protocols = protocols.map(\.name)
132 | .joined(separator: ", ")
133 | decl += " <\(protocols)>"
134 | }
135 |
136 | var lines = [decl]
137 |
138 | if !classProperties.isEmpty ||
139 | !properties.isEmpty ||
140 | !classMethods.isEmpty ||
141 | !methods.isEmpty {
142 | lines += ["", "@required"]
143 | }
144 |
145 | if !classProperties.isEmpty {
146 | lines.append("")
147 | lines += classProperties.map(\.headerString)
148 | }
149 | if !properties.isEmpty {
150 | lines.append("")
151 | lines += properties.map(\.headerString)
152 | }
153 |
154 | if !classMethods.isEmpty {
155 | lines.append("")
156 | lines += classMethods.map(\.headerString)
157 | }
158 | if !methods.isEmpty {
159 | lines.append("")
160 | lines += methods.map(\.headerString)
161 | }
162 |
163 | if !optionalClassProperties.isEmpty ||
164 | !optionalProperties.isEmpty ||
165 | !optionalClassMethods.isEmpty ||
166 | !optionalMethods.isEmpty {
167 | lines += ["", "@optional"]
168 | }
169 |
170 | if !optionalClassProperties.isEmpty {
171 | lines.append("")
172 | lines += optionalClassProperties.map(\.headerString)
173 | }
174 | if !optionalProperties.isEmpty {
175 | lines.append("")
176 | lines += optionalProperties.map(\.headerString)
177 | }
178 | if !optionalClassMethods.isEmpty {
179 | lines.append("")
180 | lines += optionalClassMethods.map(\.headerString)
181 | }
182 | if !optionalMethods.isEmpty {
183 | lines.append("")
184 | lines += optionalMethods.map(\.headerString)
185 | }
186 |
187 | lines += ["", "@end"]
188 |
189 | return lines.joined(separator: "\n")
190 | }
191 | }
192 |
193 | #if canImport(ObjectiveC)
194 | extension ObjCProtocolInfo {
195 | public static func protocols(
196 | of `protocol`: Protocol
197 | ) -> [ObjCProtocolInfo] {
198 | var count: UInt32 = 0
199 | let start = protocol_copyProtocolList(`protocol`, &count)
200 | guard let start else { return [] }
201 |
202 | defer {
203 | free(.init(start))
204 | }
205 | return UnsafeBufferPointer(start: start, count: Int(count))
206 | .compactMap {
207 | ObjCProtocolInfo($0)
208 | }
209 | }
210 |
211 | public static func properties(
212 | of `protocol`: Protocol,
213 | isRequired: Bool,
214 | isInstance: Bool
215 | ) -> [ObjCPropertyInfo] {
216 | var count: UInt32 = 0
217 | let start = protocol_copyPropertyList2(`protocol`, &count, isRequired, isInstance)
218 | guard let start else { return [] }
219 |
220 | defer {
221 | free(.init(start))
222 | }
223 | return UnsafeBufferPointer(start: start, count: Int(count))
224 | .compactMap {
225 | ObjCPropertyInfo($0, isClassProperty: !isInstance)
226 | }
227 | }
228 |
229 | public static func methods(
230 | of `protocol`: Protocol,
231 | isRequired: Bool,
232 | isInstance: Bool
233 | ) -> [ObjCMethodInfo] {
234 | var count: UInt32 = 0
235 | let start = protocol_copyMethodDescriptionList(`protocol`, isRequired, isInstance, &count)
236 | guard let start else { return [] }
237 |
238 | defer {
239 | free(.init(start))
240 | }
241 | return UnsafeBufferPointer(start: start, count: Int(count))
242 | .compactMap {
243 | ObjCMethodInfo($0, isClassMethod: !isInstance)
244 | }
245 | }
246 | }
247 | #endif
248 |
--------------------------------------------------------------------------------
/Sources/ObjCDump/ObjCDump.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | #if canImport(ObjectiveC)
4 | // MARK: - Functions for Class
5 |
6 | /// Obtain informations about the class.
7 | /// - Parameter cls: Target class.
8 | /// - Returns: Information about the class, e.g. property lists, method lists, etc.
9 | @inlinable
10 | public func classInfo(for cls: AnyClass) -> ObjCClassInfo? {
11 | ObjCClassInfo(cls)
12 | }
13 |
14 | /// Obtain a list of information on the protocols to which the class conforms.
15 | /// - Parameter cls: Target class
16 | /// - Returns: List of protocol infos
17 | @inlinable
18 | public func protocols(of cls: AnyClass) -> [ObjCProtocolInfo] {
19 | ObjCClassInfo.protocols(of: cls)
20 | }
21 |
22 | /// Obtain a list of Instance variables held by the class.
23 | /// - Parameter cls: Target class
24 | /// - Returns: List of ivar infos
25 | @inlinable
26 | public func ivars(of cls: AnyClass) -> [ObjCIvarInfo] {
27 | ObjCClassInfo.ivars(of: cls)
28 | }
29 |
30 | /// Obtain a list of properties held by the class.
31 | /// - Parameters:
32 | /// - cls: Target class
33 | /// - isInstance: A Boolean value that indicates whether the instance property is targeted.
34 | /// - Returns: List of property infos
35 | @inlinable
36 | public func properties(of cls: AnyClass, isInstance: Bool) -> [ObjCPropertyInfo] {
37 | ObjCClassInfo.properties(of: cls, isInstance: isInstance)
38 | }
39 |
40 | /// Obtain a list of method held by the class.
41 | /// - Parameters:
42 | /// - cls: Target class
43 | /// - isInstance: A Boolean value that indicates whether the instance methods is targeted.
44 | /// - Returns: List of method infos
45 | @inlinable
46 | public func methods(of cls: AnyClass, isInstance: Bool) -> [ObjCMethodInfo] {
47 | ObjCClassInfo.methods(of: cls, isInstance: isInstance)
48 | }
49 |
50 | // MARK: - Functions for Protocol
51 |
52 | /// Obtain informations about the protocol.
53 | /// - Parameter `protocol`: Target protocol
54 | /// - Returns: Information about the protocol, e.g. property lists, method lists, etc.
55 | @inlinable
56 | public func protocolInfo(for `protocol`: Protocol) -> ObjCProtocolInfo? {
57 | ObjCProtocolInfo(`protocol`)
58 | }
59 |
60 | /// Obtain a list of information on the protocols to which the protocol conforms.
61 | /// - Parameter `protocol`: Target class
62 | /// - Returns: List of protocol infos
63 | @inlinable
64 | public func protocols(of `protocol`: Protocol) -> [ObjCProtocolInfo] {
65 | ObjCProtocolInfo.protocols(of: `protocol`)
66 | }
67 |
68 | /// Obtain a list of (optionally) required properties by the protocol.
69 | /// - Parameters:
70 | /// - `protocol`: Target protocol
71 | /// - isRequired: A Boolean value that indicates whether it is required or optional
72 | /// - isInstance: A Boolean value that indicates whether the instance properties is targeted.
73 | /// - Returns: List of (optionally) required property infos
74 | @inlinable
75 | public func properties(
76 | of `protocol`: Protocol,
77 | isRequired: Bool,
78 | isInstance: Bool
79 | ) -> [ObjCPropertyInfo] {
80 | ObjCProtocolInfo.properties(
81 | of: `protocol`, isRequired: isRequired, isInstance: isInstance
82 | )
83 | }
84 |
85 | /// Obtain a list of (optionally) required method by the protocol.
86 | /// - Parameters:
87 | /// - `protocol`: Target protocol
88 | /// - isRequired: A Boolean value that indicates whether it is required or optional
89 | /// - isInstance: A Boolean value that indicates whether the instance method is targeted.
90 | /// - Returns: List of (optionally) required method infos
91 | @inlinable
92 | public func methods(
93 | of `protocol`: Protocol,
94 | isRequired: Bool,
95 | isInstance: Bool
96 | ) -> [ObjCMethodInfo] {
97 | ObjCProtocolInfo.methods(
98 | of: `protocol`, isRequired: isRequired, isInstance: isInstance
99 | )
100 | }
101 | #endif
102 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Extension/String+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/19
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | /// Finds the index of the matching closing bracket for a given opening bracket.
13 | /// - Parameters:
14 | /// - open: The opening bracket character.
15 | /// - close: The closing bracket character.
16 | /// - Returns: The index of the matching closing bracket if found, otherwise `nil`.
17 | /// - Complexity: O(n), where n is the length of the string.
18 | func indexForMatchingBracket(
19 | open: Character,
20 | close: Character
21 | ) -> Int? {
22 | var depth = 0
23 | for (index, char) in enumerated() {
24 | depth += (char == open) ? 1 : (char == close) ? -1 : 0
25 | if depth == 0 {
26 | return index
27 | }
28 | }
29 | return nil
30 | }
31 |
32 | /// Finds the index of the first closing double quote that matches the given opening double quote in the string.
33 | /// - Parameter openIndex: The index of the opening double quote.
34 | /// - Returns: The index of the matching closing double quote, or `nil` if not found.
35 | func indexForFirstMatchingQuote(
36 | openIndex: String.Index
37 | ) -> Int? {
38 | var inQuote = false
39 | var index = openIndex
40 | while index < endIndex {
41 | if self[index] == "\"" {
42 | if inQuote {
43 | return distance(from: startIndex, to: index)
44 | } else {
45 | inQuote = true
46 | }
47 | }
48 | index = self.index(after: index)
49 | }
50 | return nil
51 | }
52 |
53 | /// Extracts the initial sequence of numeric characters from the string.
54 | /// - Returns: A string containing the initial sequence of numeric characters if any, otherwise `nil`.
55 | /// - Complexity: O(n), where n is the length of the string.
56 | func readInitialDigits() -> String? {
57 | var digits = ""
58 | for (i, char) in self.enumerated() {
59 | if i == 0 && char == "-" {
60 | digits.append("-")
61 | } else if char.isNumber {
62 | digits.append(char)
63 | } else {
64 | break
65 | }
66 | }
67 | if digits == "-" { return nil }
68 | return digits.isEmpty ? nil : digits
69 | }
70 | }
71 |
72 | extension String {
73 | func trailing(after index: Index) -> String? {
74 | if distance(from: index, to: endIndex) > 0 {
75 | return String(self[self.index(after: index) ..< endIndex])
76 | }
77 | return nil
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/Node.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Node.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/19
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Node {
12 | public var decoded: ObjCType?
13 | public var trailing: String?
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/ObjCField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCField.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/21
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ObjCField: Equatable {
12 | public let type: ObjCType
13 | public var name: String?
14 | public var bitWidth: Int?
15 |
16 | package init(
17 | type: ObjCType,
18 | name: String? = nil,
19 | bitWidth: Int? = nil
20 | ) {
21 | self.type = type
22 | self.name = name
23 | self.bitWidth = bitWidth
24 | }
25 | }
26 |
27 | extension ObjCField: ObjCTypeDecodable {
28 | public func decoded(tab: String = " ") -> String {
29 | if let bitWidth {
30 | "\(type.decoded(tab: tab)) \(name ?? "x") : \(bitWidth);"
31 | } else {
32 | "\(type.decoded(tab: tab)) \(name ?? "x");"
33 | }
34 | }
35 |
36 | public func decoded(fallbackName: String, tab: String = " ") -> String {
37 | if let bitWidth {
38 | "\(type.decoded(tab: tab)) \(name ?? fallbackName) : \(bitWidth);"
39 | } else {
40 | "\(type.decoded(tab: tab)) \(name ?? fallbackName);"
41 | }
42 | }
43 | }
44 |
45 | extension ObjCField: ObjCTypeEncodable {
46 | public func encoded() -> String {
47 | if let bitWidth {
48 | if let name {
49 | return "\"\(name)\"b\(bitWidth)"
50 | } else {
51 | return "b\(bitWidth)"
52 | }
53 | } else {
54 | if let name {
55 | return "\"\(name)\"\(type.encoded())"
56 | } else {
57 | return "\(type.encoded())"
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/ObjCMethodType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCMethodType.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/22
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ObjCMethodType {
12 | public struct ArgumentInfo {
13 | public let type: ObjCType
14 | public let offset: Int
15 | }
16 |
17 | public let returnType: ObjCType
18 | public let stackSize: Int
19 |
20 | public let selfInfo: ArgumentInfo
21 | public let selectorInfo: ArgumentInfo
22 | public let argumentInfos: [ArgumentInfo]
23 | }
24 |
25 | extension ObjCMethodType: ObjCTypeEncodable {
26 | public func encoded() -> String {
27 | let arguments = argumentInfos
28 | .map({ $0.encoded() })
29 | .joined()
30 | return "\(returnType.encoded())\(stackSize)\(selfInfo.encoded())\(selectorInfo.encoded())\(arguments)"
31 | }
32 | }
33 |
34 | extension ObjCMethodType.ArgumentInfo: ObjCTypeEncodable {
35 | public func encoded() -> String {
36 | "\(type.encoded())\(offset)"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/ObjCModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCModifier.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/21
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ObjCModifier: CaseIterable, Equatable {
12 | case complex
13 | case atomic
14 | case const
15 | case `in`
16 | case `inout`
17 | case out
18 | case bycopy
19 | case byref
20 | case oneway
21 | case register
22 | }
23 |
24 | extension ObjCModifier: ObjCTypeEncodable {
25 | public func encoded() -> String {
26 | switch self {
27 | case .complex: "j" // #include
28 | case .atomic: "A" // #include
29 | case .const: "r"
30 | case .in: "n"
31 | case .inout: "N"
32 | case .out: "o"
33 | case .bycopy: "O"
34 | case .byref: "R"
35 | case .oneway: "V"
36 | case .register: "+" // FIXME: ????
37 | }
38 | }
39 | }
40 |
41 | extension ObjCModifier: ObjCTypeDecodable {
42 | public func decoded(tab: String = " ") -> String {
43 | switch self {
44 | case .complex: "_Complex" // #include
45 | case .atomic: "_Atomic" // #include
46 | case .const: "const"
47 | case .in: "in"
48 | case .inout: "inout"
49 | case .out: "out"
50 | case .bycopy: "bycopy"
51 | case .byref: "byref"
52 | case .oneway: "oneway"
53 | case .register: "register" // FIXME: ????
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/ObjCPropertyAttribute.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCPropertyAttribute.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/23
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1
12 | public enum ObjCPropertyAttribute: Equatable {
13 | case type(ObjCType?) // T
14 | case readonly // R
15 | case copy // C
16 | case retain // &
17 | case nonatomic // N
18 | case getter(name: String) // G
19 | case setter(name: String) // S
20 | case dynamic // D
21 | case weak // W
22 | case ivar(name: String) // V
23 | // case p // P The property is eligible for garbage collection.
24 | // case t // t Specifies the type using old-style encoding.
25 | case other(String)
26 | }
27 |
28 | extension ObjCPropertyAttribute: ObjCTypeEncodable {
29 | public func encoded() -> String {
30 | switch self {
31 | case .type(let type):
32 | if let type {
33 | "T\(type.encoded())"
34 | } else {
35 | "T"
36 | }
37 | case .readonly: "R"
38 | case .copy: "C"
39 | case .retain: "&"
40 | case .nonatomic: "N"
41 | case .getter(let name): "G\(name)"
42 | case .setter(let name): "S\(name)"
43 | case .dynamic: "D"
44 | case .weak: "W"
45 | case .ivar(let name): "V\(name)"
46 | case .other(let string): "\(string)"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/ObjCType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCType.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/21
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public indirect enum ObjCType: Equatable {
12 | case `class`
13 | case selector
14 |
15 | case char
16 | case uchar
17 |
18 | case short
19 | case ushort
20 |
21 | case int
22 | case uint
23 |
24 | case long
25 | case ulong
26 |
27 | case longLong
28 | case ulongLong
29 |
30 | case int128
31 | case uint128
32 |
33 | case float
34 | case double
35 | case longDouble
36 |
37 | case bool
38 | case void
39 | case unknown
40 |
41 | case charPtr
42 |
43 | case atom
44 |
45 | case object(name: String?)
46 | case block(return: ObjCType?, args: [ObjCType]?)
47 | case functionPointer
48 |
49 | case array(type: ObjCType, size: Int?)
50 | case pointer(type: ObjCType)
51 |
52 | case bitField(width: Int)
53 |
54 | case union(name: String?, fields: [ObjCField]?)
55 | case `struct`(name: String?, fields: [ObjCField]?)
56 |
57 | case modified(_ modifier: ObjCModifier, type: ObjCType)
58 |
59 | case other(String)
60 | }
61 |
62 | extension ObjCType: ObjCTypeDecodable {
63 | public func decoded(tab: String = " ") -> String {
64 | switch self {
65 | case .class: return "Class"
66 | case .selector: return "SEL"
67 | case .char: return "char"
68 | case .uchar: return "unsigned char"
69 | case .short: return "short"
70 | case .ushort: return "unsigned short"
71 | case .int: return "int"
72 | case .uint: return "unsigned int"
73 | case .long: return "long"
74 | case .ulong: return "unsigned long"
75 | case .longLong: return "long long"
76 | case .ulongLong: return "unsigned long long"
77 | case .int128: return "__int128_t"
78 | case .uint128: return "__uint128_t"
79 | case .float: return "float"
80 | case .double: return "double"
81 | case .longDouble: return "long double"
82 | case .bool: return "BOOL"
83 | case .void: return "void"
84 | case .unknown: return "unknown"
85 | case .charPtr: return "char *"
86 | case .atom: return "atom"
87 | case .object(let name):
88 | if let name {
89 | if name.first == "<" && name.last == ">" {
90 | return "id \(name)"
91 | }
92 | return "\(name) *"
93 | } else {
94 | return "id"
95 | }
96 | case .block(let ret, let args):
97 | guard let ret, let args else {
98 | return "id /* block */"
99 | }
100 | let argTypes = args
101 | .map({ $0.decoded(tab: tab) })
102 | .joined(separator: ", ")
103 | return "\(ret.decoded(tab: tab)) (^)(\(argTypes))"
104 | case .functionPointer:
105 | return "void * /* function pointer */"
106 | case .array(let type, let size):
107 | if let size {
108 | return "\(type.decoded(tab: tab))[\(size)]"
109 | } else {
110 | return "\(type.decoded(tab: tab))[]"
111 | }
112 | case .pointer(let type):
113 | return "\(type.decoded(tab: tab)) *"
114 | case .bitField(let width):
115 | return "int x : \(width)"
116 | case .union(let name, let fields):
117 | guard let fields, !fields.isEmpty else {
118 | if let name {
119 | return "union \(name)"
120 | } else {
121 | return "union {}"
122 | }
123 | }
124 | let fieldDefs = fields
125 | .enumerated()
126 | .map {
127 | $1.decoded(fallbackName: "x\($0)", tab: tab)
128 | .components(separatedBy: .newlines)
129 | .map { tab + $0 }
130 | .joined(separator: "\n")
131 | }
132 | .joined(separator: "\n")
133 | if let name {
134 | return """
135 | union \(name) {
136 | \(fieldDefs)
137 | }
138 | """
139 | } else {
140 | return """
141 | union {
142 | \(fieldDefs)
143 | }
144 | """
145 | }
146 | case .struct(let name, let fields):
147 | guard let fields, !fields.isEmpty else {
148 | if let name {
149 | return "struct \(name)"
150 | } else {
151 | return "struct {}"
152 | }
153 | }
154 | let fieldDefs = fields
155 | .enumerated()
156 | .map {
157 | $1.decoded(fallbackName: "x\($0)", tab: tab)
158 | .components(separatedBy: .newlines)
159 | .map { tab + $0 }
160 | .joined(separator: "\n")
161 | }
162 | .joined(separator: "\n")
163 | if let name {
164 | return """
165 | struct \(name) {
166 | \(fieldDefs)
167 | }
168 | """
169 | } else {
170 | return """
171 | struct {
172 | \(fieldDefs)
173 | }
174 | """
175 | }
176 | case .modified(let modifier, let type):
177 | return "\(modifier.decoded(tab: tab)) \(type.decoded(tab: tab))"
178 |
179 | case .other(let string):
180 | return string
181 | }
182 | }
183 | }
184 |
185 | extension ObjCType: ObjCTypeEncodable {
186 | public func encoded() -> String {
187 | switch self {
188 | case .class: return "#"
189 | case .selector: return ":"
190 | case .char: return "c"
191 | case .uchar: return "C"
192 | case .short: return "s"
193 | case .ushort: return "S"
194 | case .int: return "i"
195 | case .uint: return "I"
196 | case .long: return "l"
197 | case .ulong: return "L"
198 | case .longLong: return "q"
199 | case .ulongLong: return "Q"
200 | case .int128: return "t"
201 | case .uint128: return "T"
202 | case .float: return "f"
203 | case .double: return "d"
204 | case .longDouble: return "D"
205 | case .bool: return "B"
206 | case .void: return "v"
207 | case .unknown: return "?"
208 | case .charPtr: return "*"
209 | case .atom: return "%"
210 | case .object(let name):
211 | if let name {
212 | return "@\"\(name)\""
213 | } else {
214 | return "@"
215 | }
216 | case let .block(ret, args):
217 | guard let ret, let args else {
218 | return "@?"
219 | }
220 | let argTypes = args.map({ $0.encoded() }).joined()
221 | return "@?<\(ret.encoded())@?\(argTypes)>"
222 | case .functionPointer: return "^?"
223 | case .array(let type, let size):
224 | if let size {
225 | return "[\(size)\(type.encoded())]"
226 | } else {
227 | return "[\(type.encoded())]"
228 | }
229 | case .pointer(let type):
230 | return "^\(type.encoded())"
231 | case .bitField(let width):
232 | return "b\(width)"
233 | case .union(let name, let fields):
234 | guard let fields else {
235 | if let name { return "(\(name))" }
236 | else { return "()" }
237 | }
238 | let fieldDefs = fields.map({ $0.encoded() })
239 | .joined()
240 | if let name {
241 | return "(\(name)=\(fieldDefs))"
242 | } else {
243 | return "(?=\(fieldDefs))"
244 | }
245 | case .struct(let name, let fields):
246 | guard let fields else {
247 | if let name { return "{\(name)}" }
248 | else { return "{}" }
249 | }
250 | let fieldDefs = fields.map({ $0.encoded() })
251 | .joined()
252 | if let name {
253 | return "{\(name)=\(fieldDefs)}"
254 | } else {
255 | return "{?=\(fieldDefs)}"
256 | }
257 | case .modified(let modifier, let type):
258 | return "\(modifier.encoded())\(type.encoded())"
259 |
260 | case .other(let string):
261 | return string
262 | }
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Model/types.swift:
--------------------------------------------------------------------------------
1 | //
2 | // types.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/19
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | // ref: https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/runtime.h#L1856
12 |
13 | let simpleTypes: [Character: ObjCType] = [
14 | "@": .object(name: nil),
15 | "#": .class,
16 | ":": .selector,
17 | "c": .char,
18 | "C": .uchar,
19 | "s": .short,
20 | "S": .ushort,
21 | "i": .int,
22 | "I": .uint,
23 | "l": .long,
24 | "L": .ulong,
25 | "q": .longLong,
26 | "Q": .ulongLong,
27 | "t": .int128,
28 | "T": .uint128,
29 | "f": .float,
30 | "d": .double,
31 | "D": .longDouble,
32 | "B": .bool,
33 | "v": .void,
34 | "?": .unknown,
35 | "*": .charPtr,
36 | "%": .atom // FIXME: ?????
37 | ]
38 |
39 | let modifiers: [Character: ObjCModifier] = [
40 | "A": .atomic, // #include
41 | "j": .complex, // #include
42 | "r": .const,
43 | "n": .in,
44 | "N": .`inout`,
45 | "o": .out,
46 | "O": .bycopy,
47 | "R": .byref,
48 | "V": .oneway,
49 | "+": .register, // FIXME: ????
50 | ]
51 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/ObjCMethodTypeDecoder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCMethodTypeDecoder.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/22
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ObjCMethodTypeDecoder {
12 | // ref: https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-typeencoding.mm#L171
13 | public static func decode(_ type: String) -> ObjCMethodType? {
14 | guard let node = ObjCTypeDecoder._decode(type),
15 | let returnType = node.decoded,
16 | var trailing = node.trailing else {
17 | return nil
18 | }
19 |
20 | if trailing.first == "+" { trailing.removeFirst() }
21 |
22 | // Stack Size
23 | guard let _stackSize = trailing.readInitialDigits(),
24 | let stackSize = Int(_stackSize) else {
25 | return nil
26 | }
27 | trailing.removeFirst(_stackSize.count)
28 |
29 | // self
30 | guard let node = ObjCTypeDecoder._decode(trailing),
31 | let selfType = node.decoded,
32 | var trailing = node.trailing else {
33 | return nil
34 | }
35 | guard let _selfOffset = trailing.readInitialDigits(),
36 | let selfOffset = Int(_selfOffset) else {
37 | return nil
38 | }
39 | trailing.removeFirst(_selfOffset.count)
40 | let selfInfo = ObjCMethodType.ArgumentInfo(
41 | type: selfType,
42 | offset: selfOffset
43 | )
44 |
45 | // arguments
46 | var arguments: [ObjCMethodType.ArgumentInfo] = []
47 | while !trailing.isEmpty {
48 | guard let node = ObjCTypeDecoder._decode(trailing),
49 | let type = node.decoded,
50 | var _trailing = node.trailing else {
51 | return nil
52 | }
53 | guard let _offset = _trailing.readInitialDigits(),
54 | let offset = Int(_offset) else {
55 | return nil
56 | }
57 | _trailing.removeFirst(_offset.count)
58 | trailing = _trailing
59 | arguments.append(
60 | ObjCMethodType.ArgumentInfo(
61 | type: type,
62 | offset: offset
63 | )
64 | )
65 | }
66 |
67 | guard let selectorInfo = arguments.first else { return nil }
68 | if arguments.count == 1 { arguments = [] }
69 | else { arguments = Array(arguments[1...]) }
70 |
71 | return .init(
72 | returnType: returnType,
73 | stackSize: stackSize,
74 | selfInfo: selfInfo,
75 | selectorInfo: selectorInfo,
76 | argumentInfos: arguments
77 | )
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/ObjCPropertyTypeDecoder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCPropertyTypeDecoder.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/23
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ObjCPropertyTypeDecoder {
12 | public static func decode(_ type: String) -> [ObjCPropertyAttribute] {
13 | let _attributes = type.split(separator: ",").map { String($0) }
14 | var attributes: [ObjCPropertyAttribute] = []
15 |
16 | for _attribute in _attributes {
17 | guard let first = _attribute.first else {
18 | attributes.append(.other(_attribute))
19 | continue
20 | }
21 | switch first {
22 | case "T":
23 | var type = _attribute
24 | type.removeFirst()
25 | attributes.append(
26 | .type(ObjCTypeDecoder.decode(type))
27 | )
28 | case "R": attributes.append(.readonly)
29 | case "C": attributes.append(.copy)
30 | case "&": attributes.append(.retain)
31 | case "N": attributes.append(.nonatomic)
32 | case "G":
33 | var name = _attribute
34 | name.removeFirst()
35 | attributes.append(
36 | .getter(name: name)
37 | )
38 | case "S":
39 | var name = _attribute
40 | name.removeFirst()
41 | attributes.append(
42 | .setter(name: name)
43 | )
44 | case "D": attributes.append(.dynamic)
45 | case "W": attributes.append(.weak)
46 | case "V":
47 | var name = _attribute
48 | name.removeFirst()
49 | attributes.append(
50 | .ivar(name: name)
51 | )
52 | default:
53 | attributes.append(.other(_attribute))
54 | }
55 | }
56 |
57 | return attributes
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/ObjCTypeDecoder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCTypeDecoder.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/19
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ObjCTypeDecoder {
12 |
13 | public static func decode(_ type: String) -> ObjCType? {
14 | _decode(type)?.decoded
15 | }
16 |
17 | public static func _decode(_ type: String) -> Node? {
18 | guard let first = type.first else { return nil }
19 |
20 | switch first {
21 | // decode `id`
22 | // ref: https://github.com/gnustep/libobjc2/blob/2855d1771478e1e368fcfeb4d56aecbb4d9429ca/encoding2.c#L159
23 | case _ where type.starts(with: "@?"):
24 | var trailing = type
25 | trailing.removeFirst(2)
26 |
27 | if trailing.starts(with: "<") {
28 | guard let closingIndex = trailing.indexForMatchingBracket(
29 | open: "<", close: ">"
30 | ) else { return nil }
31 | let startInex = trailing.index(trailing.startIndex, offsetBy: 1)
32 | let endIndex = trailing.index(trailing.startIndex, offsetBy: closingIndex)
33 | let content = String(trailing[startInex ..< endIndex]) //
34 | guard let retNode = _decode(content),
35 | let retType = retNode.decoded else {
36 | return nil
37 | }
38 | guard var args = retNode.trailing else { return nil }
39 | guard args.starts(with: "@?") else { return nil }
40 | args.removeFirst(2)
41 | var argTypes: [ObjCType] = []
42 | while !args.isEmpty {
43 | guard let node = _decode(args),
44 | let argType = node.decoded else {
45 | return nil
46 | }
47 | argTypes.append(argType)
48 | if let trailing = node.trailing {
49 | args = trailing
50 | } else {
51 | break
52 | }
53 | }
54 | let trailing = trailing.trailing(after: endIndex)
55 | return .init(
56 | decoded: .block(return: retType, args: argTypes),
57 | trailing: trailing
58 | )
59 | }
60 | return .init(
61 | decoded: .block(return: nil, args: nil),
62 | trailing: trailing
63 | )
64 |
65 | case _ where type.starts(with: #"@""#):
66 | guard let closingIndex = type.indexForFirstMatchingQuote(
67 | openIndex: type.index(after: type.startIndex)
68 | ) else { return nil }
69 | let startIndex = type.index(type.startIndex, offsetBy: 2)
70 | let endIndex = type.index(type.startIndex, offsetBy: closingIndex)
71 | let name = String(type[startIndex ..< endIndex])
72 |
73 | let trailing = type.trailing(after: endIndex)
74 | return .init(decoded: .object(name: name), trailing: trailing)
75 |
76 | case _ where type.starts(with: "^?"):
77 | var trailing = type
78 | trailing.removeFirst(2)
79 | return .init(decoded: .functionPointer, trailing: trailing)
80 |
81 | case _ where simpleTypes.keys.contains(first):
82 | var trailing = type
83 | trailing.removeFirst()
84 | return .init(decoded: simpleTypes[first], trailing: trailing)
85 |
86 | case _ where modifiers.keys.contains(first):
87 | var content = type
88 | content.removeFirst()
89 | guard let modifier = modifiers[first],
90 | let content = _decode(content),
91 | let contentType = content.decoded else {
92 | return nil
93 | }
94 | return .init(
95 | decoded: .modified(modifier, type: contentType),
96 | trailing: content.trailing
97 | )
98 |
99 | case "b":
100 | var content = type
101 | content.removeFirst()
102 |
103 | guard let _length = content.readInitialDigits(),
104 | let length = Int(_length) else {
105 | return nil
106 | }
107 | let trailing = type.trailing(
108 | after: type.index(type.startIndex, offsetBy: _length.count)
109 | )
110 | return .init(
111 | decoded: .bitField(width: length),
112 | trailing: trailing
113 | )
114 |
115 | case "[":
116 | return _decodeArray(type)
117 | case "^":
118 | return _decodePointer(type)
119 | case "(":
120 | return _decodeUnion(type)
121 | case "{":
122 | return _decodeStruct(type)
123 |
124 | default:
125 | break
126 | }
127 | return nil
128 | }
129 | }
130 |
131 | extension ObjCTypeDecoder {
132 |
133 | // MARK: - Pointer ^
134 | private static func _decodePointer(_ type: String) -> Node? {
135 | var content = type
136 | content.removeFirst() // ^
137 |
138 | guard let node = _decode(content),
139 | let contentType = node.decoded else {
140 | return nil
141 | }
142 |
143 | return .init(decoded: .pointer(type: contentType), trailing: node.trailing)
144 | }
145 |
146 | // MARK: - Bit Field b
147 | private static func _decodeBitField(_ type: String, name: String?) -> (field: ObjCField, trailing: String?)? {
148 | var content = type
149 | content.removeFirst() // b
150 |
151 | guard let _length = content.readInitialDigits(),
152 | let length = Int(_length) else {
153 | return nil
154 | }
155 |
156 | let endInex = content.index(content.startIndex, offsetBy: _length.count)
157 | let trailing = type.trailing(after: endInex)
158 |
159 | return (
160 | .init(type: .int, name: name, bitWidth: length),
161 | trailing
162 | )
163 | }
164 |
165 | // MARK: - Array []
166 | private static func _decodeArray(_ type: String) -> Node? {
167 |
168 | guard let closingIndex = type.indexForMatchingBracket(open: "[", close: "]") else {
169 | return nil
170 | }
171 |
172 | let startInex = type.index(type.startIndex, offsetBy: 1)
173 | let endIndex = type.index(type.startIndex, offsetBy: closingIndex)
174 |
175 | var content = String(type[startInex ..< endIndex]) // [content]
176 |
177 | let length = content.readInitialDigits()
178 |
179 | if let _length = length,
180 | let length = Int(_length) {
181 | content.removeFirst(_length.count)
182 | guard let node = _decode(content),
183 | let contentType = node.decoded else {
184 | return nil
185 | }
186 |
187 | // TODO: `node.trailing` must be empty
188 | let trailing = type.trailing(after: endIndex)
189 |
190 | return .init(
191 | decoded: .array(type: contentType, size: length),
192 | trailing: trailing
193 | )
194 | }
195 |
196 | guard let node = _decode(content),
197 | let contentType = node.decoded else {
198 | return nil
199 | }
200 |
201 | // TODO: `node.trailing` must be empty
202 | let trailing = type.trailing(after: endIndex)
203 |
204 | return .init(
205 | decoded: .array(type: contentType, size: nil),
206 | trailing: trailing
207 | )
208 | }
209 |
210 | // MARK: - Union ()
211 | private static func _decodeUnion(_ type: String) -> Node? {
212 | return _decodeFields(type, for: .union)
213 | }
214 |
215 | // MARK: - Struct {}
216 | private static func _decodeStruct(_ type: String) -> Node? {
217 | return _decodeFields(type, for: .struct)
218 | }
219 |
220 | // MARK: - Union or Struct
221 | enum _TypeKind: String {
222 | case `struct`
223 | case union
224 | }
225 |
226 | private static func _decodeFields(_ type: String, for kind: _TypeKind) -> Node? {
227 | let open, close: Character
228 | switch kind {
229 | case .struct:
230 | open = "{"; close = "}"
231 | case .union:
232 | open = "("; close = ")"
233 | }
234 |
235 | guard let closingIndex = type.indexForMatchingBracket(open: open, close: close) else {
236 | return nil
237 | }
238 |
239 | let startInex = type.index(type.startIndex, offsetBy: 1)
240 | let endIndex = type.index(type.startIndex, offsetBy: closingIndex)
241 |
242 | let content = String(type[startInex ..< endIndex]) // (content)
243 | guard !content.isEmpty else { return nil }
244 |
245 | guard let equalIndex = content.firstIndex(of: "=") else {
246 | let trailing = type.trailing(after: endIndex)
247 | switch kind {
248 | case .struct:
249 | return .init(decoded: .struct(name: content, fields: nil), trailing: trailing)
250 | case .union:
251 | return .init(decoded: .union(name: content, fields: nil), trailing: trailing)
252 | }
253 | }
254 |
255 | var typeName: String? = String(content[content.startIndex ..< equalIndex])
256 | if typeName == "?" { typeName = nil }
257 |
258 | var _fields = String(content[content.index(equalIndex, offsetBy: 1) ..< content.endIndex])
259 | var fields: [ObjCField] = []
260 |
261 | var number = 0
262 | while !_fields.isEmpty {
263 | guard let (field, trailing) = _decodeField(_fields) else { break }
264 | fields.append(field)
265 | if let trailing {
266 | _fields = trailing
267 | number += 1
268 | } else {
269 | break
270 | }
271 | }
272 |
273 | let trailing = type.trailing(after: endIndex)
274 |
275 | switch kind {
276 | case .struct:
277 | return .init(
278 | decoded: .struct(name: typeName, fields: fields),
279 | trailing: trailing
280 | )
281 | case .union:
282 | return .init(
283 | decoded: .union(name: typeName, fields: fields),
284 | trailing: trailing)
285 |
286 | }
287 | }
288 |
289 | private static func _decodeField(_ type: String) -> (field: ObjCField, trailing: String?)? {
290 | guard let first = type.first else { return nil }
291 | switch first {
292 | case "b":
293 | return _decodeBitField(type, name: nil)
294 | case "\"":
295 | guard let closingIndex = type.indexForFirstMatchingQuote(
296 | openIndex: type.startIndex
297 | ) else { return nil }
298 | let startIndex = type.index(type.startIndex, offsetBy: 1)
299 | let endIndex = type.index(type.startIndex, offsetBy: closingIndex)
300 | let name = String(type[startIndex ..< endIndex])
301 |
302 | let contentType = String(type[type.index(after: endIndex) ..< type.endIndex])
303 | if contentType.starts(with: "b"),
304 | let (field, trailing) = _decodeBitField(contentType, name: name) {
305 | return (field, trailing)
306 | } else if let node = _decode(contentType),
307 | let contentType = node.decoded {
308 | return (
309 | .init(type: contentType, name: name),
310 | node.trailing
311 | )
312 | } else { return nil }
313 |
314 | default:
315 | guard let node = _decode(type),
316 | let decoded = node.decoded else {
317 | return nil
318 | }
319 | return (
320 | .init(type: decoded),
321 | node.trailing
322 | )
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/Sources/ObjCTypeDecodeKit/Protocol/ObjCTypeCodable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCTypeCodable.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/21
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public typealias ObjCTypeCodable = ObjCTypeDecodable & ObjCTypeEncodable
12 |
13 | public protocol ObjCTypeDecodable {
14 | func decoded(tab: String) -> String
15 | }
16 |
17 | public protocol ObjCTypeEncodable {
18 | func encoded() -> String
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/ObjCDumpTests/ObjCDumpTests.swift:
--------------------------------------------------------------------------------
1 | #if canImport(ObjectiveC)
2 |
3 | import XCTest
4 | @testable import ObjCDump
5 |
6 | final class ObjCDumpTests: XCTestCase {
7 | func testClass() {
8 | guard let info = classInfo(for: ObjCDumpTests.self) else {
9 | XCTAssert(true)
10 | return
11 | }
12 | print(info.headerString)
13 | }
14 |
15 | func testProtocol() {
16 | guard let `protocol` = NSProtocolFromString("NSCoding"),
17 | let info = protocolInfo(for: `protocol`) else {
18 | XCTAssert(true)
19 | return
20 | }
21 | print(info.headerString)
22 | }
23 | }
24 |
25 | #endif
26 |
--------------------------------------------------------------------------------
/Tests/ObjCTypeDecodeKitTests/ObjCMethodTypeDecodeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCMethodTypeDecodeTests.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/22
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import ObjCTypeDecodeKit
11 |
12 | final class ObjCMethodTypeDecodeTests: XCTestCase {
13 | func test() {
14 | // Instance Methods
15 | checkEncode("v32@0:8@16^{CGContext=}24")
16 | checkEncode("v40@0:8@16^{CGContext=}24@?32")
17 | checkEncode("v52@0:8B16{CGRect={CGPoint=dd}{CGSize=dd}}20")
18 | checkEncode("@32@0:8#16:24")
19 | checkEncode("@40@0:8{CGPoint=dd}16q32")
20 | checkEncode("@48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16")
21 | checkEncode("^{CGSRegionObject=}56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48B52")
22 | checkEncode("@24@0:8q16")
23 | checkEncode("f24@0:8q16")
24 | checkEncode("v40@0:8@16@24@32")
25 | checkEncode("@56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48")
26 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}64@0:8{CGPoint=dd}16{CGRect={CGPoint=dd}{CGSize=dd}}32")
27 | checkEncode("B24@0:8@16")
28 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}36@0:8@16@24B32")
29 | checkEncode("v16@0:8")
30 | checkEncode("v40@0:8Q16@24@32")
31 | checkEncode("@24@0:8^{CGPoint=dd}16")
32 | checkEncode("B76@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24@56B64@68")
33 | checkEncode("@16@0:8")
34 | checkEncode("v36@0:8@16B24@?28")
35 | checkEncode("B16@0:8")
36 | checkEncode("v60@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48B56")
37 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}16@0:8")
38 | checkEncode("@20@0:8B16")
39 | checkEncode("{CGSize=dd}16@0:8")
40 | checkEncode("v64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16{CGSize=dd}48")
41 | checkEncode("v24@0:8d16")
42 | checkEncode("v56@0:8@16{NSEdgeInsets=dddd}24")
43 | checkEncode("^{_CAView=}16@0:8")
44 | checkEncode("B28@0:8@16B24")
45 | checkEncode("i16@0:8")
46 | checkEncode("B24@0:8B16B20")
47 | checkEncode("@24@0:8@?16")
48 | checkEncode("v40@0:8^{CGSize=dd}16^{CGSize=dd}24^{CGSize=dd}32")
49 | checkEncode("B32@0:8r^{CGRect={CGPoint=dd}{CGSize=dd}}16@24")
50 | checkEncode("@?16@0:8")
51 | checkEncode("v44@0:8^{CGSize=dd}16^{CGSize=dd}24^{CGSize=dd}32f40")
52 | checkEncode("q64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48@56")
53 | checkEncode("@48@0:8@16q24@32d40")
54 | checkEncode("v56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48")
55 | checkEncode("@32@0:8q16@24")
56 | checkEncode("d32@0:8^{CGSize=dd}16@24")
57 | checkEncode("v56@0:8@16{_NSRange=QQ}24@40@?48")
58 | checkEncode("v20@0:8I16")
59 | checkEncode("v32@0:8^?16^v24")
60 | checkEncode("v64@0:8{CGAffineTransform=dddddd}16")
61 | checkEncode("@24@0:8^{CGRect={CGPoint=dd}{CGSize=dd}}16")
62 | checkEncode("B40@0:8@16d24^@32")
63 | checkEncode("v28@0:8@16B24")
64 | checkEncode("B68@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24B56@60")
65 | checkEncode("B40@0:8@16q24@32")
66 | checkEncode("d16@0:8")
67 | checkEncode("B56@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24")
68 | checkEncode("v64@0:8B16{CGRect={CGPoint=dd}{CGSize=dd}}20@52B60")
69 | checkEncode("B52@0:8i16@20d28@36@44")
70 | checkEncode("B48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16")
71 | checkEncode("{NSEdgeInsets=dddd}56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48")
72 | checkEncode("B64@0:8{CGPoint=dd}16{CGRect={CGPoint=dd}{CGSize=dd}}32")
73 | checkEncode("B24@0:8^v16")
74 | checkEncode("@24@0:8^{CGSize=dd}16")
75 | checkEncode("{CGPoint=dd}24@0:8@16")
76 | checkEncode("v40@0:8@16{CGPoint=dd}24")
77 | checkEncode("v32@0:8{CGSize=dd}16")
78 | checkEncode("@28@0:8q16B24")
79 | checkEncode("Q24@0:8@16")
80 | checkEncode("v48@0:8Q16d24@32@?40")
81 | checkEncode("B32@0:8^q16^q24")
82 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}52@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48")
83 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}24@0:8q16")
84 | checkEncode("v96@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48@56@64@72q80@88")
85 | checkEncode("{NSEdgeInsets=dddd}48@0:8{NSEdgeInsets=dddd}16")
86 | checkEncode("q64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48^v56")
87 | checkEncode("v84@0:8@16{CGPoint=dd}24{CGSize=dd}40@56@64@72B80")
88 | checkEncode("{CGAffineTransform=dddddd}24@0:8@16")
89 | checkEncode("v24@0:8^@16")
90 | checkEncode("v52@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48")
91 | checkEncode("v40@0:8@16q24@32")
92 | checkEncode("B56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48")
93 | checkEncode("B68@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48B56@?60")
94 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}40@0:8@16q24@32")
95 | checkEncode("v32@0:8@16@24")
96 | checkEncode("B32@0:8@16@24")
97 | checkEncode("@56@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24")
98 | checkEncode("v32@0:8[4{CGRect={CGPoint=dd}{CGSize=dd}}]16^q24")
99 | checkEncode("v20@0:8B16")
100 | checkEncode("B28@0:8^{CGPoint=dd}16B24")
101 | checkEncode("{NSEdgeInsets=dddd}16@0:8")
102 | checkEncode("{CGPoint=dd}48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16")
103 | checkEncode("v64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48@52B60")
104 | checkEncode("v40@0:8@16@24^@32")
105 | checkEncode("{CGPoint=dd}32@0:8{CGPoint=dd}16")
106 | checkEncode("v40@0:8^{CGRect={CGPoint=dd}{CGSize=dd}}16^{CGRect={CGPoint=dd}{CGSize=dd}}24Q32")
107 | checkEncode("v32@0:8r^{CGPoint=dd}16@24")
108 | checkEncode("v32@0:8@16d24")
109 | checkEncode("@40@0:8@16{_NSRange=QQ}24")
110 | checkEncode("@24@0:8Q16")
111 | checkEncode("v32@0:8r^{CGSize=dd}16@24")
112 | checkEncode("@24@0:8:16")
113 | checkEncode("#16@0:8")
114 | checkEncode("v48@0:8^{CGRect={CGPoint=dd}{CGSize=dd}}16^{CGRect={CGPoint=dd}{CGSize=dd}}24{CGSize=dd}32")
115 | checkEncode("v60@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24B56")
116 | checkEncode("v32@0:8@16^@24")
117 | checkEncode("Vv16@0:8")
118 | checkEncode("d32@0:8{CGPoint=dd}16")
119 | checkEncode("@32@0:8@16:24")
120 | checkEncode("q16@0:8")
121 | checkEncode("v60@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48@52")
122 | checkEncode("v28@0:8B16@?20")
123 | checkEncode("v48@0:8@16q24@32@40")
124 | checkEncode("v32@0:8{CGPoint=dd}16")
125 | checkEncode("{CGSize=dd}40@0:8{CGSize=dd}16@32")
126 | checkEncode("v24@0:8^{?=ddd{?=b1b31}}16")
127 | checkEncode("v40@0:8@16@24B32B36")
128 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16Q48")
129 | checkEncode("B44@0:8i16@20d28@36")
130 | checkEncode("d24@0:8@16")
131 | checkEncode("@44@0:8Q16@24@32B40")
132 | checkEncode("{CGAffineTransform=dddddd}16@0:8")
133 | checkEncode("B24@0:8^{_NSRange=QQ}16")
134 | checkEncode("@32@0:8{CGPoint=dd}16")
135 | checkEncode("@32@0:8@16@24")
136 | checkEncode("v32@0:8r^^{CGRect}16^q24")
137 | checkEncode("v32@0:8^@16^@24")
138 | checkEncode("{CGSize=dd}40@0:8{CGSize=dd}16f32f36")
139 | checkEncode("v48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16")
140 | checkEncode("@36@0:8{CGPoint=dd}16B32")
141 | checkEncode("@88@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16{CGRect={CGPoint=dd}{CGSize=dd}}48@80")
142 | checkEncode("Q16@0:8")
143 | checkEncode("i24@0:8@16")
144 | checkEncode("{CGSize=dd}24@0:8@16")
145 | checkEncode("@32@0:8^{CGPoint=dd}16@24")
146 | checkEncode("v24@0:8^{_CAView=}16")
147 | checkEncode("v64@0:8^{CGRect={CGPoint=dd}{CGSize=dd}}16@24^^v32^B40^q48q56")
148 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16")
149 | checkEncode("I16@0:8")
150 | checkEncode("v24@0:8@16")
151 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16{CGSize=dd}48")
152 | checkEncode("v24@0:8B16B20")
153 | checkEncode("v56@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24")
154 | checkEncode("B32@0:8{CGPoint=dd}16")
155 | checkEncode("q68@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48^v56B64")
156 | checkEncode("v48@0:8@16@24Q32@?40")
157 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}24@0:8@16")
158 | checkEncode("v72@0:8q16@24{CGRect={CGPoint=dd}{CGSize=dd}}32@64")
159 | checkEncode("f16@0:8")
160 | checkEncode("q24@0:8@16")
161 | checkEncode("{CGPoint=dd}16@0:8")
162 | checkEncode("v24@0:8Q16")
163 | checkEncode("^{CGSRegionObject=}52@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16B48")
164 | checkEncode("v32@0:8@16Q24")
165 | checkEncode("@24@0:8#16")
166 | checkEncode("@40@0:8@16@24@32")
167 | checkEncode("q76@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48^v56B64q68")
168 | checkEncode("@52@0:8@16B24@28{CGPoint=dd}36")
169 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}56@0:8@16{CGRect={CGPoint=dd}{CGSize=dd}}24")
170 | checkEncode("v48@0:8^d16d24d32d40")
171 | checkEncode("v28@0:8f16q20")
172 | checkEncode("v32@0:8^q16q24")
173 | checkEncode("v64@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16{CGPoint=dd}48")
174 | checkEncode("{CGSize=dd}40@0:8{CGSize=dd}16Q32")
175 | checkEncode("B32@0:8@16^@24")
176 | checkEncode("{CGSize=dd}32@0:8{CGSize=dd}16")
177 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}56@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16@48")
178 | checkEncode("v24@0:8q16")
179 | checkEncode("{CGPoint=dd}40@0:8{CGPoint=dd}16@32")
180 | checkEncode("@24@0:8@16")
181 | checkEncode("q20@0:8B16")
182 | checkEncode("@28@0:8@16B24")
183 | checkEncode("v24@0:8@?16")
184 | checkEncode("{?=dd}32@0:8{CGSize=dd}16")
185 | checkEncode("B48@0:8^d16@24@32^@40")
186 |
187 | // Class Methods
188 | checkEncode("v24@0:8@16")
189 | checkEncode("q16@0:8")
190 | checkEncode("v24@0:8@?16")
191 | checkEncode("v16@0:8")
192 | checkEncode("@24@0:8@16")
193 | checkEncode("@16@0:8")
194 | checkEncode("i16@0:8")
195 | checkEncode("@32@0:8Q16@24")
196 | checkEncode("Q16@0:8")
197 | checkEncode("B16@0:8")
198 | checkEncode("v20@0:8B16")
199 | checkEncode("#16@0:8")
200 | }
201 | }
202 |
203 | extension ObjCMethodTypeDecodeTests {
204 | func checkEncode(_ encoded: String) {
205 | let decoded = decoded(encoded)
206 | XCTAssertEqual(decoded?.encoded(), encoded)
207 | XCTAssertEqual(decoded?.selectorInfo.type.decoded(), "SEL")
208 | }
209 | }
210 |
211 | extension ObjCMethodTypeDecodeTests {
212 | func decoded(_ type: String) -> ObjCMethodType? {
213 | ObjCMethodTypeDecoder.decode(type)
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/Tests/ObjCTypeDecodeKitTests/ObjCTypeDecodeKitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCTypeDecodeKitTests.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/19
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import ObjCTypeDecodeKit
11 |
12 | final class ObjCTypeDecodeKitTests: XCTestCase {
13 | func testSimpleTypes() {
14 | XCTAssertEqual(decoded("@"), "id")
15 | XCTAssertEqual(decoded("#"), "Class")
16 | XCTAssertEqual(decoded(":"), "SEL")
17 |
18 | XCTAssertEqual(decoded("c"), "char")
19 | XCTAssertEqual(decoded("C"), "unsigned char")
20 |
21 | XCTAssertEqual(decoded("s"), "short")
22 | XCTAssertEqual(decoded("S"), "unsigned short")
23 |
24 | XCTAssertEqual(decoded("i"), "int")
25 | XCTAssertEqual(decoded("I"), "unsigned int")
26 |
27 | XCTAssertEqual(decoded("l"), "long")
28 | XCTAssertEqual(decoded("L"), "unsigned long")
29 |
30 | XCTAssertEqual(decoded("q"), "long long")
31 | XCTAssertEqual(decoded("Q"), "unsigned long long")
32 |
33 | XCTAssertEqual(decoded("t"), "__int128_t")
34 | XCTAssertEqual(decoded("T"), "__uint128_t")
35 |
36 | XCTAssertEqual(decoded("f"), "float")
37 |
38 | XCTAssertEqual(decoded("d"), "double")
39 | XCTAssertEqual(decoded("D"), "long double")
40 |
41 | XCTAssertEqual(decoded("B"), "BOOL")
42 | XCTAssertEqual(decoded("v"), "void")
43 | XCTAssertEqual(decoded("?"), "unknown")
44 | XCTAssertEqual(decoded("*"), "char *")
45 |
46 | XCTAssertEqual(decoded("%"), "atom") // FIXME: ?????
47 | }
48 |
49 | func testNamedId() {
50 | XCTAssertEqual(
51 | decoded(#"@"NSLayoutManager""#),
52 | "NSLayoutManager *"
53 | )
54 | XCTAssertEqual(
55 | decoded(#"@?"#),
56 | "id /* block */"
57 | )
58 | }
59 |
60 | func testPointers() {
61 | XCTAssertEqual(decoded("^i"), "int *")
62 | XCTAssertEqual(decoded("^v"), "void *")
63 | }
64 |
65 | func testArray() {
66 | XCTAssertEqual(decoded("[i]"), "int[]")
67 | XCTAssertEqual(decoded("[128i]"), "int[128]")
68 | XCTAssertEqual(decoded("[128^i]"), "int *[128]")
69 | }
70 |
71 | func testUnion() {
72 | XCTAssertEqual(
73 | decoded("(?=iQ)"),
74 | """
75 | union {
76 | int x0;
77 | unsigned long long x1;
78 | }
79 | """
80 | )
81 | XCTAssertEqual(
82 | decoded("(?=iQ(?=*B))"),
83 | """
84 | union {
85 | int x0;
86 | unsigned long long x1;
87 | union {
88 | char * x0;
89 | BOOL x1;
90 | } x2;
91 | }
92 | """
93 | )
94 | }
95 |
96 | func testStruct() {
97 | XCTAssertEqual(
98 | decoded("{CGRect={CGPoint=dd}{CGSize=dd}}"),
99 | """
100 | struct CGRect {
101 | struct CGPoint {
102 | double x0;
103 | double x1;
104 | } x0;
105 | struct CGSize {
106 | double x0;
107 | double x1;
108 | } x1;
109 | }
110 | """
111 | )
112 | XCTAssertEqual(
113 | decoded(#"{CGSize="width"d"height"d}"#),
114 | """
115 | struct CGSize {
116 | double width;
117 | double height;
118 | }
119 | """
120 | )
121 | XCTAssertEqual(
122 | decoded(#"{CGRect="origin"{CGPoint="x"d"y"d}"size"{CGSize="width"d"height"d}}"#),
123 | """
124 | struct CGRect {
125 | struct CGPoint {
126 | double x;
127 | double y;
128 | } origin;
129 | struct CGSize {
130 | double width;
131 | double height;
132 | } size;
133 | }
134 | """
135 | )
136 | XCTAssertEqual(
137 | decoded(#"{_tvFlags="horizontallyResizable"b1"verticallyResizable"b1"viewOwnsTextStorage"b1"displayWithoutLayout"b1"settingMarkedRange"b1"containerOriginInvalid"b1"registeredForDragging"b1"superviewIsClipView"b1"forceRulerUpdate"b1"typingText"b1"wasPostingFrameNotifications"b1"wasRotatedOrScaledFromBase"b1"settingNeedsDisplay"b1"mouseInside"b1"verticalLayout"b2"diagonallyRotatedOrScaled"b1"hasScaledBacking"b1"shouldCloseQL"b1"dragUpdateRequstOwner"b1"genericDragRemoveSource"b1"isAttributedPlaceholder"b1"isDDAction"b1"showingFindIndicator"b1"isDrawingLayer"b1"touchBarInstantiated"b1"calculatingContainerOrigin"b1"doesOverrideDrawInsertionPointInRect"b1"darkEffectiveAppearance"b1"isPresentingReveal"b1"isDrawingFindIndicatorContent"b1"isAutoscrollingForTextLayoutManager"b1"_downgradeState"b2"isWatchingUnspecifiedClipView"b1}"#),
138 | """
139 | struct _tvFlags {
140 | int horizontallyResizable : 1;
141 | int verticallyResizable : 1;
142 | int viewOwnsTextStorage : 1;
143 | int displayWithoutLayout : 1;
144 | int settingMarkedRange : 1;
145 | int containerOriginInvalid : 1;
146 | int registeredForDragging : 1;
147 | int superviewIsClipView : 1;
148 | int forceRulerUpdate : 1;
149 | int typingText : 1;
150 | int wasPostingFrameNotifications : 1;
151 | int wasRotatedOrScaledFromBase : 1;
152 | int settingNeedsDisplay : 1;
153 | int mouseInside : 1;
154 | int verticalLayout : 2;
155 | int diagonallyRotatedOrScaled : 1;
156 | int hasScaledBacking : 1;
157 | int shouldCloseQL : 1;
158 | int dragUpdateRequstOwner : 1;
159 | int genericDragRemoveSource : 1;
160 | int isAttributedPlaceholder : 1;
161 | int isDDAction : 1;
162 | int showingFindIndicator : 1;
163 | int isDrawingLayer : 1;
164 | int touchBarInstantiated : 1;
165 | int calculatingContainerOrigin : 1;
166 | int doesOverrideDrawInsertionPointInRect : 1;
167 | int darkEffectiveAppearance : 1;
168 | int isPresentingReveal : 1;
169 | int isDrawingFindIndicatorContent : 1;
170 | int isAutoscrollingForTextLayoutManager : 1;
171 | int _downgradeState : 2;
172 | int isWatchingUnspecifiedClipView : 1;
173 | }
174 | """
175 | )
176 | }
177 |
178 | func testAtomic() {
179 | XCTAssertEqual(decoded("Ai"), "_Atomic int")
180 | XCTAssertEqual(decoded("A*"), "_Atomic char *")
181 | XCTAssertEqual(
182 | decoded("^AQ"),
183 | "_Atomic unsigned long long *"
184 | )
185 | XCTAssertEqual(decoded("^A{CGPoint}"), "_Atomic struct CGPoint *")
186 | }
187 |
188 | func testComplex() {
189 | XCTAssertEqual(decoded("ji"), "_Complex int")
190 | XCTAssertEqual(
191 | decoded("^jQ"),
192 | "_Complex unsigned long long *"
193 | )
194 | }
195 |
196 | func test() {
197 | XCTAssertEqual(
198 | decoded("(?=iI{?=i{CGRect={CGPoint=dd}{CGSize=dd}}})"),
199 | """
200 | union {
201 | int x0;
202 | unsigned int x1;
203 | struct {
204 | int x0;
205 | struct CGRect {
206 | struct CGPoint {
207 | double x0;
208 | double x1;
209 | } x0;
210 | struct CGSize {
211 | double x0;
212 | double x1;
213 | } x1;
214 | } x1;
215 | } x2;
216 | }
217 | """
218 | )
219 | }
220 | }
221 |
222 | extension ObjCTypeDecodeKitTests {
223 | @_disfavoredOverload
224 | func decoded(_ type: String) -> String? {
225 | ObjCTypeDecoder.decode(type)?.decoded()
226 | }
227 |
228 | func decoded(_ type: String) -> ObjCType? {
229 | ObjCTypeDecoder.decode(type)
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/Tests/ObjCTypeDecodeKitTests/ObjCTypeEncodeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjCTypeEncodeTests.swift
3 | //
4 | //
5 | // Created by p-x9 on 2024/06/21
6 | //
7 | //
8 |
9 | import XCTest
10 | @testable import ObjCTypeDecodeKit
11 |
12 | final class ObjCTypeEncodeTests: XCTestCase {
13 | func testSimpleTypes() {
14 | checkEncode("@")
15 | checkEncode("#")
16 | checkEncode(":")
17 |
18 | checkEncode("c")
19 | checkEncode("C")
20 |
21 | checkEncode("s")
22 | checkEncode("S")
23 |
24 | checkEncode("i")
25 | checkEncode("I")
26 |
27 | checkEncode("l")
28 | checkEncode("L")
29 |
30 | checkEncode("q")
31 | checkEncode("Q")
32 |
33 | checkEncode("t")
34 | checkEncode("T")
35 |
36 | checkEncode("f")
37 |
38 | checkEncode("d")
39 | checkEncode("D")
40 |
41 | checkEncode("B")
42 | checkEncode("v")
43 | checkEncode("?")
44 | checkEncode("*")
45 |
46 | checkEncode("%") // FIXME: ?????
47 | }
48 |
49 | func testNamedId() {
50 | checkEncode(#"@"NSLayoutManager""#)
51 | checkEncode(#"@?"#)
52 | }
53 |
54 | func testPointers() {
55 | checkEncode("^i")
56 | checkEncode("^v")
57 | }
58 |
59 | func testArray() {
60 | checkEncode("[i]")
61 | checkEncode("[128i]")
62 | checkEncode("[128^i]")
63 | }
64 |
65 | func testUnion() {
66 | checkEncode("(?=iQ)")
67 | checkEncode("(?=iQ(?=*B))")
68 | }
69 |
70 | func testStruct() {
71 | checkEncode("{CGRect={CGPoint=dd}{CGSize=dd}}")
72 | checkEncode(#"{CGSize="width"d"height"d}"#)
73 | checkEncode(#"{CGRect="origin"{CGPoint="x"d"y"d}"size"{CGSize="width"d"height"d}}"#)
74 | checkEncode(#"{_tvFlags="horizontallyResizable"b1"verticallyResizable"b1"viewOwnsTextStorage"b1"displayWithoutLayout"b1"settingMarkedRange"b1"containerOriginInvalid"b1"registeredForDragging"b1"superviewIsClipView"b1"forceRulerUpdate"b1"typingText"b1"wasPostingFrameNotifications"b1"wasRotatedOrScaledFromBase"b1"settingNeedsDisplay"b1"mouseInside"b1"verticalLayout"b2"diagonallyRotatedOrScaled"b1"hasScaledBacking"b1"shouldCloseQL"b1"dragUpdateRequstOwner"b1"genericDragRemoveSource"b1"isAttributedPlaceholder"b1"isDDAction"b1"showingFindIndicator"b1"isDrawingLayer"b1"touchBarInstantiated"b1"calculatingContainerOrigin"b1"doesOverrideDrawInsertionPointInRect"b1"darkEffectiveAppearance"b1"isPresentingReveal"b1"isDrawingFindIndicatorContent"b1"isAutoscrollingForTextLayoutManager"b1"_downgradeState"b2"isWatchingUnspecifiedClipView"b1}"#)
75 | }
76 |
77 | func testBlock() {
78 | checkEncode("@?")
79 | checkEncode("@?")
80 | checkEncode(#"@?"#)
81 | checkEncode(#"@?"#)
82 | checkEncode(#"@?">"#)
83 | }
84 |
85 | func testAtomic() {
86 | checkEncode("Ai")
87 | checkEncode("A*")
88 | checkEncode("^AQ")
89 | checkEncode("^A{CGPoint}")
90 | }
91 |
92 | func testComplex() {
93 | checkEncode("ji")
94 | checkEncode("^jQ")
95 | }
96 |
97 | func test() {
98 | checkEncode("(?=iI{?=i{CGRect={CGPoint=dd}{CGSize=dd}}})")
99 | }
100 | }
101 |
102 | extension ObjCTypeEncodeTests {
103 | func checkEncode(_ encoded: String) {
104 | XCTAssertEqual(decoded(encoded)?.encoded(), encoded)
105 | }
106 | }
107 |
108 | extension ObjCTypeEncodeTests {
109 | @_disfavoredOverload
110 | func decoded(_ type: String) -> String? {
111 | ObjCTypeDecoder.decode(type)?.decoded()
112 | }
113 |
114 | func decoded(_ type: String) -> ObjCType? {
115 | ObjCTypeDecoder.decode(type)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------