Showing posts with label Xcode. Show all posts
Showing posts with label Xcode. Show all posts

Sunday, May 25, 2025

Accessing iOS App Files

iOS apps are built on a sandbox infrastructure, which normally makes it difficult to access the files within an app.

However, as an iOS developer with an app on the App Store, I’ve found a way to view my app’s contents using the Xcode application.

In Xcode, go to Window → Devices and Simulators to open the relevant window. You should see your connected iPhone there. In the Installed APPS panel, click the + button to add your app if it isn’t already listed. Once added, your app will appear in the list.

To view the contents of the app, you need to download it first. Click the ... button next to the app name and select Download from the options.

Once downloaded, you can view the package contents from Finder. The file I’m most interested in is the core database of my app. From here, I can easily examine all supporting files, including the database itself. Finder allows me to open the files I need and verify their contents.

This is an extremely valuable and convenient tool for iOS developers. It allows me to verify, back up, and review the app’s contents on my iPhone before making further changes. I can also modify and replace content for testing purposes, and then incorporate those changes into my Xcode project after thorough testing.

For example, I’ve recently been working on an update to migrate from a single database store to multiple database stores. This change lays the foundation for future features and improvements in data access efficiency. However, it involves a major overhaul of the project, affecting many parts of the codebase. I was concerned about testing these changes on my iPhone, especially because I didn’t want to risk losing 18 years of valuable collected data. With this tool, I can now safely back up my app by selecting Download container... before running any tests. If anything goes wrong, I can easily restore the app using the Replace container... option.

References

  • Try searching for "access iPhone app files on Mac from Xcode". As of May 25, 2025, the top results (possibly generated by AI) provided exactly the guidance I needed to begin my research.

Other Blogs

Read More...

Sunday, January 28, 2024

File Diff

Today I found a very good article about how to compare two text files. This is a handy tool not only for developers, but also good for people who want to quickly find out diference between files.

In the article, it mentioned 2 ways to access this tool. Actually, there is another very handy method to launch the app: Spotlight Search. Just press Cmd+space, then type FileMerge.

The article explains well about the app's usage. Read the article.

References

Read More...

Wednesday, May 17, 2023

Xcode 14.3 and Navigation Bar Issue

Recently, I encountered a very frustrating crash issue in my iOS app. After I updated my Xcode to version 14.3, my app suddenly crashed right at the launch time. I could not find where the crash occurred, even thought I set a break point at the very beginning of my project code.

I posted a question with my case on StackOverflow two days ago. That's the place where I normally try to get help from other talent developers around the world. The next day, I got two comments, which gave me direction on where to do further investigation.

Quickly, I found the place where the exception was raised and finally found a solution to resolve the issue! To cut a long story short, I put my question, my solution, and my findings about the issue in the hyperlink of my references.

References

Read More...

Friday, September 25, 2020

Xcode 12.0 and ToolChain Error

I updated my Xcode two days ago. After the update, I got a warning error about ToolChain failure: 

/Library/Developer/Toolchains/swift-2.2-SNAPSHOT-2015-12-10-a.xctoolchain:1:1: failed to load toolchain: 'Version' parse error: Could not parse version component from: 'swift-2'

There is no further explanation about this error in Xcode. After Google search, I found one solution. It seems that it is caused by Xcode missing remove it when I did Xcode update.


As in the suggestion, the tool chain has to be removed from /Library/Developer/ToolChain/. In my case, there are two items in the direct:

$ ls
swift-2.2-SNAPSHOT-2015-12-10-a.xctoolchain
swift-latest.xctoolchain

The first one is a directry, and the second is a link to the directory. For safe case, I use Finder to the directory, and compress those two items first as Archive.zip. Then Remove them completely.

I have to quit Xcode and relaunch Xcode again to do clean build. This time warning error is gone.

My Update Xcode 12.0


The update process was painful. It toke long time to download. I got failure 2 twice.

I had Xcode update frustrating experience before. There was one time the updated Xcode version could not build my project. I had to rollback to old version from TimeMachine backup.

From that experience, I have been kept copy of previous Xcode to external disk first. Then I remove Xcode from Applications. In this way, this time I did clean installation of Xcode 12.0. Another reason is that I do not have enough space in my hard disk to do update. I had to remove old version to get more space.

This clean installation may missed the step to remove ToolChain.

Read More...

Wednesday, March 04, 2020

Conditional Compilation in Xcode

Recently I have interest in C/C++ #ifdef macro features in Swift. The main reason is that I have some debug codes through out my project for test and debug purpose. For example, I like to add deinit destruction with print() codes in classes to test memory leaks. Since those destructors have no purpose to clean up resources, I have to comment out them in production compilation.

In Swift, there are some conditional compilation features to including or excluding block of codes. For example, the following codes using #available to test if the current iOS matches required version.

if #available(iOS 11.0, *)

Further investigation, I find out that Swift does support #if statements to provide conditional compilation. The following are steps to implement the feature.

Define Custom Compilation Flags


The first step is to define customized complication flags in Xcode project. In the section of Build Settings, find Swift Compiler - Custom Flags (make sure All and Combined are enabled on top tab bar).



Then, you can add custom flag variables. Here are two examples of DEBUG_PRINT and DEBUG_DEINIT. For each flag, add -D before it, like "-D DEBUG_DEINIT -D DEBUG_PRINT"



Note: in the picture, I defined those two with _X as suffix to disable them. If I want to enable them in compilation, I remove _X.

Use Custom Compilation Flags in Swift


