Does anybody have an Xcode script for generating @property and @synthsize directives for instance variables in a class?
This is the one I came up with based on one I found a long time ago, rewritten in Python and with the improvements that it can generate multiple properties at once, among other things.
It will generate properties for all selected instance variable using (copy) as the attribute.
There are still some edge cases with multiple @interfaces or @implementations in a file, as well as some with unusual identifiers or asterisk placement (as in *const), but it should cover most typical coding styles. Feel free to edit/post modifications if you fix any of these cases.
#!/usr/bin/python
# Takes a header file with one or more instance variables selected
# and creates properties and synthesize directives for the selected properties.
# Accepts google-style instance variables with a tailing underscore and
# creates an appropriately named property without underscore.
# Entire Document
# Home Directory
# Discard Output
# Display in Alert
import os
import re
import subprocess
# AppleScripts for altering contents of files via Xcode
setFileContentsScript = """\
on run argv
set fileAlias to POSIX file (item 1 of argv)
set newDocText to (item 2 of argv)
tell application "Xcode"
set doc to open fileAlias
set text of doc to newDocText
end tell
end run \
"""
getFileContentsScript = """\
on run argv
set fileAlias to POSIX file (item 1 of argv)
tell application "Xcode"
set doc to open fileAlias
set docText to text of doc
end tell
return docText
end run \
"""
# Get variables from Xcode
headerFileText = """%%%{PBXAllText}%%%"""
selectionStartIndex = %%%{PBXSelectionStart}%%%
selectionEndIndex = %%%{PBXSelectionEnd}%%%
selectedText = headerFileText[selectionStartIndex:selectionEndIndex]
headerFilePath = """%%%{PBXFilePath}%%%"""
# Look for an implementation file with .m or .mm extension
implementationFilePath = headerFilePath[:-1] + "m"
if not os.path.exists(implementationFilePath):
implementationFilePath += "m"
instanceVariablesRegex = re.compile(
"""^\s*((?:(?:\w+)\s+)*(?:(?:\w+)))""" + # Identifier(s)
"""([*]?)\\s*""" + # An optional asterisk
"""(\\w+?)(_?);""", # The variable name
re.M)
# Now for each instance variable in the selected section
properties = ""
synthesizes = ""
for lineMatch in instanceVariablesRegex.findall(selectedText):
types = " ".join(lineMatch[0].split()) # Clean up consequtive whitespace
asterisk = lineMatch[1]
variableName = lineMatch[2]
trailingUnderscore = lineMatch[3]
pointerPropertyAttributes = "(copy) " # Attributes if variable is pointer
if not asterisk:
pointerPropertyAttributes = ""
newProperty = "@property %s%s %s%s;\n" % (pointerPropertyAttributes,
types,
asterisk,
variableName)
# If there's a trailing underscore, we need to let the synthesize
# know which backing variable it's using
newSynthesize = "@synthesize %s%s;\n" % (variableName,
trailingUnderscore and
" = %s_" % variableName)
properties += newProperty
synthesizes += newSynthesize
# Check to make sure at least 1 properties was found to generate
if not properties:
os.sys.stderr.writelines("No properties found to generate")
exit(-1)
# We want to insert the new properties either immediately after the last
# existing property or at the end of the instance variable section
findLastPropertyRegex = re.compile("^@interface.*?{.*?}.*?\\n" +
"(?:.*^\\s*@property.*?\\n)?", re.M | re.S)
headerInsertIndex = findLastPropertyRegex.search(headerFileText).end()
# Add new lines on either side if this is the only property in the file
addedNewLine = "\n"
if re.search("^\s*@property", headerFileText, re.M):
# Not the only property, don't add
addedNewLine = ""
newHeaderFileText = "%s%s%s%s" % (headerFileText[:headerInsertIndex],
addedNewLine,
properties,
headerFileText[headerInsertIndex:])
subprocess.call(["osascript",
"-e",
setFileContentsScript,
headerFilePath,
newHeaderFileText])
if not os.path.exists(implementationFilePath):
os.sys.stdout.writelines("No implementation file found")
exit(0)
implementationFileText = subprocess.Popen(
["osascript",
"-e",
getFileContentsScript,
implementationFilePath],
stdout=subprocess.PIPE).communicate()[0]
# We want to insert the synthesizes either immediately after the last existing
# @synthesize or after the @implementation directive
lastSynthesizeRegex = re.compile("^\\s*@implementation.*?\\n" +
"(?:.*^\\s*@synthesize.*?\\n)?", re.M | re.S)
implementationInsertIndex = \
lastSynthesizeRegex.search(implementationFileText).end()
# Add new lines on either side if this is the only synthesize in the file
addedNewLine = "\n"
if re.search("^\s*@synthesize", implementationFileText, re.M):
# Not the only synthesize, don't add
addedNewLine = ""
newImplementationFileText = "%s%s%s%s" % \
(implementationFileText[:implementationInsertIndex],
addedNewLine,
synthesizes,
implementationFileText[implementationInsertIndex:])
subprocess.call(["osascript",
"-e",
setFileContentsScript,
implementationFilePath,
newImplementationFileText])
# Switch Xcode back to header file
subprocess.Popen(["osascript",
"-e",
getFileContentsScript,
headerFilePath],
stdout=subprocess.PIPE).communicate()
Whoa, there's a whole lot of crazy scripting goin' on here.
As of Xcode 4.4 (maybe before)... Your IVAR
s will be auto-synthesized.. For example..
@property (assign) BOOL automatically;
@property (strong) NSArray *believeDat;
can be "accessored" via
self.automatically = YES;
and edit the instance variable directly via the auto-generated-with-leading-underscore like..
_believeDat = @["thank you, jesus", @"mary poopins"];
no @synthesize
necessary.
As for quick and easy entering of such @property
... drag the following, one at a time, into the "Code Snippet" library.. and you can assign keyboard shortcuts to insert these jump-off points for entering the properties more quickly. I use rrr for objects and aaa for primitives.. but thats just me..
@property (nonatomic, assign) <#type#> <#name#>;
@property (nonatomic, retain) <#type#> *<#name#>;
last but not least, and some may call me crazy.. but I throw the following macros into my .pch
to further expedite, clarify, and bring welcome brevity to the process.. all common macro disclaimers apply...
#define RONLY readonly
#define RDWRT readwrite
#define NATOM nonatomic
#define STRNG strong
#define ASS assign
#define CP copy
#define SET setter
#define GET getter
along with similarly structured #define
s for Apple classes ( #define NSA NSArray \ #define NSS NSString
), this makes things easier to read, and faster to enter (for me), looking like...
@property (NATOM, STRNG) NSA* fonts;
@property (NATOM, STRNG) NSS* cachedPath;