ios concepts

Questions & Answers from a Three-Part iOS interview

Part 1 - The Phone Interview

DURATION - 60 MINUTES

Some time ago I had the pleasure of interviewing with a large publishing company headquartered in midtown Manhattan. I got the first interview after an in-house recruiter found me on LinkedIn. She quickly set up a phone interview and I did some basic preparation.

The role wanted a candidate who knew both Objective-C and Swift - I figured there wouldn't be too many Swift questions since the emphasis was on Objective-C. I hadn't touched too much Swift at the time so I threw myself into the "functional" nature of Swift, the use of generics and optionals, and I made sure to write blog posts on things like copy-on-write capabilities of the language. In short - I went somewhat low level and learned about the quirks of the languages and how it differs from Objective-C.

I'm glad I did the above - the phone interview started off with plenty of comparisons between the two languages. What's the difference between Array and NSArray? An array is a struct and thus a value type whereas an NSArray is a class and thus a reference type. The interviewer and I talked about blogs we enjoy reading - when we broached the subject of value v. reference types, I discussed a playground I had written some code in to learn about the differences between the two. I talked about how the code helped me learn about copy-on-write and challenges I faced in trying to embed reference types within a struct. 

I had blogged about some of the issues we talked about and the interviewer took note of that and said he had looked at the code I wrote - I felt pretty good about that. Up until near the end of the phone call we talked about more basic things like using categories and when they're appropriate. The interview finished with us talking about issues that arise when trying to use multiple Managed Object Contexts (I didn't know the answer and he said it was totally fine) and about the Core Data stack in general. We concluded by cracking a few jokes, recommending some reading resources and saying bye. 

Part 2 - The First In-Person Interview

DURATION - 180 MINUTES

I received some good news the next day - they were impressed with my phone interview and wanted to bring me in to the office for a more technical interview. When I arrived, I was nervous as hell. I met with the recruiter I had been in touch with and was sent to wait in a conference room with a whiteboard. The principal engineer came in and greeted me, asked me if I needed anything and told me about how things would proceed. He told me he wanted to talk to me and get to know me a bit and then we'd move onto a technical challenge. He didn't really bother asking me about my background - my training and education didn't seem to matter much. He had read my blog and told me that he liked what he saw but had some questions.

He pulled out a screen print of some of my articles with the code attached - I was a little taken aback but was glad he'd thoroughly reviewed some of my material. He asked why I did things a certain way and asked me if I thought it was the best way - I had force unwrapped a lot of optionals and made some variables optional when they really didn't need to be. I explained that I had quickly written the code to just illustrate a concept and explained how I would go about doing things in a safer way - perhaps using "guard" or "if let" statements to safely optionally bind variables if they're not nil. 

He pulled out a MacBook, fired up Xcode with some boilerplate code already there, asked me if I wanted to write code in Objective-C or Swift (I chose Swift) and described a client problem for me - he took the role of a college professor and I was a developer building a product for him. He told me he wanted to show a list with a number of rows that said the number of the cell (first cell shows 1, second shows 2, etc). He was trying to illustrate the concept of infinite to his students. I asked if it was OK to show a very large number of cells before trying to work towards infinite. He said that was fine, and suggested I try 10,000. I set the numberOfRows to 10,000 and in the cellForRow... I set the cell text to be NSString(indexPath.row+1). Easy.

I told him I couldn't do an infinite number - using Int_MAX for the numberOfRows froze the app. He said that was ok and that he had another problem - he wanted to show his class how quickly numbers can grow in size when put through certain formulas. In this case, he wanted me to use the fibonacci sequence, showing the entire sequence up to an infinite number. HE WAS HOPING I WOULD TALK ABOUT PERFORMANCE IN USING THE FIB SEQUENCE SINCE THE OUTPUT GROWS EXTREMELY QUICKLY (I DIDN'T MENTION IT UNTIL I HIT A BUG). I asked him what the formula was, if he had any design stipulations, etc. He said he just wanted to show the fib number at that point in the sequence. 