To use #if feature, it is quite straightforward. For example, there is the conditional compilation block for destructor:


  1. // MARK: - CTOR
  2. #if DEBUG_DEINIT
  3. deinit {
  4.  Logger.debug() {
  5.    String("\(CopyMoveViewController.className) deinit")
  6.  }
  7. }
  8. #endif
and here is an example of conditional compilation block for some codes:

  1. class Logger {
  2.  ...
  3.  static func debug(_ message: () -> String) {
  4.    #if DEBUG_PRINT
  5.    print(message())
  6.    #elseif DEBUG_DEINIT
  7.    print(message())
  8.    #endif
  9.  }
  10. }

Now I don't need to comment out some debug codes when I need to compile my project to production release target. The only thing I need to do is to disable those flags in my Xcode project file.

References



Read More...

Wednesday, January 08, 2020

Update to Xcode 11.3

I have been working on iOS projects with Xcode 11.1 for quite long time. Previously, I tried to update to 11.2. However, I experienced crashing issues after I compiled my project with Xcode. The problem was text editor. Each time when I tried to enter edit mode with text field UI, the app suddenly crashed. That was really bad experience.


I had to restore back to 11.1 version. Fortunately, I have Time Machine backup. From there, I restored Xcode 11.1.

Xcode 11.3 was released, but I did not update right away. Until I tried to compile my project to my iPhone with iOS 13. I got message that I had to move up to newer Xcode supporting iOS 13. I have no choice to move  on.

Update from App Store is also a pain. I tried for several days to update but I got failure each time. There is no reason given, and each time it took long period to download package, 7.8GB. That was really frustrating.

Yesterday a idea came up to my mind. Why not to go to Apple developer site to get Xcode? There must be Xcode 11.3 package just for download only. I went to the site and Xcode.xip is in the section of More(Beta, Release and More 3 sections). This makes me update much easier because it is the straight download.

After downloading, I clicked on the package. Soon I got a message of "not enough space on destination". That's why. The disk space is always problem on MacBookAir/Pro. What I did is to move out existing Xcode from Application to external disk. Finally I got Xcode 11.3 successfully installed!

So far, I have no issues with this version, and I could run my app on my iPhone for testing.

Read More...

Friday, May 31, 2019

Add Remote Repository to Xcode Project

I have been using BitBucket web based repository for a while. I like its user interface, easy and convenient to view changes of my Xcode projects. Another nice thing about this service is that BitBucket allows to create private repositories and it is free.

To add a project to git repository, exit Xcode and open a Terminal.

Local Git Repository


For creating a local git repository, please read my previous blog. This step is important because the remote git repository is based on local git repository.

Remote Git Repository


Create a new repository on BitBucket first, for example, myTestPository. Use the following command in Terminal to link local repository to remote.

# Sets to new remote
git remote add origin remote git@bitbucket.org:yourAccountName/randomdraw.git
# Verifies the new remote URL
git remote -v
# Pushes the changes in your local repository up to the remote repository you specified as the origin
$ git push -u origin master

With the above steps completion, your remote git repository is ready for committing any new changes/updates.

References


Read More...

Friday, March 29, 2019

Xcode 10.2, Swift 5, iOS 12.2 Released

On March 25, 2019, Apple formally released Xcode 10.2 with Swift 5, iOS 12.2, and others. With the release available, it is time for me to update my app projects. The following are some issues I have encountered and I think it is worthwhile to take some notes.

Warnings and Errors


The update, as same as my previous experiences, presented some issues I have to deal with. The migration process does not resolve all the issues. I got about 69 errors and 50 warnings. Those are the issues I have to check and change one by one. Fortunately, most of them are just API's changes, and Xcode does provides enough hints to tell what have changed. For example method name changes, and constants changes.

Fatal Error with no Clue


After I manually migrated all those changes. I got no warnings, and no errors in my codes. However, I had this no-clue fatal error:

Command CompileSwiftSources failed with a nonzero exit code

As my project is so complex and has massive codes. I was lost at first. Where should I start to resolve the issue? Since the Xcode 10.2 and Swift 5 are quite new. I could not find direct or clear solution from web.

Even so, some people had similar issue in their project. I looked at their cases and solutions carefully. I have to say some of their analysis provide me lights out of dark. Finally, I got this issue resolved.

In the project settings, this was my previous setting in Build Settings -> Compilation Mode:



the setting is Incremental. I found one solution is to change this to Whole Module as in the following:




Then I continued to build my project. This time, I got serval errors, which could not be found in Incremental compilation mode. One error complains that override cannot be applied to method in extension.

Here I give a simplified example: a base class implementing a none-objc delegate in iOS SDK's framework. When I wrote this base class, I put all the delegate methods within an extension of the base class. I like to use the strategy to separate delegate implementation in the extension block. This is OK in Swift 4 and 5 versions.

There are some occasions, I need to override some of those methods in inheritance classes. In the inheritance class I continue to put those override methods in extension block. In the previous version of Swift, ie ver 4, it is OK. However, this is not allowed in Swift 5. In other words, the override none-objc methods cannot be defined in extension.

It seems that Xcode Incremental compilation could not be identified the issue. Each file can be built or compiled without any errors. However, in the later or integration stage of compilation, Xcode could not resolve integration of the base class and inheritance class, based on Swift 5 rules. This may be Xcode bug. Fortunately, Xcode could start reporting and explaining errors in Whole Module mode.

I moved codes from extension to its class body block, those errors are gone!

After the successful build, I changed the compilation mode back iOS recommended default Incremental mode again.


References


Read More...

Tuesday, December 04, 2018

Add New Version to Core Data

There are sometimes when I need to update Core Data from existing version to a new version. In other words, I need to update Core Data database with changes on some entities. Apple provides very good framework to do that: Lightweight Migration. Here are my notes on how to add a new version to Core Data.

