Uncovering NSTimer Memory Leaks, Android Focus Mechanics, and Cookie Management
This article analyzes why NSTimer can cause memory leaks in iOS, presents two ways to break the retain cycle, then dives into Android's focus system—including acquisition, distribution, and clearing—and finally explains how WebView and native code handle cookies on Android.
NSTimer Memory Leak in iOS
When NSTimer is created with scheduledTimerWithTimeInterval:target:selector:, the timer stores a strong reference to the target. If a UIViewController owns the timer and also passes self as the target, a retain cycle is formed: the view controller retains the timer and the timer retains the view controller. Because the reference count of the view controller never reaches zero, its dealloc method is never called and the memory is leaked.
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerTest)
userInfo:nil
repeats:YES];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@endSolution 1 – Use a block with a weak reference
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
}The block captures weakSelf, so the timer does not keep a strong reference to the view controller. When the view controller is deallocated, the timer is released as well, breaking the cycle.
Solution 2 – Introduce a proxy object
@interface Proxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target {
Proxy *proxy = [[Proxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
// Usage in ViewController
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:[Proxy proxyWithTarget:self]
selector:@selector(timerTest)
userInfo:nil
repeats:YES];The proxy holds a weak reference to the view controller, so the timer only retains the proxy. The proxy forwards selector invocations to the view controller without creating a strong reference, eliminating the retain cycle.
Android Focus Mechanism
In Android, “focus” determines which View receives keyboard or D‑pad input. A view must be focusable and, when the device is in touch mode, also focusableInTouchMode to obtain focus via requestFocus(). The system checks visibility, enablement, and touch‑mode eligibility before granting focus.
Requesting focus
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// must be focusable
if (!canTakeFocus()) return false;
// in touch mode, must be focusableInTouchMode
if (isInTouchMode() && !(mViewFlags & FOCUSABLE_IN_TOUCH_MODE)) return false;
// ancestors must not block descendant focus
if (hasAncestorThatBlocksDescendantFocus()) return false;
if (!isLayoutValid()) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
} else {
clearParentsWantFocus();
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}If the view passes all checks, handleFocusGainInternal updates the internal focus state and propagates the request up the view hierarchy.
Descendant focusability
FOCUS_BLOCK_DESCENDANTS – blocks all descendants from receiving focus. FOCUS_BEFORE_DESCENDANTS – the view gets focus before any of its children. FOCUS_AFTER_DESCENDANTS – the view gets focus only if none of its children want it.
By default ViewGroup calls setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS), which means a parent view can capture focus before its children.
Clearing focus
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
clearParentsWantFocus();
if (propagate && mParent != null) {
mParent.clearChildFocus(this);
}
onFocusChanged(false, 0, null);
refreshDrawableState();
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
boolean rootViewRequestFocus() {
View root = getRootView();
return root != null && root.requestFocus();
}The propagate flag determines whether the focus‑clearing request is sent to the parent, and refocus controls whether the root view should immediately try to acquire focus again. When both are true, rootViewRequestFocus() is invoked, which can make a call to clearFocus() appear ineffective because focus is instantly restored.
Common pitfalls:
Setting focusableInTouchMode=true on a Button causes the first tap to only change focus; the onClick event is delivered on the second tap because the MotionEvent.ACTION_UP handler gives priority to focus acquisition.
Calling clearFocus() may seem to do nothing if the parent view’s focusability strategy immediately re‑requests focus. Using setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS) on the parent or explicitly moving focus to another view resolves the issue.
Android Cookie Management
Cookies are small text fragments stored by the browser (or WebView) and sent with each HTTP request. Android provides a singleton CookieManager for reading and writing cookies associated with a WebView.
Basic API
CookieManager.getInstance().setCookie(url, cookie);
String cookieHeader = CookieManager.getInstance().getCookie(url);When a native HTTP client receives a Set‑Cookie header, the system does not automatically add the cookie to CookieManager. The application must parse the header and store the cookie manually.
Manual handling of Set‑Cookie headers
// Example for a native HttpURLConnection response
Map<String, List<String>> headerMap = connection.getHeaderFields();
List<String> setCookieHeaders = headerMap.get("Set-Cookie");
CookieSyncManager.createInstance(context);
CookieManager cm = CookieManager.getInstance();
cm.setAcceptCookie(true);
for (String header : setCookieHeaders) {
List<HttpCookie> httpCookies = HttpCookie.parse(header);
HttpCookie httpCookie = httpCookies.get(0);
String cookieString = buildCookie(httpCookie.getDomain(),
httpCookie.getName(),
httpCookie.getValue(),
System.currentTimeMillis() + httpCookie.getMaxAge() * 1000,
httpCookie.getSecure());
cm.setCookie(httpCookie.getDomain(), cookieString);
}Typical Set‑Cookie syntax:
Set-Cookie: TEST=1234567890; Expires=Wed, 21 Oct 2022 07:28:00 GMT; Domain=baidu.com; Path=/test; Secure; HttpOnlyWebView automatically includes stored cookies in outgoing requests, mirroring browser behavior. In multi‑process applications, cookie synchronization must be performed explicitly (e.g., using CookieSyncManager or the newer CookieManager.flush() method).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
