Rakuma — Leading iOS for Rakuten’s C2C Marketplace

Role: Lead Application Engineer, Rakuten — 3 iOS engineers, within a 10-person product team (2 Android, 3 web, 2 backend) 

Timeline: Nov 2015 – May 2018 

Markets: Japan and Taiwan 

Stack: Objective-C, Swift 3, UIKit, Core Data


Context

Rakuma was Rakuten’s flea-market app: list a second-hand item in a few taps, sell it, get paid safely. The strategy was explicit — match Mercari, the market leader, feature for feature and screen for screen, then use Rakuten’s balance sheet to undercut them with zero transaction fees and take share.

That strategy shaped my three years there more than any technical decision. The engineering problems worth writing about are the ones where I had to earn the right to deviate from it.

Two structural facts mattered. The codebase was inherited from a team that had rotated to other departments, and had been built on without much attention to structure or maintainability. And it existed as two near-identical Objective-C projects, one for Japan and one for Taiwan, differing only in minor market variations. Every fix had to be written twice.


Problem 1: A crash nobody had traced

I spent my first weeks reading the codebase and the business model rather than shipping. Most of what I found was ordinary — navigation bugs, broken feature states. One thing was not.

The Home screen’s infinite scroll was leaking memory. In table:cellForRowAtIndexPath: the asynchronous image load using AFNetworking UIImageView method setImageWithURL: captured the cell strongly inside its completion block. Here’s a demonstration of this common issue.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PhotoCell" forIndexPath:indexPath];
    
    NSString *urlString = self.photoDataSource[indexPath.row];
    NSURL *imageUrl = [NSURL URLWithString:urlString];
    
    [cell.loadingSpinner startAnimating];
    
    // ❌ WRONG IMPLEMENTATION: No weak pointers used here
    [cell.photoImageView setImageWithURL:imageUrl
                        placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]
                                 success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                     
                                     // ❌ BUG 1 (RETAIN CYCLE / LEAK): 
                                     // The block captures a STRONG reference to 'cell'. 
                                     // The cell cannot be deallocated until this network request finishes or times out.
                                     cell.photoImageView.image = image;
                                     [cell.loadingSpinner stopAnimating];
                                     
                                     // ❌ BUG 2 (VISUAL FLICKER / INCORRECT DATA):
                                     // By the time this network request finishes, 'cell' might have been recycled 
                                     // for row 45, but this block is still trying to apply row 2's image to it!
                                 }
                                 failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                     
                                     // ❌ RETAIN CYCLE / LEAK ALSO HAPPENS HERE:
                                     // Even on failure, 'cell' is captured strongly.
                                     cell.photoImageView.image = [UIImage imageNamed:@"error_image"];
                                     [cell.loadingSpinner stopAnimating];
                                 }];
    
    return cell;
}

Every reusable cell that scrolled past held a strong reference it never released. On a feed users scroll endlessly, in a legacy codebase that did not properly implement ARC, the leak compounded until the app slowed and then died. It hit hardest on older, lower-memory devices, which in this market was a large share of the user base.

The fix itself was small. The impact was not: App Store rating went from 3.4 to 4.8 and weekly downloads rose 20%. Crashes and scroll stutter stopped. It earned me second place at the Rakuten Award for that quarter, and more usefully, it earned me the standing to argue for what came next.


Problem 2: Two codebases, one product

Maintaining Japan and Taiwan as separate Objective-C projects meant every bug was fixed twice, every feature built twice, and the two inevitably drifted. I argued for consolidating them and migrating to Swift, and got approval.

I rewrote the app in Swift 3 on a parallel project over five months, while one team member kept the existing codebase alive and shipping features. The new structure was four projects: a shared library holding all business logic, a shared views project, and two thin wrapper projects producing the separate App Store targets each market required.

The goal wasn’t modern syntax. It was putting the logic in exactly one place so a fix happened once, and making the codebase legible enough that any engineer on the team could work anywhere in it.

  • Average bug-fix time: 4 days → half a day
  • Regressions per deployment: ~2 → 0.3

In hindsight the two wrapper projects were probably unnecessary. A single project with multiple targets would almost certainly have produced the same separation with less structural overhead. I was optimizing for a clean conceptual boundary between the markets and paid for it in build complexity.


Problem 3: Building what the leader hadn’t

Consolidation bought me credibility, and I spent it arguing to stop cloning Mercari. Eventually I was allowed to ship features they didn’t have. Three mattered.

Rich notifications and deep links, both newly available on iOS, so push messages could carry images and structured data, and email campaigns and QR codes could route users straight to a specific item or section via URL schemas the app parsed on launch.

AI-powered listings was the significant one. Rakuten’s ML department was training an image model that returned keywords for objects in a photo — genuinely cutting-edge work inside the company at the time. I worked with them through that effort, and when the API was ready I rebuilt the listing flow around it: photo capture became the first step instead of the last, so the app could call the model, get keywords back, and pre-fill the listing fields before the seller typed anything.

Sellers went from filling a form to confirming one. Listing time fell by 70%. It earned me the Next Generation Rakuten Grand Prize award.

That inversion, restructuring a user flow around what a model is good at, rather than bolting the model onto an existing flow, is the same move I made years later building AI-powered gift discovery at NeuNet.


Outcomes

Rating 3.4 → 4.8. Weekly downloads +20%. Bug-fix turnaround 4 days → half a day. Regressions per release ~2 → 0.3. Two internal awards, including second place at the Rakuten Award and Rakuten’s Next Generation Rakuten Grand Prize. Two market-specific apps consolidated into one maintainable codebase, and the first features in the product’s history that weren’t copied from a competitor.


Afterward

Rakuma was later merged with Fril, a competitor Rakuten acquired. By then I had moved to another department to lead the refactor of the Rakuten Beauty app — a webview shell offering a directory of beauty salons plus booking and payment — which I rebuilt solo as a full native Swift 3 app in three months. I left Rakuten shortly after for Crossborders Innovation.

Both products operate only in Japan, so the apps aren’t available on non-Japanese storefronts: fril.jp · beauty.rakuten.co.jp