I am using my TapToCount-3W project as example.

In Xcode, select the data model in Project navigator:



From menu Editor, you will find Add Model Version....




Accept the default settings or make changes if you need. Make sure the base version is the most active version as base. In this case, I accept TapToCount 1.3 as my new version.

Change the property settings: identifier to 1.3 and current version to 1.3. A new version of database model is created and ready for update.



As it is indicated in Project navigator:



References




Read More...

Sunday, October 07, 2018

Run app on iOS 12.0 device from Xcode 9.4

Recently I updated my iPhone 5s with iOS 12.0. To my surprise, Xcode 9.4 on my Mac High Sierra dose not support iOS 12.0 device. That's hold on my app development.

My first reaction was to install the newest Xcode 10 beta 2 from Apple. The whole package is pretty big, 5GB+. That's OK. I downloaded the package and installed it. After that I was rushing to run my project in Xcode 10 (I did make a copy of the project to an external HD to run, to avoid any mass up). There were not much updates I had to make change in my app codes, however, I got a compiling error.

That was really painful time. The error seems to be related to linkage to iOS frameworks, no thing to do with my codes. I could not figure out what's wrong in Mac library system. Even worse, I could not run Xcode 9.4 with my project neither, with the similar linkage error.

I tried to look into my Mac library and Xcode framework dependencies. Remove and re-add frameworks. Nothing was working. I decided to stop further trying or mass-up. Took a break.

Fortunately, before I installed Xcode 10 beta 2, I checked in all my codes into my code-repository. I had my Mac Time Machine on. I know what is the time when I had the correct and latest working version of my Xcode 9.4, and it was just one day before. By using TM restoration, I finally got my app, in about 3 hours restoration of my Mac, working in Xcode 9.4.

With the successful restoration, I followed an advice from SO, copying iOS 12.0 supported folders from Xcode 10 beta to my Xcode 9.4.


Xcode-beta/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport


To


/Applications/Xcode/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

Now I can run my codes from Xcode 9.4 to my iPhone 5s device.

What's adventure and life-saving experience!


References


Read More...

Thursday, June 21, 2018

Debugging with Xcode and LLDB

Really cool stuff on lldb, Xcode debug power tool. As well there is one training video from WWDC 2018: Advanced Debugging with Xcode and lldb.



Read More...

Monday, November 13, 2017

Dynamic Height of TableView Cell

For table view in iOS, its cell could be one of several styles: basic, detail, subtitle and custom. The first 3 are built-in style. The title, subtitle, and detail are labels in the cell. By default, those labels cannot grow vertically if text in the label is too long. I found out there is a property for label to control if displaying only one or multiple lines: numberOfLines. By default, this property is one.

If you change the property to 0, the label may grow vertically to show all the text in it. That means the view cell in a table view could grow dynamically in height. This is true for the first three styles. However, for the custom view cell, it is tricky.

Recently, I was working on my iOS app to enhance its user interface. One of table views contains custom view cell. I would like to show cells in dynamic height if labels in the cell having long text. At first, I tried to set up enough constrains in the custom view cell so that no any warning nor errors in storyboard. However, my view could not show dynamic cell height.



It seems that all cell height are the same.

Finally I figured out why. The contrails in the custom view cell have to be enough for UIKit to calculate the height. In my view, I placed three labels one by one vertically. From top to bottom, I set constrains one to next vertically. However, I did not add a constrain from the last label to the bottom. As a result, UIKit could get the height of view cell.



I added the constrain to make sure the last label's bottom related to the bottom of its container. With this change, I got my app working as expected.




Enough-constrains-for-vertial-span is a trick for custom table view cell. There are other things which have to be set for dynamic cell height. See the reference blog for detail.

By the way, my app supports several languages. Therefore, for this update, I have to make all the changes in all language views in storyboard. You may notice that the messy one above is in Chinese, and the next expected one is in English. By the time I am writing this blog, I have not updated my Chinese view yet.

Reference


Read More...

Monday, May 08, 2017

Workspace in Xcode

One of interesting things I learned from Developing Apps for iOS CS193P, by Stanford U, is using Workspace in Xcode. This was explained in detail in lecture 9, TableView, at about 46minute.



Follow the instruction, what I did in my app project is to convert my project to Workspace from menu File-Save as Workspace...

Then from Finder to locate another project file and drag it to the Workspace, as two parallel projects.

One of project is a iOS Target type. This target has to be added to another project as embedded binary in project. This can be done by dragging the target to project settings.

The advantage of Workspace is that I can modify both project if needed.

Read More...

Thursday, May 04, 2017

Xcode and Swift Tips and Tricks

During my development of my iOS app, I have encountered some hurdles. Even though some of them are small ones, I did spend a lots of time to find solution to overcome them. Here are some in my past short period.

Can't find customized class from Storyboard


Normally customized view controller classes are used to handle specific requirement for some views. In Storyboard, this customized class can be specified in Storyboard right property panel "Show the identity inspector" tab.

For whatever reason, I may pressed space bar by chance in the area of Module. This caused the Module's value as None! As a result, I got this run time error:

Could not cast value of type 'UIViewController' (0x113f1b798) to 'MyProject.MyViewController' (0x113985200).

I spent hours trying to figure out why. Eventually, I found out the simple solution: change the module to my project.

This is what I had in my Storyboard for this customized class setting:



Hide Tabbar and use toolbar


In a tabar driven app, sometimes, I may need to hide tabbar and show a toolbar as alternative. To hide tabar for next view controller, it can be done in prepare(for segue: sender:) event.

  1. override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  2.  ...
  3.  let vc = segue.destination
  4.  // call vc's hidebottombar
  5.  vc.hidesBottomBarWhenPushed = true
  6.  ...
  7. }

