Last active
December 8, 2021 22:23
-
-
Save jpsim/1b86d116808cb4e9bc30 to your computer and use it in GitHub Desktop.
Swift and Objective-C Class Parsing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////// | |
// | |
// Swift and Objective-C Class Parsing | |
// | |
//////////////////////////////////////////////// | |
import Foundation | |
// Class parsing | |
enum ClassType { | |
case Swift, ObjectiveC | |
func toString() -> String { | |
switch self { | |
case .Swift: | |
return "Swift" | |
case .ObjectiveC: | |
return "Objective-C" | |
} | |
} | |
} | |
struct ParsedClass { | |
let type: ClassType | |
let name: String | |
let mangledName: String? | |
let moduleName: String? | |
} | |
extension String { | |
subscript (r: Range<Int>) -> String { | |
get { | |
let startIndex = advance(self.startIndex, r.startIndex) | |
let endIndex = advance(startIndex, r.endIndex) | |
return self[Range(start: startIndex, end: endIndex)] | |
} | |
} | |
} | |
func parseClass(aClass: AnyClass) -> ParsedClass { | |
// Swift mangling details found here: http://www.eswick.com/2014/06/inside-swift | |
let originalName = NSStringFromClass(aClass) | |
if !originalName.hasPrefix("_T") { | |
// Not a Swift symbol | |
return ParsedClass(type: ClassType.ObjectiveC, | |
name: originalName, | |
mangledName: nil, | |
moduleName: nil) | |
} | |
let originalNameLength = originalName.utf16count | |
var cursor = 4 | |
var substring = originalName[cursor..originalNameLength-cursor] | |
// Module | |
let moduleLength = substring.bridgeToObjectiveC().integerValue | |
let moduleLengthLength = "\(moduleLength)".utf16count | |
let moduleName = substring[moduleLengthLength..moduleLength] | |
// Update cursor and substring | |
cursor += moduleLengthLength + moduleName.utf16count | |
substring = originalName[cursor..originalNameLength-cursor] | |
// Class name | |
let classLength = substring.bridgeToObjectiveC().integerValue | |
let classLengthLength = "\(classLength)".utf16count | |
let className = substring[classLengthLength..classLength] | |
return ParsedClass(type: ClassType.Swift, | |
name: className, | |
mangledName: originalName, | |
moduleName: moduleName) | |
} | |
// Playground start | |
class MySwiftClass { | |
// Empty class | |
} | |
var parsedClass = parseClass(MySwiftClass.self) | |
parsedClass.type.toString() | |
parsedClass.name | |
parsedClass.mangledName! | |
parsedClass.moduleName! | |
parsedClass = parseClass(NSObject.self) | |
parsedClass.type.toString() | |
parsedClass.name | |
parsedClass.mangledName! | |
parsedClass.moduleName! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func demangle(className: String) -> String { | |
let tmpClassName = ("__TMP" + className).bridgeToObjectiveC().UTF8String | |
let propertyName: CString = "tmpProperty" | |
let tmpClass: AnyClass! = objc_allocateClassPair(NSObject.self, tmpClassName, 0) | |
objc_registerClassPair(tmpClass) | |
let propertyType = "@\"\(className)\"".bridgeToObjectiveC().UTF8String | |
let attr = objc_property_attribute_t(name: "T", value: propertyType) | |
class_addProperty(tmpClass, propertyName, [attr], 1) | |
let property = class_getProperty(tmpClass, propertyName) | |
var attCount: CUnsignedInt = 0; | |
let atts = property_copyAttributeList(property, &attCount) | |
objc_disposeClassPair(tmpClass) | |
return "\(atts[0].value)" | |
} |
This is very nice 👍
Is there any licensing to this code?
I borrowed your v1 code and wrote my debugging tool here, just working like C macro.
https://github.com/inamiy/DebugLog
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I just added a second version, which is probably just as unstable as (if not more than) the first.
It relies on an implementation quirk where the type encoding of a Swift property in a Swift class shows the demangled name.
I still recommend using v1 over v2, but wanted to share my findings nonetheless.