Скрипт Xcode для генерации / синтеза свойств - PullRequest
9 голосов
/ 20 июля 2009

Есть ли у кого-нибудь скрипт Xcode для генерации директив @property и @synthsize для переменных экземпляра в классе?

Ответы [ 8 ]

7 голосов
/ 01 января 2010

Я использую Accessorizer, который делает это и многое другое.

http://www.kevincallahan.org/software/accessorizer.html

очень недорогой и мощный.

2 голосов
/ 18 сентября 2010

Это скрипт на python для Xcode 3.2.4, который генерирует; свойства интерфейса, синтез реализации и сделки. Чтобы установить, скопируйте этот скрипт, перейдите в меню скриптов XCode (от второго до последнего) «Редактировать пользовательские сценарии ...» Добавьте его в поле «Код», создайте новое имя сценария и вставьте приведенный ниже сценарий python.

Чтобы использовать, просто выберите переменные под @interface, а затем вызовите этот скрипт. Затем он добавит все свойства @ в реализации и все @synthesize и dealloc's. Он не добавит IBOutlet ни к одной из ваших меток или кнопок, поскольку не знает этого, но это легко добавить вручную.

Отступ сценария ниже является критическим, поэтому не меняйте его.

#!/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.

# Xcode script options should be as follows:
# 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*((?:(?:\\b\w+\\b)\s+)*(?:(?:\\b\\w+\\b)))\\s*""" + # Identifier(s)
"""([*]?)\\s*""" + # An optional asterisk
"""(\\b\\w+?)(_?\\b);""", # The variable name
re.M)

# Now for each instance variable in the selected section
properties = ""
synthesizes = ""
deallocs = ""

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 = "(nonatomic, retain) " # Attributes if variable is pointer
    if not asterisk:
        pointerPropertyAttributes = "(nonatomic, assign) "

    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)
    # only do the objects
    if asterisk:
        newDealloc = "    [%s%s release];\n" % (variableName,
                    trailingUnderscore and
                                 " = %s_" % variableName)
    properties += newProperty
    synthesizes += newSynthesize
    # only add if it's an object
    if asterisk:
        deallocs += newDealloc


# 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 synthsize 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])


implementationFileText = subprocess.Popen(
["osascript",
"-e",
getFileContentsScript,
implementationFilePath],
stdout=subprocess.PIPE).communicate()[0]

# We want to insert the deallocs either immediately after the last existing
# [* release] or after the [super dealloc]
lastDeallocRegex = re.compile("^\\s+\[super dealloc\];?\\n" +
                      "(?:.*^\\s+\[\w release\];?\\n)?", re.M | re.S)

deallocInsertIndex = \
lastDeallocRegex.search(implementationFileText).end() 

addedNewDeallocLine = "\n"
if re.search("^\s*\[\w release\];?", implementationFileText, re.M):
# Not the only dealloc, don't add
addedNewDeallocLine = ""


newImplementationFileText = "%s%s%s%s" % \
         (implementationFileText[:deallocInsertIndex],
          addedNewDeallocLine,
          deallocs,
          implementationFileText[deallocInsertIndex:])

subprocess.call(["osascript",
              "-e",
              setFileContentsScript,
              implementationFilePath,
              newImplementationFileText])      

# Switch Xcode back to header file
subprocess.Popen(["osascript",
        "-e",
        getFileContentsScript,
        headerFilePath],
       stdout=subprocess.PIPE).communicate()
2 голосов
/ 20 июля 2009

Это тот, который я придумал, основываясь на одном, который я нашел давным-давно, переписанном в Python, и с улучшениями, которые могут генерировать несколько свойств одновременно, среди прочего.

Он сгенерирует свойства для всех выбранных переменных экземпляра, используя (копировать) в качестве атрибута.

Есть еще некоторые крайние случаи с несколькими @interfaces или @implementations в файле, а также некоторые с необычными идентификаторами или расположением звездочки (как в * const), но это должно охватывать большинство типичных стилей кодирования. Не стесняйтесь редактировать / публиковать изменения, если вы исправите любой из этих случаев.

#!/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()
1 голос
/ 27 октября 2012

Ух, тут много сумасшедших сценариев.

Начиная с Xcode 4.4 (может быть, раньше) ... Ваши IVAR s будут автоматически синтезированы .. Например ..

@property (assign) BOOL automatically;
@property (strong) NSArray *believeDat;

можно "получить доступ" через

self.automatically = YES;

и редактируйте переменную экземпляра напрямую через автоматически сгенерированный с подчеркиванием, например ..

_believeDat = @["thank you, jesus", @"mary poopins"];

нет @synthesize необходимо.

Что касается быстрого и простого ввода таких @property ... перетащите следующее, по одному, в библиотеку «Фрагмент кода» ... и вы можете назначить сочетания клавиш, чтобы вставить эти точки перехода для ввода свойства быстрее. Я использую ррр для объектов и ааа для примитивов .. но это только я ..

@property (nonatomic, assign) <#type#> <#name#>;

@property (nonatomic, retain) <#type#> *<#name#>;