For the case of normal view controller, bar button item can be directly added to the bottom of the view. For the case of tableview controller, in order to show bar button item on the bottom, a toolbar has to be used to encapsulate bar button items. This can be done in storyboard.

However, it seems that there is no API or method to let vc to show or hide toolbar. At first, I found a solution to show or hide toolbar by adjusting toolbar y position in viewWillAppear event. The problem is that if user changes device from portrait to landscape, the toolbar will be off screen, disappeared. As a result, I have to adjust toolbar y position in the event of viewRotated.

To adjust toolbar y position, I add the following API to view controller as extension. This makes it easier in call this API to show toolbar.

  1. extension UIViewController {
  2.  func setToolBarShow(show: Bool, delay: Double = 2) {
  3.    if let toolbar = self.navigationController?.toolbar {
  4.      let newOriginY = UIScreen.main.bounds.height - toolbar.bounds.size.height
  5.      if toolbar.frame.origin.y != newOriginY {
  6.        toolbar.frame.origin.y = newOriginY
  7.      }
  8.      if delay > 0 {
  9.        // Make sure origin is set correctly
  10.        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
  11.          if toolbar.frame.origin.y != newOriginY {
  12.            toolbar.frame.origin.y = newOriginY
  13.          }
  14.        }
  15.      }
  16.    }
  17.  }
  18. ...
  19. }

Here the trick to show toolbar in the events of viewDidAppeared and viewRotated is to delay adjusting toolbar y position. It seems that in both events, without delay, toolbar would not show. It may be something inside UIKit would not seeing this adjustment. With delay, the toolbar would show correctly at the bottom of the view.

Change navigation back button's title


In the case of parent VC to child VC by using navigation controller, the top-left button is used to pop off child VC and back to parent VC. The title of this button is by default the title of the parent VC. If the parent title is too long, it may make top text too crowded if the title of child VC is too long. One simple way to change top-left button title to "Back". This can be done by clearing parent title before pushing to child VC. UIKit will automatically use "Back" as the title.

Here is the solution to do that: clear view's title when pushing a new view controller. This can be done in the event of prepare() for segue.

One thing to remember is that to set view's title on the event of viewDidAppear(), otherwise, the view's title would be blank if the view is pushed back.

Resources


Read More...

Saturday, March 04, 2017

Localization Experience in Swift Project

When I tried to jump into localization for my app, I thought it would be a simple job. Actually, I have encountered several hurdles during this progress. Here I would like to make some notes about issues I had experienced.

Bomb! Unexpected Exception


I started to add supporting to two sets of Chinese(my native language): mainly string files such as .strings, and .stringsdict files. This can be done by clicking on Localization button on the right Utilities panel in Xcode. By enabling localization, Xcode would move the English string or plist file from project root into en.lproj folder, and creating other language folders for localized resources, for example, zh-Hant.lproj for Simplified Chinese.

One thing I noticed is that plist file also has Localization section in Xcode right Utilities panel. I thought this could be localized and I did see some settings in some plist file requiring localization. Then I tried to localize plist file together. That's a mistake. See the following sections.


I have a long list of resource files. I spent about 2 days to finish localization for my project. With all the resource files ready, I stared to compile my project and tried to start my test on Simulator. Bomb! I saw this exception right away.




That's really bizarre exception. What does it mean by "Invalid argument"? It does not provide any detail clue about the exception. I had to try internet search to find out if any other people experienced the same issue.

No Solutions Anywhere


Soon I found out this is a common issue for some Swift developers and some suggestions were provided. Unfortunately, I tried all the solutions provided, but I could not resolve this exception in my case. Two days passed, I was really in struggle stage and with no clue to fix the issue.

With no way to get out, I stood back for a moment. With so many changes in my codes, I might mess up somewhere in my project. Instead of finding a way out of the maze, what I could do is to move my project back to the previous working version and redo the localization again. This was the last and difficult choice because I did not want to give up my localization effort.

Roll Back to Previous Working Version


I decided to discard all the changes in my codes from Source Control (fortunately, I have kept doing source code repository by using Xcode Source Control feature) and rolled back to previous version. This time, I started to do one change, and then compiling my project and running my app on Simulator at a time. In this way, I could figure out where the exception would be. I was also very careful coding localization to avoid any error causing unnecessary changes in my project.

I started from my app's settings, i.e, my app name, location privacy description, and app settings, where some localizations are required. I was so lucky that I soon located the issue which caused the exception.

Don't Localize info.plist


[app_name]-info.plist is a project property file, where project settings specific to my app are defined. For simplification, I call this info property file as info.plist in this blog. I need to localize my app display name and location privacy description in this plist.

In Xcode, all string files and plist files can be localized by clicking on Localize... button in Localization section on right Utilities penal.



When a resource file is localized, the English version file is moved from project root to en.lproj folder, and other localized files are created under corresponding language folders, for example, zh-Hans.lproj is the folder for Simplified Chinese resource files.

The problem is that info.plist file would be missing from project root if it were localized. The compile and installation processes require info.plist at project root. Actually, there is a resource file infoPlist.strings for info.plist localization purpose!

To verify this, I discard my changes again. For my working version, I found Identify section in my project settings (select my app as target, in General tab):



However, the same setting would be like as follows if the info.plist is localized.



Even I tried to locate the info.plist file from Choose button, I could not locate it from the dialog window.



Don't Localize Plist?


While I am writing this blog, I realize that none of plist files in my project require localization directly. Further exploration of the APIs Apple provides for localization, all strings files are automatically localized without any reference to device local settings from API interface calls. However, to load a plist, it seems that you have to specify a path for the plist file.

