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

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

Wednesday, April 06, 2016

Convert ObjC to Swift (5)

My project conversion from ObjC to Swift is in good process. I think that I gained great knowledge and experiences about Swift during the process. I started from simple classes.   My basic principle is to keep ObjC codes as-they-are as much as possible. In another word, I would not make changes in the remaining ObjC codes, while part of ObjC being converted to Swift.

There are many ways to convert ObjC elements to Swift. During the process, I gradually learned many better ways or tips. In this article, I will talk about constants, macros, enum, private data and methods, and selector from ObjC to Swift.

Constants

I have many constants defined by #define in ObjC. If those constants are private to the class in .m, I would use Swift struct in module level as private data members in .swift file.

For example, the following are some constants in a .m file:

  1. #define kEntity @"Entity"
  2. #define kEntityChild @"EnityChild"
  3. @interface MyEntity
  4. + (Entity* ) createEntityByName:(NSString *)name
  5.      inManagedObjectContex:(NSManagedObjectContext *) moc {
  6.    Entity* entity;
  7.    entity = [NSEntityDescripton insertNewObjectForEntityForName: kEntity
  8.      inManagedObjectContext: moc];
  9.    entity.name = name;
  10.    return entity;
  11.    }
  12. ...
  13. @end

The converted .swift codes are:

  1. private struct MyEntityConstants {
  2.  let kEntity = "Entity"
  3.  let kEntityChild = "EnityChild"
  4. }
  5. class MyEntity : NSObject
  6.  static func createEntityByName(name : String,
  7.      inManagedObjectContex moc: NSManagedObjectContext) -> Entity {
  8.    let c = MyEntityConstatns()
  9.    let entity NSEntityDescripton.insertNewObjectForEntityForName(c.kEntity,
  10.      inManagedObjectContext: moc) as! Entity
  11.    entity.name = name
  12.    return entity
  13.  }
  14. ...
  15. @end

If some #define constants are used in other ObjC codes, I would either move them to my ObjC2SwiftHelper.h file. Or I prefer to add a method to my Swift class to encapsulate constants as private in the same .swift file.
Note
There is no #define type in Swift. You could use let to define constants in Swift. I prefer to use struct to group them as private constants together.

Here I use a local constant based on struct within the method. This will take a memory on heap and will be released on exit. I think this is better than memory on stack for #define constants.

Macros


In ObjC or C, #define can be used to define a macro (function call-like), which is not available in Swift. I have two very useful macros to get calling context class and method names.

  1. #define CALLER_SELECTOR_NAME NSStringFromSelector(_cmd)
  2. #define CALLER_CONTEXT       NSStringFromClass([self class])
  3. ...
  4. - (MyLogger*) logger {
  5.    if (!_logger) {
  6.        _logger = [MyLogger instance:CALLER_CONTEXT];
  7.    }
  8.    return _logger;
  9. }
  10. ...
  11. - (void) viewWillAppear:(BOOL)animated {
  12.    [self.logger debug:
  13.     ^{
  14.         return [NSString stringWithFormat:@"'%@' is called!", CALLER_SELECTOR_NAME];
  15.     }];
  16.    ...
  17. }

I found equivalent ways in Swift to get class and method names.

  1. // module level logger constant. Use String(ClassName) to get class name.
  2. - private let logger = MyLogger.instance(String(MyClass))
  3. ...
  4. class MyClass : NSObject {
  5.  func viewWillAppear(animated: Bool) {
  6.    logger.debug() {
  7.        //Use #function to get calling method name
  8.        return String(format: "'\(#function)' is called!"))
  9.     }
  10.    ...
  11.  }
  12.  ...
  13. }

Use Swift enum in ObjC


During my conversion, I have some enum types in Swift, but they are still used in some other ObjC codes. At first, my Swift enum types are not available in in ObjC, even they have the same enum types (Swift enum raw value as Int). There is a way to resolve the issue, by pasting enum name to the front of each enumerator names as in a Stackoverflow QA.

I prefer try to keep ObjC codes as much as possible as-they-are. My temporary solution is to copy ObjC enum definitions to my ObjC2SwiftHelper.h file. As a result, the ObjC enum types are still available as-they-were, and the converted Swift enum types are only used in Swift codes.

