Alexander Sergeev's blog

I have nothing to do with iOS Livejournal app 2.0

I have absolutely nothing to do with new official LiveJournal application for iOS (available here https://itunes.apple.com/en/app/livejournal/id383091547?mt=8) – it is written from scratch by guys whom I don’t know.

But wait before update! Read reviews and decide if you want it or not. I know how hard is to deal with LiveJournal’s content, so I appreciate efforts of the developer of this new version.

However, there are things that I don’t like at all:

  1. New version is slow. It uses UIWebView to draw a friendspage by contrast with previous version, which was build on a native components. I consider this as a step back. On my iPad 2 friendspage scrolling is very laggy and extremely irritating.
  2. Raw&undone. Look at the following screenshots:photo 1 photo 2

Conclusion: application is raw and lost some functions, but features Ratings. Update if you are brave and have a newest device.

Share

Talerka iOS 7.0 look&feel support

I did not only enjoy vacation during winter holidays. I worked hard on Talerka iOS application and we did release a new version which supports iOS 7-look and also features side menu.

The good things about side menu that it allows you to use much more space for content:

iOS Simulator Screen shot 18.01.2014, 16.42.16

more space on recipe screen – easier to read

iOS Simulator Screen shot 18.01.2014, 16.42.35

More space on search screen gave as a possibility to increase the heigh of table’s rows; thus, it became easier to tap on them

 

The bad thing is that I’ve broken (unintentionally) a iOS5 support. The update is on the way – after it becomes available the application will support all iOS vesrions from 5.0 to 7.0.

Share

Atomic getter&setter with GCD

I’ve faced a very interesting question recently: how to write an atomic getter and setter with a copy policy by using GCD (without ARC)?

Although I could not give an answer immediately, but later on I digged a bit and found the following obvious solution based on dispatch_sync (http://www.fieryrobot.com/blog/2010/09/01/synchronization-using-grand-central-dispatch/):

- (void)setFoo:(NSObject *)newFoo {
dispatch_sync(_lockQueue, ^{
NSObject * copied = [newFoo copy];
[_foo release];
_foo = copied;
}
}

- (NSObject *)foo {
__block NSObject * result = nil;

dispatch_sync(_lockQueue, ^{
result = [[_foo retain] autorelease];
}
return result;
}

Outcome: even though you know API you still may think in old terms.

Share

Dealing with AdressBook on iOS 6.0: ABAddressBookRequestAccessWithCompletion

As you know, we have to use ABAddressBookRequestAccessWithCompletion call in order to get access to an Address Book in iOS 6.0 and higher:

void ABAddressBookRequestAccessWithCompletion (
ABAddressBookRef addressBook,
ABAddressBookRequestAccessCompletionHandler completion
);

(from iOS developer documentation)

It is not oblivious that the completion handler will be called on the arbitrary queue:

Discussion
Use this function to request access to address book data. This call will not block while the user is being asked for access, allowing your app to continue running. Until access has been granted, any address book references your app has will not contain any data, and any attempt to modify data will fail with an error type of kABOperationNotPermittedByUserError. The user is only asked for permission the first time you request access. Later calls use the permission granted by the user.

The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation.

so you have to synchronize the execution somehow.

The following approach, introduced by Josephus here does not work for me: it locks the application completely if called on a main thread – it looks like the blocking of a main thread interferes with a system Address Book alert. Thus, we have decided to use the following code to solve the problem:


if (isIOS6orHigher) {
// System alert for iOS >= 6.0
ABAddressBookRequestAccessWithCompletion(addressBook_, ^(bool granted, CFErrorRef error) {
// We are on arbitrary queue
dispatch_async(dispatch_get_main_queue(), ^{
// We are on main queue
[self processAccessResponce:granted];
});
});
} else {
// Custom alert view for iOS < 6.0
...
}

 

Share

Valgrind and iOS

I’m working in Mail.ru now. My primary task is to fix memory management and concurrency problems in a complicated project written by a team of six people.

I had a nice experience using Valgrind tool on Linux system, so I started to investigate to possibility of using Valgrind with iOS.

The results are:

  • Valgrind works only on Mac OS X 10.7 Lion (Mac OS X 10.8 Mountain Lion is not supported yet).
  • Valgrind works only in simulator. There is no way to run it on a iPhone/iPad device.
  • Valgrind works only with iOS 5.0. iOS 6.0 is not supported.

In order to run valgrind you should:

  1. Get an Mac OS X 10.7 Lion. Fortunately, we have an old MacBook Pro with OS X 10.7 for testing purpose.
  2. Install XCode. XCode 4.5.2 works fine.
  3. Obtain a copy of valgrind‘s source from http://valgrind.org/downloads/. I took the svn version, but mac ports version works as well.
  4. I used this trick  to launch an application under valgrind in the simulator. I didn’t need to tack memory leaks, so I’m using –leack-check=no and –dsynutil=yes options.

This solution works fine for Mac OS X applications, too; however, make sure you are using Mac OS X 10.7. Looks like there is a difference in the threading model between Darwin 11 and Darwin 12; valgrind error message says:


valgrind: m_syswrap/syswrap-amd64-darwin.c:460 (void wqthread_hijack(Addr, Addr, Addr, Addr, Int, Addr)): Assertion 'VG_(is_valid_tid)(tid)' failed.

 

Share

Some thoughts on NSCache and NSDictionary

I have to work on a new projects with multiple concurrency issues.

During debugging this application I discovered and interesting thing about NSCache: it looks like NSCache retains and autorelease objects before pass them out (but NSDictionary does not):

// A possible objectForKey: implementation:
- (id)objectForKey:(id)key {
id obj = [self _getObjectForKey:key];
return [[obj retain] autorelease];
}

Why?

Apple declares NSCache a threadsafe class.

Consider the following situation:


NSCache * cache = ...;
// Thread A:
NSString * cachedString = [cache objectForKey:key1]; // point  A1
// do smth with a string:
NSLog(@"String value is %@", cachedString); // point A2

// Thread B:
 [cache removeObjectForKey:key1]; // point B1

 

Consider the processor executes  A1, then switches to B1 and returns to A2 after that.

In such a case we will have an invalid pointer to cachedString at point A2 (if we did not retain cachedString somewhere else).

But if objectForKey returns a retained-autoreleased value it is OK – the pointer is still valid (and it will remain valid until autorelease pool drain).

 

You may wish to retain-autorelease values while dealing with NSDictionary:

 

NSMutableDictionary * dict = ...;
// Thread A:
NSString * value = nil;
{ Lock lock(mutex);
value = [[[dict objectForKey:key1]retain]autorelease];
} // critical section ends
// do smth with a string:
NSLog(@"String value is %@", value);

// Thread B:
[dict removeObjectForKey:key1]; // point B1

 

 

Share

Class cast

If you want to get rid of all these nasty isKindOfClass:

if ([something isKindOfClass:[A class]]) {
   A * aPtr = (A*)something;
   // Do things with aPtr...
}

you may wish to use class_cast:

// class_cast.h file

// class_cast returns pointer to a specified type or nil

template<typename T> T * class_cast(NSObject * o) {
  if ([o isKindOfClass:[T class]]) {
    return (T*)o;
  }
  return nil;
}

Somewhere in your project (make sure you are using a C++ compiler – don’t forget to use “.mm” file extension):

// Foo.mm file
// Templates are available only in C++ - make sure to use ".mm" extension.

#include "class_cast.h"

...

if (A * aPtr = class_cast<A>(something)) {
   // Do something with aPtr
}

// or even

[class_cast<A>(something) doSomething];

// (cause we can send messages to nil)

I’m not sure if I did invent this trick or I snatched it somewhere; anyway, you may use it. For free 😉

Share

File is universal (3 slices) but does not contain a(n) armv7s slice error

If you got an error while linking that looks like:

file is universal (3 slices) but does not contain a(n) armv7s slice error

make sure your static libraries do support armv7. If they do not

  1. update them
  2. if no update available, you can force use old architectures by set up both Architectures and Build active architecture only settings in project settings:
Share

Talerka app: some stats

Talerka logo

We collected some Talerka iOS application stats via Google Analytics.

Visits

Looks like people like to cook on Sundays)

Visits

Languages

Russian, USA and Ukrainian users make the 91% of audience.

 

USA visitors

Talerka is not popular in USA as much as in Russia; the grow is slow and stable

 

Share

iOS application custom localization

Apple do not recommend to allow user to switch application language another way then switch it in system settings either I do. Sometimes (rare) it makes sense to allow user to switch the language of the specific application. Anyway, before do it – please, think twice; it’s really goes against Apple’s language policy, so benefits from such a decision should overcome the drawbacks.

So, if if you want to switch your application language, use the following code:

// Use "nil" to fallback to system settings
void loadCustomLocalization(NSString * locCode) {
  NSString * appleLanguages = @"AppleLanguages";
  if (locCode) {
    [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:locCode] forKey:appleLanguages];
  } else {
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:appleLanguages];
  }
  [[NSUserDefaults standardUserDefaults] synchronize];
}

Call loadCustomLocalization() before UIApplicationMain() call, for example:

void loadLocalization() {
  const int result = [[[NSUserDefaults standardUserDefaults] stringForKey:@"language"] intValue];
  NSString * language = 0;
  switch (result) {
    case 1: language = @"ru"; break;
    case 2: language = @"en"; break;
  }
  loadCustomLocalization(language);
}

int main(int argc, char *argv[]) {
  loadLocalization();
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  return retVal;
}

My application has three values for language setting in application settings menu.

User may choose between system language, Russian or English.

But remember: very often this is not a kind of freedom you want to give to your customers; the real freedom is to provide all application content on all possible languages (but it costs a lot).

Share



Forgot?
Register
Facebook login by WP-FB-AutoConnect