I remember that when I initially added a localization language to my project, from Info tab in project settings:




I saw a prompt dialog for adding new language for only two items: infoPlist.strings, which is for info.plist, and MainStoryboard.storyboard.



For info.plist file, Xcode provides corresponding infoPlist.strings file for localization. In my project, another place where a plist file is used is for my app default settings.

Localization of App Default Settings


The app default settings are defined in a bundle file: Settings.bundle, where a plist file Root.plist, which for default settings, and an English localization strings file within en.lproj folder, are created by Xcode at the time when the project was initially created.




To localize default settings for another language, a language folder and strings file have to be created manually(as in above zh-Hans.lproj and zh-Hant.lproj). The reason is that the plist file and strings files are within the bundle file and there is no Localization section in the right Utilities panel for adding new languages.

I found out that in order for settings working for any other languages, do not remove one item in Root.plist file: Strings Filename and its value as Root.



I had trouble to make default settings working while I was making changes in Root.plist file. I did not understand the usage of that item and removed it from the plist when I tried to clean up default settings. I was tumbled by this mistake. Anyway, it is a good experience and I now fully understand its importance.

References


Read More...

Monday, November 07, 2016

Playground and Testing Subquery by NSExpression (3)

In order to apply more than one fetch conditions on each of a subset property, I think that I can still use Subquery to do that. For my test example, friends property is a collection and it can be used as the first parameter in a Subquery. In this way, the predicate, the third parameter in the Subquery, can apply more than one fetch conditions on each friend.

Nested Subquery


Fortunately, it is possible to build a nested Subquery expression. The result of a Subquery is an array(may be empty). By using NSExpression's function count, the second or nested Subquery can be placed in a Subquery expression's third parameter predicate: Subquery(...).@count > 0 as an example.

Thereafter, it comes my nested Subquery test:

TestSubquery.TestNestedSubquery(nameContains: "L", birthYearLowLimit: 2010, friendsLevel: 1)

and the result is as follows:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010, friends: (none)
  4.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  5. Collection expression: {<MyPlayground_Sources.Person: 0x618000070700>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x618000070700>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000070700>}, $x, SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0)
  12.  collection: {<MyPlayground_Sources.Person: 0x618000070700>}
  13.  variable: x
  14.  predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
  15. Subquery expression result value: Optional(<__NSArrayM 0x60800005f8f0>(
  16. <MyPlayground_Sources.Person: 0x618000070700>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x618000070700>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

It is so fun to test Subquery in Playground!

One-to-many Relationships to More Than One Levels


With above knowledge about Subquery expression, we may use powerful Subquery expression to go to deep more than one level of one-to-many relationships. For example, we may query persons with constrains on the second level of friends, i.e., friends' friends.

TestSubquery.TestNestedSubquery(aggregate: .none, nameContains: "a1", checkFriendsNull: true, friendsLevel: 2)

The above test applies aggregate operator NONE on the second level friends property of person Bob, i.e., friends' friends. Here are printed out debug information, as-expected:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010
  4.      Friend - name: Tony1, birthYear: 2012, friends: (none)
  5.      Friend - name: Lisa1, birthYear: 2011, friends: (none)
  6.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  7. Collection expression: {<MyPlayground_Sources.Person: 0x60000007fa00>}
  8. Collection expression result value: (
  9.    "<MyPlayground_Sources.Person: 0x60000007fa00>"
  10. )
  11. Person - name: Bob, birthYear: 1997
  12. Predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
  13. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000007fa00>}, $x, SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0)
  14.  collection: {<MyPlayground_Sources.Person: 0x60000007fa00>}
  15.  variable: x
  16.  predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
  17. Subquery expression result value: Optional(<__NSArrayM 0x618000059fb0>(
  18. )
  19. )
  20. Subquery expression value as [Person]: [] as follows

empty array because none of friends' friends Lisa1 contains "a1".

Updates


Important findings! Recently (2017.02.21) I found an issue or restriction with Subquery. All the objects in Subquery have to be key-value-coding-compliant, that is, all the objects have to be based on NSObject.

In other words, object from none NSObject based class such as struct would cause exception or crash when calling method expressionValue(with: context:). Subquery would not be able to figure out any methods without implementing key-value-observation(KVO) protocol. I spent almost more than one day to figure this issue out.

Resources



Read More...

Wednesday, October 26, 2016

Playground and Testing Subquery by NSExpression (2)

My next test is to use Subquery expression to test a one-to-many case. A person may have some friends. My search is to find a person whose friends at least one's birth year is greater than 2011. For this case, the operator any can be used. I have defined an aggregation enumerate type for using operators:

  1. public enum AggregateType: String {
  2.  case any = "any"
  3.  case all = "all"
  4.  case none = "none"
  5. }

By the way, I found in my test that in Subquery, the operator NONE is actually NOT ALL.

One-to-many Relationship Test


Here is my test in Playground:

TestSubquery.TestSubquery(aggregate: .any, birthYearLowLimit: 2011, friendsLevel: 1)

