Xcode 8— Converting Alcatraz’s plugin to Source Editor Extension
As we know Xcode 8 won’t load any plugins from Alcatraz. The good news is that Apple offers us an “official” way to create plugins which we can share with other developers through the Mac App Store.
Xcode Source Editor Extension
Xcode Source Editor Extension is the official way to create plugins as introduced at WWDC 2016. I would recommend to spend some time watching the video from WWDC since is the only official resource from Apple.
LogDisabler is an Xcode plugin which I create using the Alcatraz’s template. It was using UNIX’s command called ‘sed’ which was replacing specific text. In our case was NSLog( replaced with //DSLog(. The purpose was to disable logs before submitting the app to production.
Converting LogDisabler to Source Editor Extension
Actually I think that “Rewriting LogDisabler” would fit better since there was nothing in common between the two projects (other than the RegularExpression used to find the comment which eventually changed for better results).
Creating the Xcode Source Editor Extension project
Open Xcode and select File Create new project and select Cocoa from macOS template.
Fill in your details and make sure to select Objective-C for language. I am developing this project in Objective-C since almost all tutorials was developed in Swift.
Then we need to add a new target for our Extension. Go to File->New target and select Source Editor Extension.
Again, fill in your details and press Finish.
As you noticed Xcode created two targets. One for the desktop app and one for the Source Editor Extension.
On my project the Source Editor Extension was named Manage Logs(renamed from Manage Comments). Ignore the files on macOS target since we are not going to use them at this example.
In SourceEditorExtension.m you will find two commented-out methods:
extensionDidFinishLaunching(): If the extension needs to do any work at launch, implement this optional method. This method is called as soon as the extension is launched.
commandDefinitions: If the extension needs to return a collection of command definitions that differs from those in its Info.plist, implement this optional property getter.
For this project we won’t use SourceEditorExtension.m
On SourceEditorCommand.m you will find the method:
performCommandWithInvocation: This is where the magic happens. This method is called each time a button pressed (option in Editor we created).
Info.plist is where we are going to add our two options Enable Logs and Disable Logs.
The array XCSourceEditorCommandDefinitions will hold the details for the options. Copy/paste Item 0 and modified it to look like the above plist.
XCSourceEditorCommandIdentifier it’s an identifier that will be used in SourceEditorCommand to identify which button was pressed.
XCSourceEditorCommandName is the text that will appear in the Editor menu.
Now if you run the project you will notice that a new instance of Xcode is launched with a gray icon. That means that it’s the Xcode containing our extension.Open a project and click on any .m or .swift file. Then go to Editor and you will notice an option Manage Logs with two other options Enable Logs and Disable Logs. Try to click on them and of course nothing will happen.
PS. The Manage Logs option in(Editor->Manage Logs->Disable Logs) appears because it’s the name of the target. So if you need to change that option just change the name of the target.
Back to SourceEditorCommand.m
OK we are now ready to start developing! We just need to replace NSLog( with //NSLog and print( with //print(. That should be easy right?
Unfortanetly the problem is not so easy, it’s a little bit more complex but at the end was not so difficult either. So let’s see what we need to do:
- Identify if the current file is .swift or .m file
- Identify which button was pressed (Enable Logs or Disable Logs)
- Get the lines of the current file using NSArray
- Loop through the lines and find which lines are containing logs
- Replace the specific line with the commented-out log.
This is the code for our project:
Don’t worry. We are going to discuss every single step.
Identify the type of the current file
So, identifying the type of the current file is pretty much easy. Just check the string value in invocation.buffer.contentUTI for public.objective-c-source or public.swift-source. Since on our project the Regular Expressions and the strings that will be used for comment out the logs, are different for Swift and Objective-C, we need to check the type of the file and set the appropriate variables.
Identify which button was pressed
Remember the when we set a value for key XCSourceEditorCommandIdentifier for each button on Info.plist? On Line 2 we use invocation.commandIdentifier to check which button was clicked.
Loop through the lines of the current file
On Line 5 we loop through invocation.buffer.lines which is an array that holds the lines of the current file. You can access the line as NSString.
On Line 8 we check if the current line is commented-out. If it’s true,we skip it.
On Line 13 we check if the current line contains NSLog( or print(. If it’s containing the string then it will proceed to further checks. Otherwise it will skip the current line.
On Line 16 we call a function that I created. This function is checking if the string has a valid log message and if it’s true, it returns the modified string (commented-out log).
On Line 19 we get the index of the current line.
On Line 22 we replacing the current line with the modified one.
And that’s it! There is no need to describe commentLogFromLine and unCommentLogFromLine methods because you probably won’t need them. They are just parsing a string.
I think it was really simple to create that Extension. The point was to get the lines in NSString, which we are familiar with. As soon as you get the NSString you can use your imagination.
Xcode Source Editor Extension VS Alcatraz
After experimenting with both, I can now compare them and share my thoughts.
I found Source Editor Extension very easy to use although Apple offers only a video from WWDC 16 as documentation. Implementing any project requires the same procedure (Identify if the source file is Objective-C or Swift, Identify which button was pressed, get the lines in array, modify the line and replace). The downsides of Source Editor Extension is that it’s only for text replacement and that it can only edit one source file at a time.
On the other hand Alcatraz (plugins) was a bit more complex and I remember spending some time to get familiar with. For the LogDisabler I used NSTask and UNIX’s command sed to find and replace occurences for logs. I know, using sed to replace text in your project sounds a little bit scary since a lot of things can go wrong. The downside about Alcatraz’s plugin is that it won’t offer access to the source file as Source Editor Extension and sometimes you have to go deep to achieve that.
Comparing LogDisabler implementation with Source Editor Extension and Alcatraz plugin:
Source Editor Extension:
Easy access to the buffer of the source file. Much better “comment-out” algorithm without any known issues (able to comment out log command in one line with several commands). Edit only one source file at a time.
You have to go “low level” to replace text and this can mess up your project but as soon as you have a solid solution you can replace all log commands under your project. String manipulation was good but not as good as with Source Editor Extension.
You can find here the LogDisabler build with Source Editor Extension