I created a model class (he told me that was a good place to start). I called it FibNum and wrote an init function to take an integer and calculate the next fibonacci number recursively. Since this uses multiple stack frames, even when I was going for a high number of rows (somewhere around 5,000) and not an INFINITE number of rows, I blew the stack. This was a blessing in disguise! It allowed me to talk about bug solving and let him see how I went about tracking down issues and fixing them. He asked me the pros and cons of using a recursive solution and then suggested I cache the response of a previous recursive solution and pass it into the function (his words were "cache that sucker" and I ended up tail optimizing the recursive function). He then showed me his solution in both Objective-C and Swift - it was really complex. The Swift solution used a class that conformed to SequenceType and allowed for generics - the Objective-C code was waaaay longer and also included an iterator. He asked me if I thought his solutions were "over-engineering" - my answer was "If you know you'll be applying the same concepts to other problems in the near future, it's not over-engineering; If you're just writing code for this one time solution, I would not do that much work.". He seemed satisfied with my answer.

I was asked to wait for my next interviewer - he walked in and introduced himself. We ended up talking on a personal level for a while (sometimes I can be really talkative when I'm nervous and don't know when to shut up) - turns out we both played soccer on the same fields, were both New York natives, and went to rival specialized high schools. Good start (seriously). He gave me a piece of paper and a pen and described a problem - if given an array and a "sum" integer, return a boolean that says whether or not the array contains two numbers that can add up to the sum parameter. I was dreading this moment, mostly because I didn't expect it for some reason (certainly not with pen and paper). I had no autocomplete to help me out - this wasn't pseudocode, as I was asked to write it with proper Swift syntax. Luckily I was OK on the syntax - definitely wouldn't have compiled if I'd typed it into Xcode, but close enough. LET THIS BE A LESSON TO MAKE SURE YOU KNOW API METHODS FOR FOUNDATIONAL DATA STRUCTURES.