One of Bob's friend Lisa was born in the year of 2012. Therefore, the expected result Bob is found:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010, friends: (none)
  4.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  5. Collection expression: {<MyPlayground_Sources.Person: 0x600000262040>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x600000262040>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: ANY $x.friends.birthYear > 2011
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x600000262040>}, $x, ANY $x.friends.birthYear > 2011)
  12.  collection: {<MyPlayground_Sources.Person: 0x600000262040>}
  13.  variable: x
  14.  predicate: ANY $x.friends.birthYear > 2011
  15. Subquery expression result value: Optional(<__NSArrayM 0x600000055240>(
  16. <MyPlayground_Sources.Person: 0x600000262040>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x600000262040>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

I tested other two operators(all and none). The results are all empty array(of Person). That's expected.

Need to Check Friends Collection?


What happens if Bob has no friends? I thought. I immediately did the following test with friendsLevel to 0:

TestSubquery.TestSubquery(aggregate: .any, birthYearLowLimit: 2011, friendsLevel: 0)

The result is as expected, empty array. However, to my surprise, I got Bob in an array of [Person] for both ALL and NONE operators:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x60000006aec0>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x60000006aec0>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: ALL $x.friends.birthYear > 2011
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000006aec0>}, $x, ALL $x.friends.birthYear > 2011)
  10.  collection: {<MyPlayground_Sources.Person: 0x60000006aec0>}
  11.  variable: x
  12.  predicate: ALL $x.friends.birthYear > 2011
  13. Subquery expression result value: Optional(<__NSArrayM 0x60000005e960>(
  14. <MyPlayground_Sources.Person: 0x60000006aec0>
  15. )
  16. )
  17. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60000006aec0>] as follows
  18. Person - name: Bob, birthYear: 1997

I am not sure if this is a bug in Subquery. Still I don't know how the implementation is done within the predicate. To guarantee the result is correct, I think that further checking if friends being nil is needed. My tests are updated with additional parameter to check friends so that the predicate contains checking if friends being nil.

TestSubquery.TestSubquery(aggregate: .all, birthYearLowLimit: 2011, checkFriendsNull: true, friendsLevel: 0)

The test result is prefect, as-expected if checkFriendsNull parameter is set to true. All tests for other two operators are also correct.

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x618000069200>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x618000069200>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000069200>}, $x, $x.friends != nil AND ALL $x.friends.birthYear > 2011)
  10.  collection: {<MyPlayground_Sources.Person: 0x618000069200>}
  11.  variable: x
  12.  predicate: $x.friends != nil AND ALL $x.friends.birthYear > 2011
  13. Subquery expression result value: Optional(<__NSArrayM 0x608000056b30>(
  14. )
  15. )
  16. Subquery expression value as [Person]: [] as follows

More on One-to-many Relationship Test


The above test has only one condition on friends' birthYear property. How about to add one more condition on property name containing something?

Here is a test with two conditions:

TestSubquery.TestSubquery(aggregate: .any, nameContains: "T", birthYearLowLimit: 2011, checkFriendsNull: true, friendsLevel: 1)

The result of the test is as follows:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010, friends: (none)
  4.    Friend - name: Lisa, birthYear: 2012, friends: (none)
  5. Collection expression: {<MyPlayground_Sources.Person: 0x60800007f5c0>}
  6. Collection expression result value: (
  7.    "<MyPlayground_Sources.Person: 0x60800007f5c0>"
  8. )
  9. Person - name: Bob, birthYear: 1997
  10. Predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
  11. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60800007f5c0>}, $x, $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011)
  12.  collection: {<MyPlayground_Sources.Person: 0x60800007f5c0>}
  13.  variable: x
  14.  predicate: $x.friends != nil AND ANY $x.friends.name CONTAINS "T" AND ANY $x.friends.birthYear > 2011
  15. Subquery expression result value: Optional(<__NSArrayM 0x618000051af0>(
  16. <MyPlayground_Sources.Person: 0x60800007f5c0>
  17. )
  18. )
  19. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x60800007f5c0>] as follows
  20. Person - name: Bob, birthYear: 1997
  21.    Friend - name: Tony, birthYear: 2010, friends: (none)
  22.    Friend - name: Lisa, birthYear: 2012, friends: (none)

The result seems correct, however, Bob's friend Tony was born in 2010, and another friend Lisa was born in 2012. If you look carefully at the logic of Subquery's predicate, the fetch conditions are done by ANY operator on friends subset separately, i.e., checking only on name on friends, and then only birth year on friends. It does not mean one of friends whose both name and birth year meet fetch conditions.

In my next blog, I'll continue to talk about how to find out that at least one of friend's both properties meed fetch requirement.

Resources


Read More...

Monday, October 24, 2016

Playground and Testing Subquery by NSExpression (1)

I am not sure when Playground was introduced to Xcode, may version 2? I started to use Playground when I moved to Xcode 3. As I mentioned in my previous blog, I would like to find out a better way to test Subquery.

It is very easy to add a Playground in iOS project in Xcode. However, it is hard to test CoreData in Playground. First, CoreData model is not easy to add to Playground, even though I eventually found a way to add CoreData context to Playground. Next difficulty is that I still cannot fig out how to add CoreData managed data classes to Playground. It seems that namespace between my project and Playground are different. I could change all my managed classes to public, but they are not visible nor accessible through CoreData fetch mechanism.

My purpose is to test Subquery. When I found out that Subquery can be built in NSExpression, I immediately think about Playground as a place to do my test.

Playground in Xcode


To add a Playground to Xcode project is very simple: just add a new file to Xcode project and then select Playground. By default, it is called as MyPlayground. There are two folders within the Playground: Sources for all swift source codes, and Resources for resource files.

The following is MyPlayground in Xcode left panel Project navigator view.



On the right panel, the left area on the top is an editable area for swift codes. The right side on the top is an area for immediate result view and the bottom is debug result view.



It is better to organize all the classes, extensions, enumerations and types in swift files under Sources folder. In order to access them from Playground, all the accessible elements have to be public.

During your start test stage, you may need to place all your classes and variables, types, and functions in Playground. In this way, any run-time errors would be highlighted during your test. When all those elements are OK, you can move them to a swift file under Sources folder.

