代码之家  ›  专栏  ›  技术社区  ›  Redwood

用于生成/合成属性的Xcode脚本

  •  9
  • Redwood  · 技术社区  · 16 年前

    8 回复  |  直到 16 年前
        1
  •  7
  •   Dad    16 年前

    我使用Accessorizer,它可以做到这一点,还有更多。

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

    非常便宜和强大。

        2
  •  2
  •   Redwood    16 年前

    这是我在很久以前发现的一个基础上提出的一个,用Python重写,并改进了它可以一次生成多个属性等。

    它将使用(copy)作为属性为所有选定的实例变量生成属性。

    #!/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()
    
        3
  •  2
  •   Pat    15 年前

    这是Xcode 3.2.4的python脚本,用于生成;接口属性、实现综合和dealloc。 要安装,复制此脚本,转到Xcode脚本菜单(倒数第二个) “编辑用户脚本…” 在“代码”下添加它,创建一个新的脚本名称,并将python脚本粘贴到下面。

    要使用,只需选择@接口下的变量,然后调用此脚本。 它不会将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()
    
        4
  •  1
  •   Kendall Helmstetter Gelner    16 年前

    #! /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;
    
        6
  •  1
  •   Alex Gray    13 年前

    从Xcode 4.4开始(可能更早)。..你的 IVAR s将自动合成。例如。。

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

    self.automatically = YES;
    

    并通过自动生成的前导下划线直接编辑实例变量,如。。

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

    @synthesize 必要的。

    至于快速轻松地进入此类 @property …将以下内容逐一拖动到“代码段”库中。。您可以指定键盘快捷键来插入这些跳转点,以便更快地输入属性。我用 rrr 对于对象和 啊啊啊 对于原始人。但那只是我。。

    @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;
    
        7
  •  0
  •   Pinochle    16 年前

    这是我昨天写的一个@property指令,几个小时后才遇到这个问题。这是一个简单的文本过滤器,将其扩展到@synthese指令(添加适当的 when case 声明并对其进行适当补充 when block_end 条件),并且不需要做太多工作来扩展它来处理一个文件中多次出现的@interface/@实现(通过跟踪它们的名称——可以通过正则表达式捕获来完成,因为其他一切都在脚本中):

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

    我在用户脚本编辑器中使用“无输入”和“替换文档内容”作为I/O选项来运行此程序。

        8
  •  0
  •   Kevin Callahan    16 年前

    配件 http://www.kevincallahan.org/software/accessorizer.html 做这些事情,还有更多。它还处理自定义前缀和后缀(后缀)。如果你想要谷歌的下划线,你就可以了。如果你想更改它,就可以动态更改它——不需要编辑脚本。此外,还有一个默认值表,您可以在其中根据传入的ivar类型(复制、保留、只读、分配等)定义默认属性说明符。它执行IBOutlet检测,并自动插入IBOutlet关键字,nils出-viewDidUnload的视图,执行多种样式的dealloc。它还为集合(NSMutableArray和NSSet)编写所有复杂的访问器。它可以进行密钥归档、各种锁定方法、对属性进行排序和合成块、编写KVO代码、Singleton代码、转换为选择器、生成HeaderDoc标签、NSLog()等。..它还有一个灵活的样式选项卡,用于在换行符上是否放置大括号、空格、自定义参数名称等。大多数事情都是通过服务处理的,所以你只需选择你的ivar块,点击一两个按键,就完成了。如果你将Accessorizer最小化到dock,它的界面就不会出现在前面,这样你就可以专注于Xcode或任何其他支持Services的编辑器。当然,Accessorizer也会写出显式的访问器(如Objective-C 1.0中),并允许您覆盖属性——所有这些都只需简单地切换开关即可。您甚至可以根据传入的类型自定义覆盖。观看视频以查看其实际效果。