Private data members and methods


In ObjC, all public data members and methods are exposed to outside by using .h file. In .m file, you can add more data members and methods, which are not published to outside. In theory, they are still available to outside if you know how to call they correctly. This is a way to add private data or methods to ObjC class.

In Swift, you can use private to hide data members and methods completely. By default, all data members and func are public. I could add data and func in a class with private restriction. However, I prefer to move all private members to the module level, outside of a class. In this way, all the members within the class are public and it is much simple and clean.

Selector Support in Swift


According to the information of a Stackoverflow QA (as in reference), Selector type defined in Swift is different from that in ObjC. Therefore, even Selector type is supported in most cases in Swift by using #selector(ClassName.methodName), but property setter has to use another way (Selector(setFoo:)).

I like to use #selector() than Selector(), because compiler can verify the first case, not the later one. I find a way to avoid to reference to property setter by Selector: add a func in Swift class and call the setter within the func. In case I have to use Selector to the property setter, I use #selector() with the func.

For example:

  1. class MyClass : NSObject {
  2.  var entity : Entity // property
  3.  func setEntity(obj: Entity) {
  4.    self.entity = obj // call setter to set property value
  5.  }
  6.  ...
  7. }
  8. // in another Swift module
  9. document.save(entity, notify:entity, withSelect:#selector(MyClass.setEntity(_:)))

References


Read More...

Friday, April 01, 2016

Convert ObjC to Swift (4)

In Swift, constants are indicated by let, which means value would not be changed, while variables are indicated by var, which will be changed later on. This is a very efficient way to optimize codes and to enhance memory management. The concept is very simple and I had no problems in most cases. I got several times by giving hints to convert variables to let when they are not changed. I like this.


Variable Parameters in Swift func

In Swift func, parameters by default are constants, even they are collection objects. I had a case that I have to make some changes after my codes converted from ObjC to Swift.

For example, the following method in ObjC takes NSMutableDictionay as parameter and a new object is added to the dictionary:

  1. - (void) encodeObject:(id<NSCoding> object
  2.    withKey: (NSString *) key
  3.    toDirctionary: (NSMutableDictionary *)wrappers {
  4.    NSFileWrapper *aWrapper;
  5.    ...
  6.    [wrappers setObject:aWrapper forKey:key];
  7. }

Note
The parameter is a type of mutable dictionary. As a result, an object can be added to the dictionary within the method.

However, the default parameter would generate a compile error in Swift codes. It complains that the parameter is a let constant! I have to change the parameter with inout attribute to make the parameter as variable, not a constant.

  1. func encodeObject(object : AnyObject,
  2.    withKey key : String,
  3.    inout toDirctionary wrappers : [String : NSFileWrapper]) {
  4.    let aWrapper = NSFileWrapper(...)
  5.    ...
  6.    wrappers.updateValue(aWrapper forKey:key)
  7. }

Throw Errors


To convert exception/errors throw from a method in ObjC, the syntax of func in Swift is much more clear and simplified.

For example, the following method in ObjC includes a possible error generated from the method call

  1. @implementation MyClass
  2. ...
  3. - (id) mapToSomethingForName:(NSString *)aName error:(NSError *__autoreleasing *)outError {
  4.  ...
  5.  }
  6. @end

The corresponding Swift codes are as follows.

  1. enum MyClassError : ErrorType {
  2.    case InvalidContent
  3.    case EmptyContent
  4. }
  5. class MyClass {
  6.  func mapToSomethingByName(aName: String) throws {
  7.    if (...) { // Invalid cases or use guard (...) else {...}
  8.      throw MyClassError.InvalidContent
  9.    }
  10.    ...
  11.  }
  12. }

Note
I added a struct for error types at the beginning and throw errors in the func in case of invalid.

To handle errors in Swift is much clean and simple. To ignore errors thrown, the following is an example.

  1. let x = try? instance.methodWithErrors()
  2. if x == nil {
  3.  ...
  4. }


To catch errors, the following is another example.

  1. do {
  2.  let x = try instance.methodWithErrors()
  3.  ...
  4. }
  5. catch let error as NSERROR! {
  6.  logger.debug(){
  7.      return String(format: "error: \(error.localizedDescription)")
  8.      }
  9. }

References


Read More...