Test Class


As I mentioned in my previous blog, the first parameter in Subquery is a collection of objects, defined by a class. What I found that there are some requirement for the class. First, the class has to inherit from NSObject which has hash code for equality. Secondly, if you need to use operator ALL, ANY, or NONE on collection properties defined in the class, the collection has to be NSSet type.

For my test, I defined the following Person class.

  1. public class Person : NSObject {
  2.    public var name: String
  3.    public let birthYear: Int
  4.    public var friends: NSSet?
  5.    init(name pName: String, birthYear year : Int) {
  6.        name = pName
  7.        birthYear = year
  8.        super.init()
  9.    }
  10. }

I created a swift file TestSubquery.swift in Sources folder. The Person class is defined there. This class is perfect data for Subquery's first parameter: collection. The class has two simple properties name and birthYear, and one collection property friends. In my test, only one person object is created and placed in an array as collection, which is used as the first parameter in Subquery.

friends property is optional NSSet type collection, where actually Person objects are hold or null if no objects. Each person in friends could have friends. In this way, nested friends could be used as many levels of one-to-many relationship for testing. The following is my help function(in TestSubquery.swift file as a module function) to create base data for my tests. The function takes one parameter to control levels of nested friends. I'll explain how to use this parameter to do some interesting tests later.

  1. func getPerson(friendsLevel: Int) -> [Person] {
  2.    print("Creating Person object and friends...")
  3.    let p = Person(name: "Bob", birthYear: 1997)
  4.    if friendsLevel > 0 {
  5.        let p1 = Person(name: "Tony", birthYear: 2010)
  6.        if friendsLevel > 1 {
  7.            let p1_1 = Person(name: "Tony1", birthYear: 2012)
  8.            if friendsLevel > 2 {
  9.                 let p1_1_1 = Person(name: "Tony1_1", birthYear: 2011)
  10.                 let p1_1_2 = Person(name: "Tony1_2", birthYear: 2012)
  11.                 p1_1.friends = NSSet(objects: p1_1_1, p1_1_2)
  12.            }
  13.            let p1_2 = Person(name: "Lisa1", birthYear: 2011)
  14.            p1.friends = NSSet(objects: p1_1, p1_2)
  15.        }
  16.        let p2 = Person(name: "Lisa", birthYear: 2012)
  17.        p.friends = NSSet(objects: p1, p2)
  18.    }
  19.    printPerson(personCollection: [p], printFriends: true)
  20.    return [p]
  21. }

Here is the complete debug information of person data structure (up to 2 levels of nested friends):

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3.    Friend - name: Tony, birthYear: 2010
  4.      Friend - name: Tony1, birthYear: 2012
  5.        Friend - name: Tony1_2, birthYear: 2012, friends: (none)
  6.        Friend - name: Tony1_1, birthYear: 2011, friends: (none)
  7.      Friend - name: Lisa1, birthYear: 2011, friends: (none)
  8.    Friend - name: Lisa, birthYear: 2012, friends: (none)


In addition to that, I have added some other types and help functions, as well as a test class TestSubquery. Here I don't need to list all the codes for them. Only test codes will be provided and explained in the following sections.





The complete source code can be downloaded from a link in Resources section.

Test Structure


Subquery NSExpression will be used as an expression object for my tests. To build a Subquery expression, two things have to be prepared: a collection data or expression and a predicate. Therefore, all my tests are defined in the following structure or steps:


  1. get an expression of Person collection,
  2. create a predicate, and
  3. build a Subquery expression, finally evaluate the Subquery expression to get result.

Step 1 is very simple. The collection of Person is obtained from the function getPerson(...) as explained above. In step 2, a predicate is created by one of a list of help functions, which define interested fetch query conditions against to the collection of Person. Finally, it is time ready to build a Subquery expression and then to evaluate its result in step 3.

Let's look at a simple test func defined in TestSubquery class.

  1. public class TestSubquery {
  2. ...
  3.  public static func TestSubquery(nameContains: String, birthYearLowLimit: Int, friendsLevel: Int = 2) {
  4.    let e = getColletionExpression(friendsLevel: friendsLevel)
  5.    let predicate = getPredicate(nameContains: nameContains, birthYearLowLimit: birthYearLowLimit)
  6.    let expValue = getSubqueryExpressionResult(collection: e, predicate: predicate)
  7.    printSubqueryExpressionWith(result: expValue)
  8.  }
  9. ...
  10. }

At line 4, only one person with a name, birth year and a collection of friends (or null if no friends) is created as collection expression by the help func of getCollectionExpression. At line 5, a predicate with CONTAINS(string) function for name and low limit for birth year is obtained from the help func of getPredicate. With the data collection and predicate ready, at line 6, the help func of getSubqueryExpressionResult builds a Subquery expression and evaluates the result.


Test and Result


To better understand my test, extensive information are printed out in debug window. Here is one example of using above test func in Playground:

TestSubquery.TestSubquery(nameContains: "L", birthYearLowLimit: 2010, friendsLevel: 0)

and the debug information is as follows:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x60800007e940>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x60800007e940>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: $x.name CONTAINS "L" AND $x.birthYear > 2010
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60800007e940>}, $x, $x.name CONTAINS "L" AND $x.birthYear > 2010)
  10.  collection: {<MyPlayground_Sources.Person: 0x60800007e940>}
  11.  variable: x
  12.  predicate: $x.name CONTAINS "L" AND $x.birthYear > 2010
  13. Subquery expression result value: Optional(<__NSArrayM 0x610000046030>(
  14. )
  15. )
  16. Subquery expression value as [Person]: [] as follows

