Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Swift and Objective-C Class Parsing
////////////////////////////////////////////////
//
// 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!
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)"
}
@jpsim

This comment has been minimized.

Copy link
Owner Author

jpsim commented Jun 25, 2014

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.

@inamiy

This comment has been minimized.

Copy link

inamiy commented Jun 26, 2014

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
You can’t perform that action at this time.