ios - ObjC - How to explicitly hand off ownership to a block that will be performed asynchronously? -
this question relates ios apps in objective-c using mrr (not arc), , gcd (grand central dispatch).
the concurrency programming guide states (emphasis mine):
for blocks plan perform asynchronously using dispatch queue, safe capture scalar variables parent function or method , use them in block. however, you should not try capture large structures or other pointer-based variables allocated , deleted calling context. time block executed, memory referenced pointer may gone. of course, it safe allocate memory (or object) , explicitly hand off ownership of memory block.
this seems contradict blocks , variables doc states:
when block copied, creates strong references object variables used within block.
the latter statement seems describing how block captures pointer-based variables (objects). in other words, block implicitly taking ownership, not parent method explicitly handing off ownership block.
how 1 explicitly hand off ownership of object block? there way that, , needed in cases?
here's test.
given simple data class named employee few properties:
employee.h:
#import <foundation/foundation.h> @interface employee : nsobject @property (nullable, nonatomic, retain) nsnumber* empid; @property (nullable, nonatomic, retain) nsstring* firstname; @property (nullable, nonatomic, retain) nsstring* lastname; @end
a sample app calls dispatchaftertest test memory management in asynchronously dispatched blocks. test simpler begin with, kept getting expanded explore different possibilities.
- (void)dispatchaftertest { nslog(@"%s started", __func__); employee* employee = [employee new]; employee.empid = @(1001); employee.firstname = @"first name"; employee.lastname = @"last name"; nsmutablearray<employee*>* employeemutablearray = [nsmutablearray<employee*> new]; [employeemutablearray addobject:employee]; [employee release]; employee = [employee new]; employee.empid = @(1002); employee.firstname = @"adam"; employee.lastname = @"zam"; [employeemutablearray addobject:employee]; employee* employee3 = [employee new]; employee3.empid = @(1003); employee3.firstname = @"john"; employee3.lastname = @"kealson"; [employeemutablearray addobject:employee3]; nsarray<employee*>* employeearray = [[nsarray<employee*> alloc] initwitharray:employeemutablearray]; nsarray<employee*>* autoreleasedarray = [nsarray<employee*> arraywitharray:employeemutablearray]; // dispatch block asynchronously use employee object, employeemutablearray, , employeearray nslog(@"%s calling dispatch_after()", __func__); //dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", null); dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_default, 0); double delayinseconds = 5; dispatch_time_t poptime = dispatch_time(dispatch_time_now, delayinseconds * nsec_per_sec); dispatch_after(poptime, queue, ^(void){ nslog(@"%s start of dispatch_after block", __func__); nslog(@"%s empid=%@, firstname=%@, lastname=%@", __func__, employee.empid, employee.firstname, employee.lastname); // expecting exc_bad_access error if employee object has been deallocated. nslog(@"%s employeemutablearray:", __func__); (employee* emp in employeemutablearray) { nslog(@"%s id: %@ name: %@ %@", __func__, emp.empid, emp.firstname, emp.lastname); } nslog(@"%s employeearray:", __func__); (employee* emp in employeearray) { nslog(@"%s id: %@ name: %@ %@", __func__, emp.empid, emp.firstname, emp.lastname); } nslog(@"%s autoreleasedarray:", __func__); (employee* emp in autoreleasedarray) { nslog(@"%s id: %@ name: %@ %@", __func__, emp.empid, emp.firstname, emp.lastname); } employee.lastname = @"zammal"; nslog(@"%s employee.lastname changed %@", __func__, employee.lastname); nslog(@"%s employeemutablearray[1].lastname=%@", __func__, employeemutablearray[1].lastname); nslog(@"%s employeearray[1].lastname=%@", __func__, employeearray[1].lastname); nslog(@"%s autoreleasedarray[1].lastname=%@", __func__, autoreleasedarray[1].lastname); nslog(@"%s end of dispatch_after block", __func__); }); nslog(@"%s changing 1st employee's name john smith", __func__); employeemutablearray[0].firstname = @"john"; employeemutablearray[0].lastname = @"smith"; //nslog(@"%s releasing serial dispatch queue", __func__); //dispatch_release(queue); // not needed global dispatch queues, including concurrent dispatch queues or main dispatch queue. nslog(@"%s releasing employee objects , arrays (except autoreleasedarray)", __func__); [employee release]; [employee3 release]; [employeemutablearray release]; [employeearray release]; /* (int ii = 0; ii < 10; ii++) { // following nslog() throws exc_bad_access runtime error after dispatch_after block finishes. nslog(@"%s accessing employee object after release: id=%@, firstname=%@, lastname=%@", __func__, employee.empid, employee.firstname, employee.lastname); nslog(@"%s sleeping 1 seconds", __func__); [[nsrunloop mainrunloop] rununtildate:[nsdate datewithtimeintervalsincenow:1]]; } */ nslog(@"%s finished", __func__); }
and here's output:
2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] started 2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] calling dispatch_after() 2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] changing 1st employee's name john smith 2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] releasing serial dispatch queue 2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] releasing employee objects , arrays (except autoreleasedarray) 2016-03-06 14:49:56.127 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller dispatchaftertest] finished 2016-03-06 14:49:56.139 studyobjc_mrr_coredata[917:184234] -[contactstableviewcontroller tableview:viewforheaderinsection:] 2016-03-06 14:50:01.619 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke start of dispatch_after block 2016-03-06 14:50:01.620 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke empid=1002, firstname=adam, lastname=zam 2016-03-06 14:50:01.620 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke employeemutablearray: 2016-03-06 14:50:01.620 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1001 name: john smith 2016-03-06 14:50:01.621 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1002 name: adam zam 2016-03-06 14:50:01.621 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1003 name: john kealson 2016-03-06 14:50:01.621 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke employeearray: 2016-03-06 14:50:01.621 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1001 name: john smith 2016-03-06 14:50:01.622 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1002 name: adam zam 2016-03-06 14:50:01.622 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1003 name: john kealson 2016-03-06 14:50:01.622 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke autoreleasedarray: 2016-03-06 14:50:01.622 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1001 name: john smith 2016-03-06 14:50:01.622 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1002 name: adam zam 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke id: 1003 name: john kealson 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke employee.lastname changed zammal 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke employeemutablearray[1].lastname=zammal 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke employeearray[1].lastname=zammal 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke autoreleasedarray[1].lastname=zammal 2016-03-06 14:50:01.623 studyobjc_mrr_coredata[917:184289] __48-[contactstableviewcontroller dispatchaftertest]_block_invoke end of dispatch_after block
some things note.
- because of 5 second delay before block executed, employee, employeemutablearray, , employeearray objects released before block executed, proves did indeed implicitly take ownership of them. test runs delay increased 120 seconds or more , got same results.
- if
for
loop @ end of dispatchaftertest uncommented, throws exc_bad_access runtime error expected after dispatched block finishes. - changing 1st employee's name john smith in parent method after dispatching block shows block did not take copy of data @ time dispatched.
at 1 point in testing block threw nsinvalidargumentexception when think trying access employeearray autoreleased (i.e. created via [nsarray arraywitharray:]). i've not been able replicate exception, , unfortunately not have copy of test code @ time.
here's exception details:
2016-02-28 10:21:48.235 studyobjc_mrr_coredata[669:48826] __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke start of dispatch_after block 2016-02-28 10:21:48.236 studyobjc_mrr_coredata[669:48826] __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke empid=1002, firstname=adam, lastname=zam 2016-02-28 10:21:48.236 studyobjc_mrr_coredata[669:48826] __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke employeemutablearray: 2016-02-28 10:21:48.236 studyobjc_mrr_coredata[669:48826] __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke id: 1001 name: first name last name 2016-02-28 10:21:48.236 studyobjc_mrr_coredata[669:48826] __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke id: 1002 name: adam zam 2016-02-28 10:21:48.237 studyobjc_mrr_coredata[669:48826] -[calayer countbyenumeratingwithstate:objects:count:]: unrecognized selector sent instance 0x7f80b8f3d390 2016-02-28 10:21:48.241 studyobjc_mrr_coredata[669:48826] *** terminating app due uncaught exception 'nsinvalidargumentexception', reason: '-[calayer countbyenumeratingwithstate:objects:count:]: unrecognized selector sent instance 0x7f80b8f3d390' first throw call stack: ( 0 corefoundation 0x00000001040d0e65 __exceptionpreprocess + 165 1 libobjc.a.dylib 0x00000001037c0deb objc_exception_throw + 48 2 corefoundation 0x00000001040d948d -[nsobject(nsobject) doesnotrecognizeselector:] + 205 3 corefoundation 0x000000010402690a ___forwarding___ + 970 4 corefoundation 0x00000001040264b8 _cf_forwarding_prep_0 + 120 5 studyobjc_mrr_coredata 0x00000001032bcc5b __54-[contactstableviewcontroller dispatchasyncexperiment]_block_invoke + 955
my first guess block hadn't retained array since "autoreleased" object, changed initialized initwitharray: , manually released after dispatch_after() call.
autoreleasedarray added later try replicate exception, working fine.
restating question (if may turn 1 question many):
- is test method using safe way pass local object variables block executed asynchronously, or ownership need handed off explicitly block concurrency guide states?
- if ownership needs explicitly handed off block, how done?
- [bonus question] if current method isn't safe, how can modified prove isn't safe (i.e. cause deallocation issue) while still using same method (local object variables implicitly captured block)?
is test method using safe way pass local object variables block executed asynchronously, or ownership need handed off explicitly block concurrency guide states?
you're testing guide not saying. talking non-object allocations, malloc
'd array. block going take copy of pointer, incapable of doing pointed-to allocation. allocation that, need block's ultimate responsibility free memory.
(i'm not quite sure parenthetical remark "(or object)", reading it's misplaced. sentence seems mean "you can allocate memory , give block ownership; can allocate objects". it's possible tiny doc bug.)
it safe use nsobject
-types you're doing. quoted documented block behavior towards objects:
when block copied, creates strong references object variables used within block.
(and note concurrency guide says right after quote: "dispatch queues copy blocks added them".)
for captured values of type
id
— objective-c object pointers — object retained when block created [emphasis mine] (when execution passes on block) , released when block destroyed. ensures “captured” objects survive long block does.
(even without being compiled arc,) compiler doing right thing: if override dealloc
on employee
, see not called on instance until end of block. (when you're not compiling arc, can watch retain/release/dealloc events overriding methods on class.)
really, there's no other way blocks usably function (though it's not unreasonable wonder). alternative compiler handling barrelfull of boilerplate:
employee * bemployee = [employee retain]; dispatch_async(q, ^{ // use bemployee [bemployee release]; });
for every object wanted use inside block (or @ least explicit send of retain
/release
existing pointer).
further notes on objects in blocks , debugging: if weren't safe, turning on nszombie debugging feature best way confirm. in general, cocoa not overwrite or destroy deallocated objects, , still sit in memory until memory reused else. ref. can away messaging dangling pointer, not zombie.
Comments
Post a Comment