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...

Friday, October 21, 2016

One-to-Many Relationship and Subquery

Recently I have to deal with a case in my app. In a one-to-many relationship, I need to define a predicate for many's element to be checked. Here taking Person as a simple class for example, as in following swift class:

  1. class Person {
  2.  var name: String?
  3.  var birthYear: Int?
  4.  var friends: NSSet?
  5. }

the Person class has three properties, name, birthYear, and friends. I use CoreData model to define Person entity. What I need to find a person whose has friends(at least one) and those friends has to meet certain criteria.

For example, find all person who has friends and at least one of friends' birth year is greater than 2000.

Soon I found a solution: I need to build a predicate with function Subquery. For above example, my fetch request can be built in following codes:

  1. let request = NSFetchRequest<Person> = NSFetchRequest(entityName: "Person")
  2. let predicate = NSPredicate(format: "SUBQUERY(friends, $x, $x.name CONTAINS 'David' && $x.birthYear > 2000).@count > 0")
  3. request.predicate = predicate

Subquery Function


The Subquery function is rarely mentioned, however, it is a very powerful to resolve some complicated one-to-many relationship.

The function takes three parameters:

  1. A collection
  2. a variable, and
  3. a predicate

Basically, the function will loop through the collection to verify each element if the predicate condition is true, and all the matched elements will be returned back as a collection or empty if none met. For above example case, the Subquery will enumerate each person in friends collection, return a collection of persons whose name contains "David" and whose (David's) birth year is greater than 2000.

For this kind or more complicated relationships, this function is very concise and powerful. For example, relationship may go one more level deeper: friends' friends whose name contains "Lisa" and whose (Lisa's) birth year is greater than 2005. For this case, nested Subquery can be used. That is, a Subquery contains another Subquery to verify one more deep relationship:

  1. let request = NSFetchRequest<Person> = NSFetchRequest(entityName: "Person")
  2. let predicate = NSPredicate(format: "SUBQUERY(friends, $x,
  3.  SUBQUERY($x.friends, $y, $y.name CONTAINS 'Lisa' && $y.birthYear > 2005).@count > 0).@count > 0")
  4. request.predicate = predicate

The Subquery function can further use ALL, ANY or NONE operator in the third predicate parameter on collection property for more complicated predicate conditions.

  1. SUBQUERY(friends, $x, $x.friends != nil && ALL $x.friends.birthYear > 2000)
  2. SUBQUERY(friends, $x, $x.friends != nil && ANY $x.friends.birthYear > 2000)
  3. SUBQUERY(friends, $x, $x.friends != nil && NONE $x.friends.birthYear > 2000)

Operator ALL, ANY or NONE applies to the left collection for all of, at least one, or none of element in the collection. In this way, frinds.friends's property(birthYear) can be added to complete the predicate condition.

As you can see, in all above codes, Subquery is used in NSPredicate, which is attached to NSFetchRequest to fetch entity objects in CoreData context. To test Subquery in various cases, I have to find some way to add objects to CoreData database. In most cases I had to run my app to do my tests. That's very time consuming and painful process.

Further investigating into Subquery, I found that Subquery used in NSPredicate is actually based on NSExpression. Actually, Subquery can be tested by using NSExpression with various samples of collections. My next blog will discuss this very interesting topic.


References



Read More...

Thursday, October 13, 2016

Localize Plurals in iOS

Apple emphasizes internationalization and localization since its start of iPhone development. This is actually a very wide and difficult topic in some areas. Plurals in various languages make it really hard to code. In iOS, a solution is provided to get localized plural string and it is very nice and handy.

You can find the solution in many articles if you google it. Basically, the solution is based on a Plist resource file and couple of functions.

By default, the resource file is named as Localizable.stringsdict. Another file should be created as well: Localizable.strings along side even empty.

In the current Xcode 8, there is no stringsdict file type when you add from New File... What you can do is to use Plist file type, and then remove plist extension from the file name. In this way, the XML file can be viewed in ASCII Property List or a hierarchical format.

Plist File Structure


The property list is based on dictionary or dict node to define hierarchical structure. Each dict contains a list of key-value property. Each value can be a string or dict (children set of key-value list). The reason to use dict is that each key within a dictionary has to be unique.

This resource file contains three levels of dict. The top dict contains a list of resource keys. Each key can be used by function NSLocalizedString to retrieve a format string for the resource file.

  1. <plist version="1.0">
  2.    <dict>
  3.        <key>number_of_files</key>
  4.        <dict>
  5.            ...
  6.        </dict>
  7.        <key>working hours</key>
  8.        <dict>
  9.            ...
  10.        </dict>
  11.        <key># of entities</key>
  12.        <dict>
  13.            ...
  14.        </dict>
  15.    </dict>
  16. </plist>

The second dict level contains a list of key-value pairs. The first pair is used to a format for a plural string: by key NSStringLocalizedFormatKey, and its value as the format string. For example:

  1.           <key>NSStringLocalizedFormatKey</key>
  2.           <string>Total: %#@files@, %#@subs@</string>
The string value normally contains one or more variables. Each variable is marked by %#@ as preceding, and @ as ending. The variable should contain a numeric value, therefore, the result of the variable would be a localized plural string.

The remaining key-value pairs define plural rule for each variable. Each variable is a key, and the paired value defines plural rule by the third level of dict. For example, the following is English plural rule for the variable files.

  1.           <key>files</key>
  2.           <dict>
  3.               <key>NSStringFormatSpecTypeKey</key>
  4.               <string>NSStringPluralRulType</string>
  5.               <key>NSStringFormatValueTypeKey</key>
  6.               <string>d</string>
  7.               <key>zero<key>
  8.               <string>%d file</string>
  9.               <key>one</key>
  10.               <string>%d file</string>
  11.               <key>other</key>
  12.               <string>%d files</string>
  13.           </dict>
  14.           <key>subs</key>
  15.           <dict>
  16.               ...
  17.           </dict>

The first two key-value pairs in the third dict level are as-they-are standard, no change. The remaining key-value pairs are for language specific plural rules. For English, the plural rule has three categories: zero, one and other.

Refer to the resources for language plural categories.

Functions for Plural Strings


With above Plist file defined, the localized plural strings can be retrieved by following codes:

  1. let numFiles = 3
  2. let numSubs = 1
  3. let format = NSLocalizedString(" ", comment: "")
  4. let text = String.localizedStringWithFormat(format, numFiles, numSubs)

The first function NSLocalizedString gets a localized format string from Plist, and the second function localizedStringWithFormat retrieves corresponding plural string based on numeric values.

I have written a simple function to get localized plural string by extension to String.

  1. extension NSString {
  2.  static func localizedStringForPlurals(formatKey key: String, comment: String = "", args: CVarArg... ) -> String
  3.    {
  4.        let format = NSLocalizedString(key, comment: comment)
  5.        let result = withVaList(args){
  6.            (NSString(format: format, locale: NSLocale.current, arguments: $0) as String)
  7.        }
  8.        return result
  9.    }
  10. }

It is much simple to use the extension function in my project:

  1. let text = String.localizedStringForPlurals(formatKey: "number_of_files", comment: "Total: # of file, # of sub", args: numFiles, numSubs)
  2. // or
  3. let text1 = String.localizedStringForPlurals(formatKey: "number_of_files", args: numFiles, numSubs)


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...

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...

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...