I reasoned out loud that I'd need to iterate through the entire array at least once (worst case) and that the "sum" integer was there for a reason. He told me that there was no extraneous information in the problem - this was good. We were having a back and forth about the problem, possible solutions and pitfalls I could avoid in terms of optimization. First I tried using a nested loop - then I thought about lookup times and knew I'd be doing a lot of checking (for the current element, if we've come across the counterpart before, return TRUE). I know hash tables have constant lookup times, and I mentioned that to him - he encouraged me to use a dictionary and told me I was on the right track. I had an hour to solve the problem, and at this point I was probably 25 minutes in.

I finally figured out the solution after going back and forth - all told it was around 45 minutes (too long in my opinion but I was nervous). He told me I couldn't have optimized it more if I'd tried, that there were a few other solutions that offered the same runtime, and that I did a solid job. I was pumped that I'd solved it, despite how simple it was - writing code in front of an audience is never easy. We talked for another 20 or so minutes - we had a great rapport and it was clear we got along. He showed me some of the stuff he'd been doing for company and asked me if I had any project I wanted to show him. I GOOFED and didn't have anything on my iPhone - I quickly downloaded an app I built from the app store and showed it to him. I told him that even though it was really simple, I faced my fair share of challenges with writing my own caching system (amongst other things). We talked about APIs I used, frameworks and how long it took me to build. I explained how I came up with the idea for the project and how I got started with the designs. We ended it there and shook hands as he went back to his desk to continue with his work. 

Part 3 - Meeting the Team

DURATION - 180 MINUTES

A day went by and I was anxious to hear back - I kept going over where I went wrong in my head and kept thinking about what they'd hold against me. My phone buzzed with an email from the recruiter - they'd chosen me as a final candidate and wanted me to meet the team. I went back in a few days later and headed back up to the engineering floor. I was greeted by the principal engineer and we hung out for a few minutes before I was led to a conference room on the other side of the floor - I was greeted by a Software Engineer, the lead QA automation Engineer and the Scum Master for the team. They went over things that hadn't been asked before - my project experience, team experience, agile experience, background, etc. It was much less fun than the other parts of the interview!

The Software Engineer asked me iOS specific questions (such as the use of delegates) - I told her it was just a design pattern that allowed view controllers to communicate with one another. I also told her they could be used for callbacks. We talked about controller communication patterns - delegates, NSNotificationCenter and KVO. To sum up my answers to her, take a look at the table below -

She didn't probe me too much further on iOS stuff. The meeting was OK - trying to impress three people at once was harder than dealing with people one-on-one. When I was alone with fellow developers, I could get a feel for their personality and either talk more or less, more loudly of softly, bullshit a little more or less, etc. With three people at once, a candidate just can't do that. I got a pretty bad vibe from the QA Engineer but got on well with the other two team members.

The principal met back up with me and asked me to go on a walk with him - we went to the lobby and looked at some of the art on the walls and talked about random things - our childhoods, passions, favorite buildings in New York, things like that. He brought me to other departments in the building (the company owns the entire building) and let me see cool stuff being worked on that wasn't related to Software Engineering. Finally we went to the top floor and sat on plush leather chairs - I was waiting to meet the department head and the director of a partner department. They were finally ready for me - they were both very senior. After the conversation flowed for a while, they figured out that I'd looked them up on LinkedIn and we had a laugh about it - the director looked at the department head and said "He's clearly done his research". We talked about the challenges the company faces, I asked them about difficult situations they've been in - the meeting was mostly for ME to question THEM.

The one question that really stuck with me that they asked was "Are you more of a product guy or a development guy?". Without a hitch I told them I'm a product guy - if I fundamentally disagree with a feature being introduced or something of that nature, I don't care how junior I am, I'm going to bring it up. I think they liked that, but I'll never know because I never heard back from them. I contacted the recruiter several times and never got a response. I called her and left a voicemail - I got an email from her almost immediately saying they went with the other candidate. Unfortunately, I didn't get any feedback. It was down to me and (most likely) one other candidate - whatever the outcome, that was a good feeling. I learned a ton just by talking to so many different, deeply intelligent people.

All told, I spent 7+ hours interviewing with them. I was upset at the outcome, not so much that I didn't get the job as the fact that they never contacted me to tell me the outcome. It was a fantastic learning experience regardless - I hope it my talking about it helps others. Some parts were far more intense than I expected (having the code in my blog looked at for mistakes, for example). Always prepare for a thorough vetting process when it comes to being interviewed. Be prepared for anything and you'll be surprised by nothing. 

Let | Var | Reference & Value Types

This is about as short a post as it can get! For a deeper look at reference & value types in Swift, see my other post on the topic.

I see a lot of questions on SO that discuss mutability in Swift. Let's go over some basic points about mutable (and immutable) objects in Swift:

  1. Creating a let variable enforces immutability - that variable cannot point to a different piece of data. You cannot change the data at that memory adddress and you also can't point the variable to a different memory address. Say you have let a = 5 and let b = 8.
    You cannot then say a = b nor can you say a = 7.
  2. If you have a var variable, all that means is that your variable can be reassigned - that could mean that the value it points to changes or that you point it to a different memory address.
  3. Structs, enums and other Swift value types (primitives such as Int, Double, String, Array, Dictionary and Set) dispay deep copy semantics. This means that if two variable are pointing to the same memory location, changing the data of one won't change the data of the other. A deep copy of the changed variable will be made and it'll point to its own separate memory address. Each value type instance holds a unique reference to its data. The ability of values to point to the same memory address until they no longer have the same data is due to Swift's Copy-On-Write optimization.
    //Assume Student() is a struct that initializes a student with an age.
    let a = Student(25)
    let b = a
    print(a.age) //25
    print(b.age) //25
    a.age = 35
    print(a.age) //35
    print(b.age) //25
    • As you can see above, changing a property value for a won't cause a change in b. Additionally, both will point to the same memory address until we change a.age - then a deep copy is made, passed to b, and the two variables point to different places in memory
  4. With reference types (often classes) multiple instances can share a single copy of data (they can point to the same memory address). If we pass a class instance to a function, for example, we're actually passing in a reference to the memory address of the object.
    //Assume Student() is a class that initializes a student with an age.
    let a = Student(25)
    let b = a
    print(a.age) //25
    print(b.age) //25
    a.age = 35
    print(a.age) //35
    print(b.age) //35
    • Since we're dealing with reference types, a shallow copy is made, meaning both instances are pointing to the same memory address. A change to data in one will result in change to data in the other.
    • With reference types, it's easy to leave around many reference to an object. Changing data of one reference will change the rest, and this can result in unexpected behavior and leads to bugs. Immutable types are considered safer and can lead to more bug-free code.

Data Structures in Swift!

I recently added a README file to my GitHub repo to add a bit of clarity to anybody checking out the code. It includes sort and search algorithms and some basic data structures. I've covered linked lists previously on this blog, so the next few posts will be on the various algorithms I've implemented in Swift.

To finish up the series, I'll be doing a (possibly multi-part) post on Binary Search Trees - they're easily my favorite way to review recursion (or learn about it for the first time, if you're new to programming). I've written a BST in two ways - with a "node" data structure that is used to create a tree and without a node structure. The latter version uses itself as structure, meaning that each node is treated as an independent subtree. The logic is extremely different between the two, and although I would personally recommend writing a BST without a node structure, it's still good practice to review.

Swift Algorithms & Data Structures

The various files make use of Swifty features, all of which can be reviewed by looking at the README file on the repo. This includes preconditions, subscripting, extensions, sequencetypes and computed properties. Enjoy!

When To Use a Category

During a phone interview I had with Web M.D. recently (it started as a lightly technical interview and then I was thrown a curve ball - see the bottom for more) I was asked to define a category and explain when I'd use one. I gave an answer, but more importantly, I gave my answer. The interviewer responded with his own answer, and from the way he explained it, that was the answer he was looking for.

In reflecting back on that interview question, I would disagree with that my interviewer said about categories. He told me that "they should primarily be used when you don't have access to the source code of a class". While I agree that categories provide a useful way to extend functionality of classes that we don't have the source code for, there is no single right answer to this question. Categories can be useful in other ways, and can be less useful in just as many ways. In disagreeing with my interviewer (which I didn't express during the interview, although thinking back on it, I definitely should have), I'll list some of my ideas here:

  1. If you need to add functionality to a class but don't have access to the source code, use a category.
  2. If you need to add data to an existing class (properties or ivars), don't use categories, and instead just subclass and make use of inheritance. I say this because categories don't support adding properties or ivars (unless making use of associated objects, which I won't get into here). 
  3. If you have a totally oversized, bloated implementation file, consider breaking up the class into specific units by using categories
  4. If you want to provide some pseudo level of encapsulation, you can add "private" methods to a category and only selectively import that category filename. However, this isn't the only way to mimic encapsulation (you can use extensions, for example), so take this one with a pinch of salt.
  5. If you have instance of a specific class littered throughout your source code and suddenly need to add functionality to that class, use a category. Your alternative would be to create a subclass, add new methods, and then replace all of your instance with the new subclass. Inefficient, to say the least. This works to your benefit in (sort-of) reverse - if you have instances of a subclass somewhere, adding methods to the parent class through a category trickles down to the subclasses.  
  6. If you absolutely, 100% do not need to create a parent-child object hierarchy, use a category

The interview question I got that really shut me down was about graphing a curve using data points representing pollen levels. The interviewer was explaining to me that there are certain things a seasoned developer with a computer science degree would know how to do that I wouldn't (I have a degree in Environmental Science, which involved high level math and science coursework). To be totally honest, I think that even if I had majored in CS, I wouldn't have remembered anything about plotting points using bezier curves and the De Casteljau algorithm. Unfortunately, I was relayed the information that although I seemed to be a solid candidate, I wasn't technically experienced enough to join the team and hit the ground running. On to the next.

NSUserDefaults, NSKeyedArchiver & NSCoding (Updated)

(Update) Apple documentation can be confusing - the serialization and archiver programming guide is one such example. There seem to be contradictory statements throughout; the guide doesn't really explain the way archivers, serializers, defaults databases and encoders work with each other. A little bit of further digging on the developer website helped me find a few scattered articles about these terms that were written more recently than the programming guide - I used this information collectively to help elucidate how these APIs and storage methods are related to each other. 

 

NSKeyedArchiver/NSCoder

Objects in memory can’t be passed around to other programs - they contain things like pointers that are only valid in the context of your current virtual memory space (which exists for each process in a multi-tasking OS). Serialization is the process of converting the in-memory representation of an object into a stream of bytes than can be passed between programs. In Cocoa and Cocoa Touch, NSKeyedArchiver is an API that we can use to serialize an object graph. 

If we want an object to be archivable, it must adhere to the NSCoder protocol methods, which encode and decode the objects. encodeWithEncoder and initWithEncoder are the two required protocol methods - the former tells the archiver how to serialize an object into bytes, and the latter tells the archiver how to transform the serialized data in an object. The NSCoding protocol methods go over objects one-by-one and encode both instance value and class type of the current object - the encoded objects are then archived.

You can then write your archive to data (turn the stream into NSData or write directly to a file path (if no path is specified, it goes to the root directory of the application). You can even archive to data and then write to NSUserDefaults (or archive to data and THEN write to a disk). It all just depends on your needs as a developer.

To archive to a data object, use the following code snippet as a guideline:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.array];

We can then either write to NSUserDefaults or directly to disk - to specify a file path where we want to store our archive, we can do something like this:

NSString *filename  = [NSHomeDirectory() stringByAppendingString:@"/Documents/app.bin"];

[data writeToFile:filename atomically:YES];

If an object graph doesn’t need to be serialized, and instead, an object hierarchy needs to be stored, we can skip archiving and go directly to NSUserDefaults (assuming we aren’t talking about custom objects). 

 

NSUserDefaults

NSUserDefaults provides a way to store small amounts of information, largely user preferences, in a “defaults database”. These parameters are called defaults because they’re commonly used to determine an application's default state at startup. At runtime, unboxing this defaults database can be accomplished with NSUserDefaults; in order to avoid constantly reading from that database throughout the lifecycle of an application, values can be cached. A method called synchronize is automatically called every so often, keeps the cached data synced with the defaults database.

NSUserDefaults can be used to store NSData, NSStrings, NSNumbers, NSDates, NSArrays or NSDictionaries. The call to NSUserDefaults is a thread-safe singleton that we use to set up a defaults database. We can also include a check when the app is running to see whether we have a database currently being used - if we do, use it, if not, create an instance using the singleton. 

In saving to our sharedInstance (the singleton) of NSUserDefaults, we can use the setObject: forKey: method, which takes any of the NSUserDefaults compatible objects and stores it with a key for quick lookup. We then synchronize to make sure that our cache is up to date. When retrieving, we call our sharedInstance and grab objectForKey:@“” and pass the key we know is associated with the object. 

*NOTE: If we want to store custom objects in NSUserDefaults (we may have such a small amount of data to persist that this makes sense), then we must implement NSCoder protocol methods and archive the objects. 

 

Conclusion

Trying to store custom objects? 

Implement NSCoding protocol methods to create a byte stream and use NSKeyedArchiver to create an archive of the data. If custom objects aren’t part of the equation, you can just go straight to NSUserDefaults.

 

So you’ve created an archive of encoded custom objects…where and how can you store them?

You can write your archive to an NSData object, and then either write that to disk or store it in NSUserDefaults. Alternatively, you can just write your archive directly to disk, either by specifying a file path or using the default one (the root directory of the application).