последнее, но не менее важное, , и некоторые могут назвать меня сумасшедшим .. но я добавляю следующие макросы в свой .pch, чтобы ускорить, прояснить и внести желаемую краткость в процесс ... все общее применяются ограничения по макросам ...

#define RONLY readonly
#define RDWRT readwrite
#define NATOM nonatomic
#define STRNG strong
#define ASS assign
#define CP copy
#define SET setter
#define GET getter

вместе с аналогично структурированными #define с для классов Apple (#define NSA NSArray \ #define NSS NSString), это упрощает чтение и ускоряет ввод (для меня), как ...

@property (NATOM, STRNG) NSA* fonts;
@property (NATOM, STRNG) NSS* cachedPath;
1 голос
/ 23 августа 2010
1 голос
/ 03 февраля 2010

Вот пользовательский скрипт, который я сейчас использую - он работает с одной переменной экземпляра за раз. Он пытается использовать правильный механизм сохранения (простые типы не сохраняются), а также создает оператор @synthesize в файле реализации - в настоящее время он еще не создает для вас операторы dealloc.

#! /usr/bin/perl -w

#Input: Selection
#Directory: Selection
#Output: Display in Alert
#Errors: Display in Alert

use strict;

# Get the header file contents from Xcode user scripts
my $headerFileContents = <<'HEADERFILECONTENTS';
%%%{PBXAllText}%%%
HEADERFILECONTENTS

# Get the indices of the selection from Xcode user scripts
my $selectionStartIndex = %%%{PBXSelectionStart}%%%;
my $selectionEndIndex = %%%{PBXSelectionEnd}%%%;

# Get path of the header file
my $implementationFilePath = "%%%{PBXFilePath}%%%";
my $headerFilePath = $implementationFilePath;

# Look for an implemenation file with a ".m" or ".mm" extension
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
    $implementationFilePath =~ s/.m$/.mm/;
}

# Handle subroutine to trime whitespace off both ends of a string
sub trim
{
    my $string = shift;
    $string =~ s/^\s*(.*?)\s*$/$1/;
    return $string;
}


# Get the selection out of the header file
my $selectedText =  substr $headerFileContents, $selectionStartIndex, ($selectionEndIndex - $selectionStartIndex);

#my $otherText = substr $headerFileContents, $selectionStartIndex;
#my $pulledText = "";
#if ( length($otherText) && $otherText =~ /.*$(^.*;).*/ )
#{
#    $pulledText = $1;
#}
#
#
#print $pulledText;


$selectedText = trim $selectedText;


my $type = "";
my $asterisk = "";
my $name = "";
my $behavior = "";
my $iboutlet = "";

# Test that the selection is:
#  At series of identifiers (the type name and access specifiers)
#  Possibly an asterisk
#  Another identifier (the variable name)
#  A semi-colon
if (length($selectedText) && ($selectedText =~ /([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*)/))
{
    $type = $1;
    $type = trim $type;
    $asterisk = $2;
    $asterisk = trim $asterisk;
    $name = $3;
    $behavior = "";
    if (defined($asterisk) && length($asterisk) == 1)
    {
        $behavior = "(nonatomic, retain) ";
    }
    else
    {
        $behavior = "(nonatomic) ";
        $asterisk = "";
    }
}
else
{
    print "Bailing, error in Regex";
    exit 1;
}

# special case, see if we need to keep around an IBOUTLET declaration.
if ( length($selectedText) && ($selectedText =~ /IBOutlet/) )
{
   $iboutlet = "IBOutlet ";
}

# Find the closing brace (end of the class variables section)
my $remainderOfHeader = substr $headerFileContents, $selectionEndIndex;
my $indexAfterClosingBrace = $selectionEndIndex + index($remainderOfHeader, "\n}\n") + 3;
if ($indexAfterClosingBrace == -1)
{
    exit 1;
}

# Determine if we need to add a newline in front of the property declaration
my $leadingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 1) eq "\n")
{
    $indexAfterClosingBrace += 1;
    $leadingNewline = "";
}

# Determine if we need to add a newline after the property declaration
my $trailingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 9) eq "\@property")
{
    $trailingNewline = "";
}

# Create and insert the proper declaration
my $propertyDeclaration = $leadingNewline . "\@property " . $behavior . $iboutlet . $type . " " . $asterisk . $name . ";\n" . $trailingNewline; 
substr($headerFileContents, $indexAfterClosingBrace, 0) = $propertyDeclaration;

my $replaceFileContentsScript = <<'REPLACEFILESCRIPT';
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
REPLACEFILESCRIPT

# Use Applescript to replace the contents of the header file
# (I could have used the "Output" of the Xcode user script instead)
system 'osascript', '-e', $replaceFileContentsScript, $headerFilePath, $headerFileContents;

# Stop now if the implementation file can't be found
if (!(-e $implementationFilePath))
{
    exit 1;
}

my $getFileContentsScript = <<'GETFILESCRIPT';
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
GETFILESCRIPT

# Get the contents of the implmentation file
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; <SCRIPTFILE>};
close(SCRIPTFILE);