In this test result, line 1-7 explain that only one person "Bob" who is born in 1997 created as in a collection expression. The debug information at line 8 is the print-out during a predicate was obtained. From line 9-12 are information about building Subquery expression, and its three parameters: collection, variable and predicate. The information at remaining lines shows the Subquery expression evaluation result, and detail information as an array of Person or empty if no result.

If test is changed to name containing "B" and birth year greater than 1990, the person Bob will be in the result array:

  1. Creating Person object and friends...
  2. Person - name: Bob, birthYear: 1997
  3. Collection expression: {<MyPlayground_Sources.Person: 0x608000077fc0>}
  4. Collection expression result value: (
  5.    "<MyPlayground_Sources.Person: 0x608000077fc0>"
  6. )
  7. Person - name: Bob, birthYear: 1997
  8. Predicate: $x.name CONTAINS "B" AND $x.birthYear > 1990
  9. Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x608000077fc0>}, $x, $x.name CONTAINS "B" AND $x.birthYear > 1990)
  10.  collection: {<MyPlayground_Sources.Person: 0x608000077fc0>}
  11.  variable: x
  12.  predicate: $x.name CONTAINS "B" AND $x.birthYear > 1990
  13. Subquery expression result value: Optional(<__NSArrayM 0x60800005cc50>(
  14. <MyPlayground_Sources.Person: 0x608000077fc0>
  15. )
  16. )
  17. Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x608000077fc0>] as follows
  18. Person - name: Bob, birthYear: 1997

It is really fun to test Subquery expression in Playground.

Stay tuned for the power of Subquery expression and more interesting findings in my other tests.

Resources


Read More...

Tuesday, October 11, 2016

Swift 3 and Generics

Soon after Apple offically released Swift 3 and Xcode 8 after WWDC 2016 on September 13, 2016, I started a process to convert my iOS application to this new version. This time, Xcode provides an auto-conversion tool to convert most of my codes to Swift 3. However, I still had manually to make some changes to handle compiling errors and warnings.

For the errors, I had no choice to fix my codes. One interesting feature in Xcode 3 is that any function call which returns a type, a warning message is generated even the result is assigned to a variable. For example, MyLoger class uses chaining method a lot, that is, method call returning back to class itself.

  1. self.logger.debug() {
  2.   return "[\(#function)] is called." }.indent(true)


even the above method call does not return its result to a variable, Xcode still shows a warning message: result of call to 'indent' is unused. Since MyLogger is used in many places, a long list warning appears very annoying. What my fix is to add a no result call method endChain() to the log class.

  1. self.logger.debug() {
  2.   return "[\(#function)] is called." }.indent(true).endChain()


Those changes are OK, except one I still cannot find a solution, as explained in the following section.

Subclass Based on Generic Class not Available in Storyboard


I have a base class based on UITableViewController and NSFetchedResultsControllerDelegate. This base class contains most of basic codes for table view and table view's data source. I need to utilize Swift generic feature to define this base class as generic class, holding common codes. Then my subclass based on the generic base class would have specialized codes for various data entities.

The problem is that all my subclasses based on this generic base class are not showing up in Storyboard (Showing the identity inspector tab, Custom class dropdown list). By narrowing down to a simple generic class and a subclass from the base class, I found this problem persistence.

I think this most likely being a bug in the current version of Xcode.

Eventually I got an alternative solution: removing generic from the base class, and duplicating the base class into several classes based on my entity data types(fortunately, only a limited number of data types in my project).  I tried to make as less changes as possible in all those base classes. First, I changed generic type to a entity data type. Then each class is divided into two parts, common codes which takes majority, and entity data type specific part only a small portion of codes.

I also tried to closure block as method parameter or local data members so that my subclass will plug or assign block of codes for the closure. The example:

  1. typealias FetchRequestBlock_t = () -> NSFetchRequest<MyEntity1>
  2. class MyEntity1TableViewController : UITableViewController, NSFetchedResultsControllerDelegate
  3. {
  4.    // MARK: Properties, methods special for MyEntity1
  5.    // Block perperty to be initialized by subclass
  6.    var fetchRequestBlock : FetchRequestBlock_t?
  7.    var fetchedResultsController : NSFetchedResultsController<MyEntity1>?
  8.    // Block as parameter: plug in codes by subclass call
  9.    func setupFetchResultController(fetchRequestBlock: FetchRequestBlock_t) {
  10.      let fetchRequest = fetchRequestBlock()
  11.      ...
  12.    }
  13.    fileprivate func methodSave(entity: MyEntity1) {
  14.      // Use block var in local usages
  15.      MyEntity1.save(entity, self.fetchRequestBlock!)
  16.      self.tableView.reloadData()
  17.    }
  18.    ...
  19.    // MARK: -
  20.    // Common codes
  21.    // MARK: Propeties and methods
  22.    ...
  23. }

Here is an example of subclass to initialize block use base class method:

  1. class MyEntity1ListViewController : MyEntity1TableViewController
  2. {
  3.    ...
  4.    override open func viewDidLoad()
  5.    {
  6.        super.viewDidLoad()
  7.        let block: FetchRequestBlock_t = {
  8.          let fetchRequest: FetchRequest<MyEntity1> = NSFetchRequest(entityName: "MyEntity1")
  9.          ...
  10.          return fetchRequest
  11.        }
  12.        self.fetchRequestBlock = fetchRequest
  13.        self.setupFetchResultController(fetchRequestBlock: fetchRequest)
  14.        ...
  15.    }
  16.    ...
  17. }

With this not-perfect-solution, all my subclasses are visible in Storyboard. I hope the next version of Xcode would fix this bug.

References


Read More...