# Look for the class implementation statement
if (length($implementationFileContents) && ($implementationFileContents =~ /(\@implementation [_A-Za-z][_A-Za-z0-9]*\n)/))
{
    my $matchString = $1;
    my $indexAfterMatch = index($implementationFileContents, $matchString) + length($matchString);

    # Determine if we want a newline before the synthesize statement
    $leadingNewline = "\n";
    if (substr($implementationFileContents, $indexAfterMatch, 1) eq "\n")
    {
        $indexAfterMatch += 1;
        $leadingNewline = "";
    }

    # Determine if we want a newline after the synthesize statement
    $trailingNewline = "\n";
    if (substr($implementationFileContents, $indexAfterMatch, 11) eq "\@synthesize")
    {
        $trailingNewline = "";
    }

    # Create and insert the synthesize statement 
    my $synthesizeStatement = $leadingNewline . "\@synthesize " . $name . ";\n" . $trailingNewline;
    substr($implementationFileContents, $indexAfterMatch, 0) = $synthesizeStatement;

    # Use Applescript to replace the contents of the implementation file in Xcode
    system 'osascript', '-e', $replaceFileContentsScript, $implementationFilePath, $implementationFileContents;
}

exit 0;
0 голосов
/ 09 февраля 2010

Accessorizer http://www.kevincallahan.org/software/accessorizer.html делает это и многое другое. Он также обрабатывает пользовательские префиксы и постфиксы (суффиксы). Если вы хотите подчеркнуть Google, вы его получили. Если вы хотите изменить его, измените его на лету - не нужно редактировать скрипты. Кроме того, есть таблица значений по умолчанию, в которой вы можете определить спецификаторы свойств по умолчанию на основе типа передаваемого ивара (копировать, сохранять, только для чтения, назначать и т. Д.). Он выполняет обнаружение IBOutlet и автоматически вставляет ключевое слово IBOutlet, обнуляет ваши представления для -viewDidUnload, выполняет несколько стилей dealloc. Он также записывает все эти волосатые средства доступа для коллекций (NSMutableArray и NSSet). Он выполняет архивирование ключей, различные подходы к блокировке, он может сортировать ваши свойства и синтезировать блоки, писать код KVO, код Singleton, преобразовывать в селектор, генерировать теги HeaderDoc, NSLog () и многое другое ... Он также имеет гибкую вкладку стилей для ставить фигурные скобки на новую строку или нет, для пробелов, для имен пользовательских аргументов и т. д. Большинство вещей обрабатываются с помощью Сервисов, поэтому вы просто выбираете блок ivar, нажимаете одну или две клавиши, и все готово. Если вы сворачиваете Accessorizer в док-станцию, его интерфейс не выходит вперед, что позволяет вам оставаться сосредоточенным на Xcode или любом другом редакторе, поддерживающем Сервисы. Конечно, Accessorizer также записывает явные методы доступа (как в Objective-C 1.0) и позволяет вам переопределять свойства - все с помощью простого переключения переключателя. Вы даже можете настроить переопределение в зависимости от переданного типа. Посмотрите видео, чтобы увидеть его в действии.

0 голосов
/ 06 августа 2009

Вот один, который я написал вчера, чтобы выполнить директивы @property, прежде чем через несколько часов столкнуться с этим вопросом. Это простой текстовый фильтр, и было бы тривиально расширить его до директив @synthesize (добавьте соответствующее предложение when к оператору case и внесите соответствующие дополнения в условие when block_end), и не так много работы по его расширению для обработки нескольких вхождений реализации @ interface / @ в одном файле (путем отслеживания их имен - это можно сделать с помощью перехватов regexp, как и все остальное в скрипте):

#! /usr/bin/ruby

# -------------- Basic Definitions -----------------------------

doc = "%%%{PBXFilePath}%%%"

# regular expressions

search_exp = /[[:space:]]*([[a-zA-Z0-9]]*)[[:space:]]\*([a-zA-Z0-9]*)/
interface_start = /@interface/
block_end = /^\}/

#initializing variables

properties_list = []
properties_string = ""
reading_interface = 0

#---------------- Start Processing -----------------------------

file = File.open(doc, "r").readlines

file.each do |line| 

# capture the regular expression matches only in the 
# interface declaration and print out the matching
# property declarations

  case line

  # start capturing
  when interface_start
    reading_interface = 1
    puts line

  # capture and keep in properties_list
  when search_exp
    if (reading_interface == 1) then
      data = Regexp.last_match
      properties_list <<  data
    end
    puts line

  # unpack properties_list and print out the property
  # declarations
  when block_end
    if (reading_interface == 1) then
      reading_interface = 0
      properties_list.each do |pair|
        properties_string << "@property (readwrite, copy) #{pair[0].lstrip};\n"
      end
      puts line
      puts "\n" + properties_string
    end
  else puts line
  end

end

Я запускаю это, используя «без ввода» и «заменить содержимое документа» в качестве параметров ввода-вывода в редакторе пользовательских сценариев.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...